Malicious driver from Equation APT

malana
6 min readJan 22, 2025

--

This write up is about analyzing a malicious driver supposedly form the infamous APT Equation. You can download the sample from the following link:

https://malshare.com/sampleshare.php?action=getfile&hash=888dba9b6af3eefee1af6835639b59022aa5ccf487cbdf0965887ca27f7c0478

MD5 dd3024193ef3e05ec51106966544fc42

SHA-1 4d3b600fd76d9905269e1e96bf2a42ed7a1d106f

SHA-256 888dba9b6af3eefee1af6835639b59022aa5ccf487cbdf0965887ca27f7c0478

Entry Point:

The entry point of the driver is the method DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) that takes a RegistryPath as an argument that will be used throughout the code. Then, at offset 0x00010DB7, it calls the method PsCreateSystemThread(&ThreadHandle, 0x1F03FFu, &ObjectAttributes, 0, &ClientId, f__main_Thread, 0) with the argument f__main_thread as the pointer to the main thread function where the juicy stuff happens.

Decrypting strings:

Following the execution path of the sample we will find 3 different decryption functions as follows:

Decryptor1 at offset 0x00011432 using Linear congruential generator algorithm with seed of 0x0AA107FB and it works on word-strings.

Decryptor2 at offset 0x0x11482 using Linear congruential generator algorithm with seed of 0x0AA107FB and it works on ascii-strings

Decryptor3 at offset 0x12330 it works on standard Unicode string object using a simple XOR algorithm.

You can find my decryption script in the following link it will add comments to the places where the encrypted strings will be used and rename the pointer: https://github.com/ongahaia/deobfuscation/blob/main/Decryptor.py

API resolving:

The sample resolves APIs on the fly using the following procedure:

at offset 0x00011582 we call it f__ResolveEncryptedAPIName_kernel which works as follows:

1. Calls the proper decryptor function to decrypt the API name.

2. Calls the fun f__ResolveAPIName_kernel (offset 0x0001185C):

i. It calls the function f__Query_SystemInformation(offset 0x00011FBA) to collect information about the loaded modules in the system using ZwQuerySystemInformation with SystemModuleInformation as argument.

ii. Then iterates through the loaded modules to find on that starts with nt and ends with .exe

iii. Then calls f__API_Resolve_Name (offset 0x 0001174E) with arguments of (module imageBase — decrypted API name) as follows:

Which in turn calls the function f__Extract_Export_directory (offset 0x0001163C) which walks the PEB to get to the IMAGE_EXPORT_DIRECTORY and gets the pointer to the corresponding API name.

Registry key read/write:

The sample uses the registry key “theDriverPath/Parameters” to read and write several values throughout the code. The function at offset 0x00011EA6 will do the ZwOpenKey, ZwQueryValueKey:

1. f__Read_Excluded_or_msvcp73 at offset 0x000104B4 attempts to read from the ValueName “Excluded” and if it doesn’t have any values it returns the previously encrypted hardcoded string “msvcp73.dll”.

2. f__Read_reg_bit_controlat offset 0x0001064A attempts to read from the ValueName “Control” a single bit at position of index (it will be clear in a bit why).

3. f__write_Status_DORD at offset 0x00010928 attempts to write a DWORD value in the ValueName “Status”, the DWORD Value is passed to it (either 1 or 0)

Now let’s dive into the functionality of the sample:

Starting at the f__main_thread offset 0X00010AFC, which starts by writing 0 to the “Status” KeyValue. Then decrypts 3 stored strings with the names of native windows applications and stores them in a string array

It Reads from the “Excluded” or returns the hard coded string “msvcp73.dll”.

It then checks for either of the 3 applications is running by calling f__check_if_process_exists (offset 0x00010A40) which returns 0 if the process found.

Then it writes 1 in the “Control” keyValue at the corresponding bit location of the index of the found process.

it attempts to attach the that process and prepare to inject some shell code:

It starts by decrypting three APIs

Then it calls the function f__FindAlertableThread_and_PID(offset 0x00011B78) which extracts process information about the previously found running process (services.exe, lsass.exe, winlogon.exe) by calling the function f__ExtractProcessInfo (offset 0x00011A14) which uses f__Query_SystemInformation as follows:

Then looping through the threads of the process to look for an alertableThread

It returns the thread id. Then it attempts to open the process whose thread was found:

Then gets the process ID and attach to it

After that we notice referencing 4 functions as trying to calculate the size of each function

Then it allocates a buffer in the attached process then allocates a local memory region of size 210h

Then calls the function f__prepare_Struct210 (offset 0x00010FBC) which starts by decrypting the names of APIs then gets the pointers by walking the PEB of kernel32.dll then using the function f__API_Resolve_Name

Then starts to copy information to the allocated memory region of 210h starting with the pointers to the resolved APIs and the contents of the previously read value from the “Excluded” keyValue, then copies the shellcode functions

Then it calls the function f__InjectAPC (offset 0x00011CCA) which starts by resolving APIs by their previously encrypted names:

Then calls the resolved KeInitializeApc:

For more information about this API and its applications, you could refer to the following links:

https://repnz.github.io/posts/apc/kernel-user-apc-api/#keinsertqueueapc

https://wikileaks.org/ciav7p1/cms/page_7995529.html

Eventually the driver detaches from the process and frees memory

Then writes 0 in the “control” bit and 1 in the “Status” keyValues

The ShellCode:

Shell code 1 dynamically invokes LoadLibraryW with arguments comes from either Excluded keyValue or Msvcp73.dll

Shell code 2 starts by calling shell code 1 then jumps to where ever eax is pointing to:

--

--

No responses yet