# A Programmer's View On Conti-Locker

## Introduction

***

* I am writing this because a majority of articles summarize how this ransomware works but don't go into much detail on how the code works. I will be looking at the leaked source code of Conti-Locker ransomware and explaining in detail how it's code works using flowcharts and my knowledge on system programming. I hope this also helps future malware writers/ red-teamers make similar prototypes for adversary simulations. &#x20;
* Conti-Locker is a ransomware developed by the Conti Ransomware Gang, a Russian-speaking criminal collective with suspected links with Russian security agencies. Conti Ransomware Gang operated with a Ransomware-as-a-service (Raas) model. A summary on the gang can be found at the [forescout website](https://www.forescout.com/resources/analysis-of-conti-leaks/).
* In the Raas model, a ransomware developer/group designs and creates a ransomware and lends it to affiliates or "Operators" who use it on organizations in return for a percentage of the profit. This model has become infamous in the recent times due to it's popularity and ease of use. The technical intricacies of developing a ransomware rests on the group whereas the operators just utilize it to ransom organizations. This with addition with social-engineering means the ceiling for conducting cybercriminal activities has dropped down to such a level that even people with bare-minimum technical skills can conduct ransomware activities.
* The source code was leaked by an disgruntled affiliate in revenge for the cybercriminals siding with Russia on the invasion of Ukraine on January 25th, 2021.

## Encryptor

***

#### Setup

* The ransomware first sets up a custom structure named "string\_" which contains a character array of length 16384 bytes and a doubly linked tail queue which holds "**Entries**". This structure is then used in another doubly linked tail queue named "string\_list\_" which takes a "**STRING\_LIST**".&#x20;

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FiyxH53tLw126Cf9xUkzm%2Fimage.png?alt=media&#x26;token=e8917710-741a-4785-a800-5dd885fa6c64" alt=""><figcaption></figcaption></figure>

* After these structures are created, there is a function named "my\_stoi" which is a custom implementation of extracting integers from a string (probably to avoid winAPIs in the IAT). While the implementation is not that computationally efficient (because it multiplies digits by powers of 10 and adds them up), it does the work.&#x20;

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FRIxdPbUuaWuMXD5XePEw%2Fimage.png?alt=media&#x26;token=ed868477-e78b-428f-a477-08cb1099fe3c" alt=""><figcaption></figcaption></figure>

* Below that is a "**ParseFile**" function which supposedly takes in a FilePath and a list of filenames and parses their contents. The function expects the input file to be encoded in an OEM/DOS-style character set and converts each line to a Unicode (wide character) string before adding it to the list. While being a bit rudimentary, it's implementation is actually very clever. Instead of directly giving string literals, it is dynamically hashing the APIs and it's usage of TAILQ ensures that the parsing process is extremely efficient.&#x20;

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FS1d4skbMnQrDtAvHhv7c%2Fparse.jpg?alt=media&#x26;token=1d966e5b-adc5-4abf-b410-6bcb425d53dc" alt=""><figcaption></figcaption></figure>

* The command-line arguments are parsed by the "**GetCommandLineArg**" function which performs a non-case sensitive comparison of the CLI arguments against a list using **plstrcmpiW** WinAPI. But the actual handling of the arguments is done by "**HandleCommandLine**" function. Following are the CLI options of Conti-Locker -
  1. "-h" -> Specifies the HostsPath
  2. "-p" -> Specifies the pathlist
  3. "-m" -> Specifies the encryption mode
  4. "-log" -> Enables Logging
* Conti-Locker has 4 types of encryption modes available. We will look into thes soon.
  * ALL\_ENCRYPT (Enabled by default)
  * LOCAL\_ENCRYPT
  * NETWORK\_ENCRYPT
  * BACKUPS\_ENCRYPT
* More configuration options are available in the "global\_parameters.cpp" file including extensions, mutex names, etc.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FesNFoHN692nulAlcClf5%2Fimage.png?alt=media&#x26;token=57c296a9-3858-4fa9-b346-29563a7cc6d7" alt=""><figcaption></figcaption></figure>

#### Main Function

* Below is a simplified flowchart of what the ransomware does in general. We will delve deeper into what it does in detail soon.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FQtzimb0DSTW8sBa3SF05%2FPasted%20image%2020250820234218.png?alt=media&#x26;token=3c423104-b0a7-4d2e-a41d-3fbf52923540" alt=""><figcaption></figcaption></figure>

* As seen in the flowchart, the first thing that the ransomware does is create a system-wide mutex to ensure only one instance of the ransomware runs at a time. The mutex name, `"kjsidugidf99439"`, is obfuscated using the `OBFA` macro to prevent detection based on a known string. Also "**pWaitForSingleObject**" WinAPI is used to check if a mutex already exists. If it does, it immediately exits.
* Then it performs dynamic API resolution, which loads WinAPIs during runtime using **LoadLibrary** and **GetProcAddress**. This is defined in then namespace of **api::InitializeApiModule().**
* After that it finds and disables function hooks placed by antivirus (AV), Endpoint Detection and Response (EDR), or analysis tools on critical Windows API functions. By unhooking these functions, the ransomware can execute its operations (like file I/O or process manipulation) without being monitored or blocked.
* The code then initializes five separate linked lists using the `TAILQ` (tail queue) macro. These lists will be used to manage the malware's state and targets:
  * `g_WhitelistPids`: A list of Process IDs (PIDs) that should **not** be terminated by the `prockiller` module.
  * `DriveList`: A list of local drives (e.g., C:, D:) to be scanned for files.
  * `ShareList`: A list of network shares (e.g., `\\SERVER\share`) discovered during the network scan.
  * `g_PathList`: A list of specific file paths provided via the command line to be encrypted.
  * `g_HostList`: A list of specific network hosts provided via the command line to be targeted.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F4NpdsFBh1hP79VshwRvz%2Fimage.png?alt=media&#x26;token=3e9c556c-fa10-403e-94ce-2698c5087e05" alt=""><figcaption></figcaption></figure>

* It then parses CLI arguments and setups various ThreadPools for multi-threading.
* Before the encryption process though, it does pre-encryption steps to ensure no are the attack is successful -

  * `locker::DeleteShadowCopies()` - It executes a command (likely using `vssadmin.exe delete shadows /all /quiet`) to delete all Volume Shadow Copies. This prevents the victim from using the "Previous Versions" feature in Windows to easily restore their encrypted files.
  * `process_killer::GetWhiteListProcess(&g_WhitelistPids)`- Populates the `g_WhitelistPids` list with processes that are critical for system stability or for the ransomware's own operation, ensuring they are not terminated.
  * `locker::SetWhiteListProcess(&g_WhitelistPids)`- This passes the whitelist to the encryption module so it can avoid encrypting files that are currently in use by these whitelisted processes, preventing system crashes.

  <figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FUqnpvC74iXIlzgKvdzy5%2Fimage.png?alt=media&#x26;token=274b225a-86e4-4905-92ba-04e57e7f4752" alt=""><figcaption></figcaption></figure>
* Finally, the encryption process is started.  As per the provided encryption scope, the ransomware recursively enumerates local, network and backup drives and encrypts them. Multiple thread pools are started at once to encrypt files as soon as possible.
* Now that we know what the ransomware does in general, we can delve deeper.

#### Logging Capabilities

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FauUxl8ofg24E7yhlI8bQ%2Fimage.png?alt=media&#x26;token=201a4dfb-2430-45cd-9a07-06ae6f5d1215" alt=""><figcaption></figcaption></figure>

* This module is designed to record the malware's operational progress and any errors it encounters for debugging or tracking purposes by the malware authors. The ransomware utilizes a **CRITICAL\_SECTION** to synchronize threads writing to the log file. A logfile named - "**CONTI\_LOG.txt**" is created in the C drive and logs are written there.
* The malware's logging namespace has two functions, namely - "Init" and "Write". Init function initializes the critical section, creates the log file with appropriate permissions and makes it "**FILE\_FLAG\_WRITE\_THROUGH**" i.e it forces the operating system to write data directly to the disk, bypassing the system's write cache. This ensures that log entries are not lost if the system crashes or is abruptly powered off.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FduQnbLt13ZBN1vdufuca%2Fimage.png?alt=media&#x26;token=039f33c9-37ac-46db-9ea0-c7b5e19210f1" alt=""><figcaption></figcaption></figure>

* The Write function enables the thread pools to write their debug logs to the logfile. It first checks the availability of the log file, locks the critical section (If another thread is already inside this block, this thread will pause and wait until the other thread calls `pLeaveCriticalSection`) and writhes the log to the file.
* The log is written in the format of -> Timestamp: Log contents.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FJMBCGHlqowAyxy9s1RD8%2Fimage.png?alt=media&#x26;token=931d68c6-f315-4942-8f42-82aedbda59d2" alt=""><figcaption></figcaption></figure>

#### Obfuscation

* Before moving on to other parts, we need to quickly see how Conti-Locker obfuscates the strings to prevent static detection. This will make it easier to know how the anti-security measures are used. The "OBFW" macro that we have been seeing in the code up until now is a part of this obfuscation suite.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FyBImwRg1BIuPsVJuEHOn%2Fobf1.jpg?alt=media&#x26;token=f0f09bf0-0c9c-4674-9101-4bf1bfb1c23f" alt=""><figcaption></figcaption></figure>

* The engine first generates a random number during compilation. The numbers are not generated when the program runs, but when the compiler is building it. This is achieved through C++ template metaprogramming (TMP). It first creates a starting `seed` value by parsing the `__TIME__` preprocessor macro, which ensures the seed changes every second the code is compiled. It then uses this seed in a compile-time implementation of a `LinearCongruentialEngine`, a classic pseudo-random number algorithm, which recursively calculates a final random integer.&#x20;
* The core idea of the obfuscation engine is to obfuscate each byte of a string literal at compile time using random keys, then decrypt it at runtime only when the string value is needed, so that the decrypted string never exists persistently in the program’s binary data. It's flowchart is given below.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FJwVOmwlccvHCGo5FYEbK%2Fobf2.jpg?alt=media&#x26;token=f0f00d73-55b1-4eeb-9d36-525e5578a45f" alt=""><figcaption></figcaption></figure>

* It utilizes Extended Euclidean Algorithm at compile time to find the modular multiplicative inverse of an integer modulo for the affine encryption scheme used in this system. In this context, for any encryption parameter `A`, its modular inverse modulo 127 is needed during decryption to reverse the affine transformation applied at encryption.
* The main work is performed by the `MetaBuffer` template class, which is parameterized by two encryption keys, `A` and `B`, and a compile-time sequence of indexes indicating string positions. When a `MetaBuffer` is instantiated with a string and a sequence of indexes corresponding to its length, its constructor encrypts each character of the string at compile time using an affine cipher: `E(x) = (A * x + B) % 127`. The encrypted form is stored in a buffer. At runtime, calling the `decrypt()` method on the buffer will, if not already done, iterate through the buffer and apply the inverse affine decryption formula (`D(y) = A_inv * (y - B) % 127`, where `A_inv` is the modular inverse of `A`) to recover the original bytes, marking the buffer as decrypted.
* The `OBFA` and `OBFW` macros provide a convenient way for obfuscation directly on ANSI and Unicode string literals, respectively. Macros such as `_TSTR`, `_STR`, and `_WCS` allow for portability between character set types and conditionally switch the method of string handling based on build configuration.
* Using these macros essentially means that strings are encrypted at compile-time and decrypted at runtime. I found this extremely clever and I admire the creativity that went into this obfuscation engine.

#### Process Killer

* This is one of the modules that is present in the ransomware but isn't used that much. The Process killer namespace has 2 classes - GetWhiteListProcess and KillAll. The latter has been commented out and is out of scope, so we will be mainly talking about the first.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FfhtZSMpFvvxbOu12THdd%2Fprockiller.jpg?alt=media&#x26;token=6ec556be-bb8f-48ee-87f0-1c4255af687f" alt=""><figcaption></figcaption></figure>

* Before the function logic begins, the code defines the necessary data structures for managing the whitelist. The `PID` struct represents a single node in this list, containing a `DWORD` to hold the numeric `dwProcessId` and a `TAILQ_ENTRY` macro that embeds the necessary forward and backward pointers for list traversal. The `PID_LIST` type defines the head of this list, which acts as the main handle for the entire collection. The function `GetWhiteListProcess` accepts a pointer to one of these lists (`PPID_LIST`) as an `__out` parameter, indicating that the function's primary job is to populate this list, which was created and initialized elsewhere in the program.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FWCWy85yuzjgYEomO4Dcb%2Fimage.png?alt=media&#x26;token=a2d6aec3-9239-4330-8d7b-a3b77a78e4ed" alt=""><figcaption></figcaption></figure>

* The **GetWhiteListProcess** function identifies the process ID (PID) of the Windows Explorer shell (`explorer.exe`) and add it to a dynamically constructed list. This function's purpose is terminate the `explorer.exe` service to ensure it doesn't hold files open (like databases, office applications, or backup agents) to ensure they can be encrypted.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FsmkRZdJe3AQwUrp4awVz%2Fimage.png?alt=media&#x26;token=6d07399b-fb30-40f3-a3bd-09eacd46fbb0" alt=""><figcaption></figcaption></figure>

* The function's core operation begins by leveraging the Windows Tool Help Library to enumerate all processes currently running on the system. This is initiated with the `CreateToolhelp32Snapshot` API call, using the `TH32CS_SNAPPROCESS` flag to specify that it needs a point-in-time snapshot of all active processes.
* It then prepares a `PROCESSENTRY32W` structure, which will serve as a container for the information of each process retrieved from the snapshot. Finally with the setup complete, `Process32FirstW` is called to retrieve the first process from the snapshot, priming the main loop.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FkceSnaRdU90q100Oy8aM%2Fimage.png?alt=media&#x26;token=69895c7e-32e5-4ecf-a57c-b5cb9393ced8" alt=""><figcaption></figcaption></figure>

* This loop iterates over all the snapshotted process until it stumbles upon `"explorer.exe"`. Most importantly, the target string `"explorer.exe"` is not present as a plaintext literal. Instead, it is wrapped in the `OBFW` macro, which invokes the advanced compile-time string obfuscation engine.
* When the loop identifies the `explorer.exe` process, it proceeds to add its PID to the whitelist. It first allocates memory for a new `PID` list node using `m_malloc`. The process ID is copied from the `pe32.th32ProcessID` field into the `dwProcessId` member of the newly allocated node. Finally, the `TAILQ_INSERT_TAIL` macro is called. This function takes the list head (`PidList`), the new node (`Pid`), and the name of the entry member (`Entries`) and safely and efficiently appends the new node to the very end of the whitelist. This process is repeated for every instance of `explorer.exe` found, although typically there is only one.

#### FileSystem Scanner

* Conti-Locker utilizes it's `FileSystem` namespace to scan the local disks for data and files to encrypt. Similarly to before modules, it creates a `drive_info` structure for this namespace. This structure contains a wstring value - **RootPath** , which supposedly stores the file path of each scanned file and a TAILQ\_ENTRY macro.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FNaLcYT39w4AvVC3pxUHE%2Fimage.png?alt=media&#x26;token=1c97c250-1afe-48dc-b30a-9c7620f6d033" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F1EpKf2q6H8ILhZ6k6uh0%2Ffilesystem.jpg?alt=media&#x26;token=eb88a725-8ba9-43e2-b837-728ab845fd2f" alt=""><figcaption></figcaption></figure>

* The `EnumirateDrives` function identifies all logical drives present on the system (e.g., `C:\`, `D:\`, removable drives) and compiles them into a linked list for later processing. This serves as the starting point for the local encryption phase of the ransomware.
* It initializes a `TAILQ` (a type of linked list) named `DriveList`, which will store the root paths of the discovered drives and makes an initial call to the Windows API function `pGetLogicalDriveStringsW` with a `NULL` buffer. This is a standard technique to query the API for the required buffer size to hold all the drive strings.
* Then it allocates a memory buffer and calls `pGetLogicalDriveStringsW` a second time, now with the allocated buffer, which the API fills with a sequence of null-terminated strings (e.g., `"C:\"`, `"D:\"`, `"E:\"`), followed by a final extra null terminator to mark the end of the entire list.
* After that, it enters a **while** loop to parse the string buffer. The loop iterates through each null-terminated string in the buffer and for each drive string found (e.g., `"C:\"`), it creates a `DRIVE_INFO` structure, stores the drive's root path in the structure and finally uses `TAILQ_INSERT_TAIL` to add this `DRIVE_INFO` node to the end of the `DriveList.`

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FrYCGr67dcu2bCrRr4xim%2Fimage.png?alt=media&#x26;token=42db867f-7fbe-4af5-9a9b-8c30a6b4a728" alt=""><figcaption></figcaption></figure>

* Optionally if the logging function is enabled, it writes a summary message stating the total number of drives found (e.g., "Found 3 drives:") and the path of each individual drive found.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FOPINsM7vU2SDBCWnjJhg%2Fimage.png?alt=media&#x26;token=f0ccd38c-090f-4730-80f0-7bc03be841fe" alt=""><figcaption></figcaption></figure>

* Before going to the next major function, we need to go over some minor utilities first. The utility **`MakeSearchMask`** takes a directory path and creates a search mask suitable for `FindFirstFileW`. It correctly appends `\*` to the path, ensuring it handles paths that do or do not end with a backslash.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FwYIUpHip7xKYv06IXS87%2Fimage.png?alt=media&#x26;token=bf1205a8-b6ff-47d8-a816-d70013ecbeff" alt=""><figcaption></figcaption></figure>

* The **`MakePath`** utility safely combines a directory path and a filename into a full path, adding a backslash only if needed.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FzTXN27qk9YGLcSvEL05o%2Fimage.png?alt=media&#x26;token=618c6995-f3a4-484f-a739-eb093006686b" alt=""><figcaption></figcaption></figure>

* Next, the `CheckDirectory` utility checks if a given directory name contains any substrings from a hardcoded blacklist and the `CheckFilename` utility performs blacklisting for files. The hardcoded blacklist is given in the picture below.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F8uR2vs2PW0pPyKzGV4Mf%2Fimage.png?alt=media&#x26;token=e8ec014d-93a4-497f-b7cb-d3d0fd45d5d8" alt=""><figcaption></figcaption></figure>

* The **`DropInstruction`** function is responsible for creating the ransom note file (`R3ADM3.txt`) in a given directory. It calls `global::GetDecryptNote` to get the content of the note and writes it to the file using `pCreateFileW` and `pWriteFile`.
* The main `SearchFiles` function aggregates everything to execute a comprehensive, non-recursive file search using a breadth-first search (BFS) algorithm. It initializes a `TAILQ` to act as a queue for directories to visit, starting with the provided `StartDirectory`. The main `while` loop continues as long as this queue is not empty, dequeuing one directory at a time for processing.
* For each directory, it immediately calls `DropInstruction` to plant the ransom note. It then uses `pFindFirstFileW` and `pFindNextFileW` to enumerate every file and subdirectory within it.  If the item is a directory and not on the `CheckDirectory` blacklist, it is added to the end of the queue for future processing. If the item is a file and it passes the `CheckFilename` blacklist checks, it is deemed a target.
* &#x20;Instead of encrypting the file directly, this function acts as a "producer" in a producer-consumer pattern; it submits the full file path as a task to a designated thread pool via `threadpool::PutTask`. This architecture allows the file discovery process to run in parallel with the CPU-intensive encryption process, maximizing throughput. The function even includes a back-pressure mechanism, temporarily suspending itself if the task queue grows too large, which prevents excessive memory consumption.
* Finally the `StartLocalSearch` function serves as the high-level entry point that initiates the entire local filesystem attack. It is designed to be executed in its own thread, created by a function like `CreateThread.` &#x20;
* The function receives a single argument: a pointer to the `DRIVE_LIST` that was populated by the `EnumirateDrives` function. It then iterates through this list using a `TAILQ_FOREACH` loop. For each drive it finds in the list, it logs the action for debugging purposes and then calls the main `SearchFiles` function, passing the drive's root path as the starting point and specifying the `LOCAL_THREADPOOL` as the target for all encryption tasks generated.

#### Network Scanner

* After scanning the local filesystem, Conti-Locker moves on to the network if given the option. In a nutshell, this module enumerates SMB shares, asynchronously connects to them and adds them to a `HostList`.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FiKCpJppPUk1Jjr7GrSsu%2Fnetwork%20scan.jpg?alt=media&#x26;token=59f25d9c-d7ac-47d1-9d30-f33c0838e224" alt=""><figcaption></figcaption></figure>

* The module begins by defining the core data structures and global variables that manage the state of the entire scanning operation. These structures are built around `TAILQ` queues. The structures are -
  * **`SUBNET_INFO` and `SUBNET_LIST`**: This list is used to store the base addresses of all local subnets that need to be scanned (e.g., 192.168.1.0, 10.0.0.0). The scanner will generate all possible host IPs within these subnets.
  * **`HOST_INFO` and `HOST_LIST`**: This is a thread-safe work queue. Once the scanner confirms that a specific IP address is alive and has the SMB port (445) open, a `HOST_INFO` structure containing that IP is added to this list. A separate worker thread will consume hosts from this list to perform the next stage of the attack.
  * **`CONNECT_CONTEXT` and `CONNECTION_LIST`**: This is the most complex structure, designed specifically for the asynchronous IOCP model. Each `CONNECT_CONTEXT` holds all the state for a single connection attempt to a single IP address. This includes the `OVERLAPPED` structure required by IOCP, the `SOCKET` for the connection, the target IP address (`dwAddres`), and a `State` flag (`CONNECTED`, `CONNECTING`, `NOT_CONNECTED`). The `CONNECTION_LIST` holds all the connection contexts for a single batch of IP addresses being scanned simultaneously.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F8lf5NS6kjQdamOqlfMLp%2Fimage.png?alt=media&#x26;token=955fe11d-25d2-4f48-a06f-8ac632764c23" alt=""><figcaption></figcaption></figure>

* Before the scanning can begin, Conti-Locker gathers information about its environment and loads necessary functions. The functions are -

  * **`GetConnectEX`**: This function dynamically loads the `ConnectEx` function pointer. `ConnectEx` is a powerful Microsoft-specific Winsock extension that can initiate a connection and wait for its completion asynchronously. To get a pointer to it, the code must create a dummy socket and use the `WSAIoctl` function with the `SIO_GET_EXTENSION_FUNCTION_POINTER` command.&#x20;
  * **`GetSubnets`**: It queries the system's ARP table (the mapping of IP addresses to physical MAC addresses on the local network) by calling `GetIpNetTable`. It then iterates through every entry in this table, intelligently filtering for IPs that belong to private network ranges. For each valid private IP it finds, it extracts the subnet (by taking the first three bytes of the IP address, e.g., 192.168.1) and adds it to the `SubnetList`, ensuring that each unique subnet is only added once. This effectively tells the malware which network segments to target for its port scan.
  * **`EnumShares`**: Once a live host has been identified, this function is called to enumerate its available file shares. It uses the `NetShareEnum` Windows API function to query the remote machine for a list of its shares. It specifically filters for disk shares (`STYPE_DISKTREE`) and special or temporary shares, while explicitly blacklisting the administrative `ADMIN$` share to avoid unnecessary noise or permission errors. For each valid, accessible share found, it constructs the full UNC path (e.g., `\\192.168.1.10\Public`), logs the discovery, and adds the path to a `ShareList` for subsequent processing by the file encryption engine.

  <figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FjqrbbBwO8BGRSSRsY0jE%2Fimage.png?alt=media&#x26;token=0406c342-6bcc-43b6-bf0a-9429f1085842" alt=""><figcaption></figcaption></figure>
* The actual scanning happens in the `PortScanHandler` and it's helper functions. This is a dedicated worker thread that runs an event loop centered on `GetQueuedCompletionStatus` and blocks until an asynchronous I/O operation completes and is posted to the `g_IocpHandle.`
* This function handles three types of events -
  1. **`START_COMPLETION_KEY`**: This message kicks off a new batch scan. It calls `CreateHostTable` to prepare connection contexts for an entire subnet.
  2. **`CONNECT_COMPLETION_KEY`**: This signals that a `ConnectEx` attempt has finished. If the connection was successful, it calls `AddHost` to place the live IP onto the shared `g_HostList`. It also decrements the `g_ActiveOperations` counter.
  3. **`TIMER_COMPLETION_KEY`**: This is a timeout mechanism. After a batch scan is initiated, a 30-second timer is set. If the timer fires and there are still active operations (`g_ActiveOperations > 0`), it means some connections are stuck. The code then cancels these hung operations using `CancelIo` to ensure the scanner never gets permanently stuck.
* The helper functions of `PortScanHandler` are -> **`CreateHostTable (`**&#x54;his function prepares a batch of hosts for scanning by generating all possible IP addresses), **`ScanHosts(`**&#x4F;nce the host table is created, this function iterates through every connection context and fires off an asynchronous `g_ConnectEx` call targeting port 445 on each IP) and **`AddHost(`**&#x61; thread-safe function that adds a confirmed live host to the `g_HostList`, uses the `g_CriticalSection` to prevent race conditions and checks against the local machine's own IP addresses to avoid the malware attacking itself).
* The Host and Share Handler function (`HostHandler`) runs in a separate worker thread and acts as the "consumer" to the port scanner's "producer." Its job is to take the live hosts discovered by the scanner and perform the actual attack.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FXBoASuqXUiJyib8v8wFZ%2Fimage.png?alt=media&#x26;token=8a771cbb-5c5a-4d93-a303-83817e671172" alt=""><figcaption></figcaption></figure>

* The `HostHandler` runs in an infinite loop, continuously checking the `g_HostList`. If the list is empty, it sleeps for a second and checks again. When a host appears in the list, the thread wakes up, locks the critical section, removes the `HOST_INFO` from the queue, and unlocks the critical section.
* It then calls `network_scanner::EnumShares` to get a list of all shared folders on that host. After finding the shares, it iterates through each one and immediately hands off the UNC path to `filesystem::SearchFiles`, specifying `NETWORK_THREADPOOL` as the destination for encryption tasks. Lastly if it dequeues a host with the special `STOP_MARKER` address, it knows the scan is complete, and it exits its loop.
* The main entry point of this module is the `StartScan` function. It initializes and coordinates the entire network scanning operation.

#### Anti-Hooking

* Now that everything been enumerated and the malware knows what to do, it can start it's encryption process. But just before the encryption process, it invokes to anti-hooking module to remove function hooks from critical dlls. The `removeHooks` function contains the core logic and orchestrates the entire process of finding and removing API hooks from a specified loaded module (e.g., `ntdll.dll` or `kernel32.dll`).&#x20;
* The core strategy is to get a clean, unmodified copy of the target DLL to use as a reference. The execution flow is as follows -> It calls `GetModuleFileName` to retrieve the full file path of the target module handle (`hmodule`) on the disk and uses the dynamically resolved `CreateFile` to open this file with read-only access. It then uses `CreateFileMapping` to create a file-mapping object from the file handle. Finally, it calls `MapViewOfFile`. This maps the entire contents of the DLL file into the current process's memory as a read-only block (`originDll`).&#x20;
* With the clean DLL mapped into memory, the code now needs to find the location of every function it exports. It does this by manually parsing the PE file structure of the `originDll` buffer.
* After all this, it iterates over function, performing a quick check on the first byte of the in-memory function (`funcHooked`) to look for the `JMP` instruction. Next, it performs a more definitive check by comparing the first few bytes of the pristine function code (`funcAddr`) with the first few bytes of the in-memory code (`funcHooked`). If they do not match, the `funcIsHooked` flag is set to true.
* If a hook is detected, the code proceeds to remove it. It does that in the following way -
  * It calls `VirtualProtect` on the memory region of the hooked function (`funcHooked`) to change its memory permissions from the typical read-execute to **read-write-execute**. This is a necessary step, as executable code sections are normally not writable.
  * It then uses `CopyMemory` to copy the first 10 bytes from the unhooked function (`funcAddr`) over the top of the hooked function (`funcHooked`). This overwrites the `JMP` instruction and any other patched bytes, effectively restoring the function to its original, untampered state.
  * Finally, it calls `VirtualProtect` a second time to revert the memory permissions back to their original state.

#### Encryption Algorithm

* Finally we come to the encryption process. Conti-Locker utilizes ChaCha20 for it's encryption process. The encryption module is two-fold. One is the actual encryption using ChaCha20 and the other is creation of thread pools to do the encryption parallelly. Let's talk about the thread pools first. It's flowchart is given below.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FCWan2CfvrCaotbFhbviV%2Fimage.png?alt=media&#x26;token=d4f77d90-cc76-4e7f-99d9-d89e48f5a57c" alt=""><figcaption></figcaption></figure>

* The system is designed to manage three distinct pools, identified by the `THREADPOOLS` enum: `LOCAL_THREADPOOL`, `NETWORK_THREADPOOL`, and `BACKUPS_THREADPOOL`. This separation allows the malware to allocate different numbers of threads and potentially different priorities for encrypting local files versus files on network shares or backups.
* The central `THREADPOOL_INFO` structure holds all the state for a single pool, including an array of thread handles (`hThreads`), counters for threads and tasks, a `CRITICAL_SECTION` for thread-safe queue access, the task list itself (`TaskList`, a `TAILQ`), and components for managing workflow, such as an event handle (`hQueueEvent`) and a flag for back-pressure (`IsWaiting`).
* The `Create` function allocates the necessary resources for a specific pool, including the array for thread handles and the synchronization objects. The `Start` function is then called to launch the worker threads; it iterates the specified number of times and calls `CreateThread`, passing the `ThreadHandler` function as the entry point for each new thread.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FdI9wMS0lBTMCYPRYbARH%2Fimage.png?alt=media&#x26;token=af6e70d7-6513-4934-a624-f7eed484ff00" alt=""><figcaption></figcaption></figure>

* The `PutTask` function serves as the interface for producer threads to add work to the queue. It is a thread-safe operation that locks the critical section, creates a `TASK_INFO` struct containing the filename, and adds it to the tail of the list. The `SuspendThread` function, called by the producer, waits on an event object, effectively blocking the producer until the queue has been drained to a manageable level by the consumer threads. Finally, the `Wait` function provides a graceful shutdown mechanism. It sends a special `STOP_MARKER` task to each worker thread and then calls `WaitForMultipleObjects`, blocking until all threads in the pool have finished their current work.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FRRlZBRA4IyqPl9byDxkk%2Fimage.png?alt=media&#x26;token=32f000ba-a81f-4460-b2f7-de913ca46a9f" alt=""><figcaption></figcaption></figure>

* The main showrunner of the thread pool part is the `ThreadHandler` function. Each thread created by the `threadpool::Start` function begins execution here and remains in this function for its entire lifetime. At the outset, each thread performs a one-time initialization to prepare for its encryption duties. It allocates a large, 5MB memory buffer (`BufferSize`) for efficient file I/O operations. It also establishes a persistent cryptographic context by calling `GetCryptoProvider` to acquire a handle to the Microsoft CryptoAPI provider and then uses `pCryptImportKey` to import the global, hardcoded attacker's RSA public key (`g_PublicKey`). This setup is done once per thread.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FxiB5FE4111WaPwpqWRCL%2Fimage.png?alt=media&#x26;token=75cee284-9f4c-48ee-b1ba-9aecfe6b6f4e" alt=""><figcaption></figcaption></figure>

* After initialization, the thread enters its main `while (TRUE)` loop, where it continuously attempts to dequeue and process tasks. It first locks the pool's critical section, then checks the head of the `TaskList`. If a task is available, it is removed from the queue, and the task counter is decremented. If the producer thread was suspended (`IsWaiting` is true) and the task count has now dropped to half of the maximum, the worker thread calls `SetEvent` on `hQueueEvent` to signal the waiting producer thread that it can resume adding new tasks.
* After unlocking the critical section, the worker processes the task. It checks if the filename is the special `STOP_MARKER`, and if so, it exits the loop to terminate. Otherwise, it prepares a `locker::FILE_INFO` structure and passes it, along with the pre-initialized crypto provider, RSA key handle, and I/O buffer, to the main `locker::Encrypt` function.
* Upon successful encryption, the worker calls `locker::ChangeFileName` to append the ransomware's extension to the file, finalizing the attack on that specific file. After processing is complete, it securely cleans up any handles or keys associated with the file and deletes the `TASK_INFO` object before looping back to get the next task. Now onto the encryptor.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FoabNZuqNLLztvYJTzGLb%2Fimage.png?alt=media&#x26;token=a4b8fa0f-479d-450c-9210-12822c854022" alt=""><figcaption></figcaption></figure>

* Before encryption, Crypto-Locker executes the `locker::DeleteShadowCopies` function. This function uses the Windows Management Instrumentation (WMI) framework via COM interfaces to systematically eradicate all Volume Shadow Copies (VSS), which are Windows' built-in file versioning and system restore points. By doing so, it eliminates the victim's easiest and most immediate path to recovery.
* The function then initializes the COM library for multi-threaded operation, sets appropriate security levels, and connects to the `ROOT\CIMV2` WMI namespace. It also sets the `__ProviderArchitecture` context to ensure it interacts with the native 64-bit WMI provider. It then executes a WQL query (`SELECT * FROM Win32_ShadowCopy`) to enumerate all existing shadow copies. For each one found, it extracts its unique ID and programmatically constructs a command line to delete it using the `WMIC.exe` utility, a method that is both powerful and reliable.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FaacN9jXrJi0CEqXeycMU%2Fimage.png?alt=media&#x26;token=e9e34148-71ed-4b22-80b2-02ad5e0ad0b6" alt=""><figcaption></figcaption></figure>

* Along with the deletion of shadow copies, Conti-locker runs the `KillFileOwner` function in parallel. This function uses the Windows Restart Manager API to restart the applications holding a lock on system files. It starts a Restart Manager session, registers the locked file path, and retrieves a list of all processes using it. If the process is whitelisted, the function aborts to avoid system instability or self-termination. If the locking processes are not on the whitelist, it calls `RmShutdown` with the `RmForceShutdown` flag, forcibly terminating them.
* Two helper functions, `CheckForDataBases` and `CheckForVirtualMachines`, are used for for the hybrid encryption scheme. Each contains an extensive, hardcoded list of file extensions commonly associated with databases (`.sql`, `.mdb`, `.mdf`, `.dbf`, etc.) and virtual machine disks (`.vmdk`, `.vhd`, `.vdi`, `.qcow2`, etc.). These functions perform a quick, case-insensitive check on a file's name to determine if it belongs to one of these.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FEj1xs1hCxBJgxUJip6Ix%2Fimage.png?alt=media&#x26;token=4d0f907e-dce3-4020-b932-5d62d1e47b6c" alt=""><figcaption></figcaption></figure>

* Conti-Locker utilizes ChaCha20 along with RSA for the encryption process. This is implemented in the `GenKey` function. For every single file to be encrypted, a unique 32-byte symmetric key and an 8-byte nonce (IV) are generated using the `CryptGenRandom` function. This key/IV pair is used to initialize a ChaCha20 cipher context (`ECRYPT_ctx`), which will perform the fast, bulk encryption of the file's content. This unique ChaCha20 key and IV are then immediately encrypted using a hardcoded, master RSA public key that was imported once per worker thread. The result of this RSA operation is a 512-byte encrypted key blob.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FrAAJ3AOjXm0urU1gGWm8%2Fimage.png?alt=media&#x26;token=c746f5cc-e345-4731-ab5d-ba106f2c0b65" alt=""><figcaption></figcaption></figure>

* After successfully generating the cryptographic keys and opening the target file (using `KillFileOwner` if necessary), it enters a decision tree to select one of three encryption modes based on the file's type and size.&#x20;
* Files identified as high-value databases are subjected to `EncryptFull`, which overwrites every single byte of the file with encrypted data.&#x20;
* Virtual machine disks are targeted with a specific `EncryptPartly` mode that encrypts three 7% chunks of the file.&#x20;
* For general files, the strategy is size-dependent: small files (under 1MB) are fully encrypted; medium files (1MB to 5MB) are subjected to `EncryptHeader`, which only encrypts the first megabyte which makes them unreadable to most programs; and large files (over 5MB) are encrypted with an intermittent `EncryptPartly` mode that encrypts five 10% chunks.
* Once the file content has been encrypted, the `WriteEncryptInfo` function is called. This function appends a metadata footer to the end of the now-encrypted file. This footer contains the 512-byte RSA-encrypted ChaCha20 key blob, the original 64-bit file size, and a byte indicating which encryption mode was used. This information is essential for the decryption tool to properly reverse the process.&#x20;
* With the footer successfully written, the `ChangeFileName` function renames the file by appending the ransomware's custom extension. Finally, the `CloseFile` function performs a secure cleanup.

## Decryptor

***

* Majority of the modules of the decryptor are the same as the encryptor, so I will be skipping them. I will however be analyzing the main core decryption module of the decryptor. The flowchart of the decryption module is given below.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F6IJK46U2nI7ECl9IzugY%2Fdecrypt.jpg?alt=media&#x26;token=6c6f8035-e88b-420b-819a-83e012beb3c8" alt=""><figcaption></figcaption></figure>

* The decryptor mirrors the locker's logic with precision, reading the metadata footer from each encrypted file, decrypting the per-file symmetric key using the attacker's master private key, and then applying the correct decryption algorithm based on the mode used during encryption.
* The central data structure is `decryptor::FILE_INFO`, which is designed to hold all the state required to decrypt a single file. It contains fields for the filename, a handle to the file, the original and encrypted file sizes, the ChaCha20 key and IV, and the `EncryptMode` and `DataPercent` values that will be read from the encrypted file's metadata.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FVy7farx32QyZroP34G2l%2Fimage.png?alt=media&#x26;token=e1638723-897a-42fa-bbe8-ccdba745d481" alt=""><figcaption></figcaption></figure>

* The core function `decryptor::Decrypt` first calls  `OpenFileDecrypt`. This helper function opens the target file and then immediately calls `ReadEncryptInfo`. `ReadEncryptInfo` seeks to a position 534 bytes from the end of the file, which is where the locker engine wrote the metadata footer.&#x20;
* It reads this 534-byte block, which contains the 512-byte RSA-encrypted ChaCha20 key/IV, the 1-byte encryption mode, the 1-byte data percentage, and the 8-byte original file size. This information is parsed and stored in the `FileInfo` struct.
* The function then calls `CryptDecrypt`, providing it with the attacker's master private key (`PrivateKey`). It uses this private key to decrypt the 512-byte `EncryptedKey` blob that was just read from the file's footer. This recovers the original, unique 32-byte ChaCha20 key and 8-byte IV that were used to encrypt this specific file.
* With the plaintext ChaCha20 key and IV now available, the function calls `ECRYPT_keysetup` and `ECRYPT_ivsetup` to initialize the ChaCha20 stream cipher context (`FileInfo->CryptCtx`).

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FQfU2OuGubVhuFVk74sTC%2Fimage.png?alt=media&#x26;token=901e9dd2-8510-4974-b71a-6e5992e379db" alt=""><figcaption></figcaption></figure>

* Now according to the decryption mode, it calls one of the below three functions -
  * **`DecryptFull`**: This function reads the entire file from beginning to end in large chunks (up to 5MB at a time). For each chunk, it calls `ECRYPT_decrypt_bytes` to decrypt the data in-place in the buffer. It then seeks the file pointer back to the beginning of the chunk it just read and overwrites the encrypted data with the newly decrypted plaintext using the `WriteDecryptedData` helper function. This process continues until the entire file has been read, decrypted, and rewritten.
  * **`DecryptHeader`**: This function mirrors the `EncryptHeader` logic. It decrypts only the first 1,048,576 bytes (1MB) of the file, leaving the rest untouched. It reads, decrypts, and rewrites the data in the same manner as `DecryptFull` but stops after reaching the 1MB limit.
  * **`DecryptPartly`**: It uses a `switch` statement based on the `DataPercent` value (either 20 or 50) read from the file's metadata to calculate the exact size of the encrypted chunks (`PartSize`) and the size of the unencrypted gaps (`StepSize`). It then enters a loop, performing a series of seeks and partial decryptions. For example, for the 50% mode, it will decrypt a 10% chunk, seek forward over the next 10% unencrypted gap, decrypt the next 10% chunk, and so on, until all five encrypted parts have been restored.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FzLrCJ88Cny1yq643ZfkV%2Fimage.png?alt=media&#x26;token=80d6dd7b-b949-4408-badb-ef2ed6998167" alt=""><figcaption></figcaption></figure>

* After the content has been decrypted, the function seeks back to the position 534 bytes from the end of the file and calls `SetEndOfFile`. This truncates the file, removing the metadata footer and restoring the file to its exact original size. The file is renamed back to it's original filename using `decryptor::ChangeFileName` .
* In the end of the source code, there is a commented out function that handles the decryption process asynchronously. This means that the malware authors likely had plans to integrate the IOCP model (discussed in the network scanner module) into the decryptor.

## Conclusion

***

* The ransomware's design is a case study in modern malware development. The use of multiple thread pools in a producer-consumer model for both filesystem scanning and encryption helps cripple a system as rapidly as possible. This is complemented by a pretty good encryption strategy that uses a hybrid ChaCha20 and RSA scheme, intelligently adapting its method to fully, partially, or header-only based on file type and size to maximize damage. Furthermore, the deletion of Volume Shadow Copies ensures that once infected, the victim **HAS** to pay the ransomware gang.
* As stated in the introduction, understanding these intricate mechanisms is invaluable. For security researchers and blue teams, this code provides a clear blueprint of the advanced threats that modern defenses must counter. For red-teamers and those involved in adversary simulation, it serves as an inspiration in building effective and evasive tools.
