Jean-Baptiste's Blog

Part 2 of Analysing my first malware: Process Injection

Process Walking

In the last blog post, we were left with the following code

Process Walking Code

Here the local variable v35 and v36 could not be deduced properly by IDA as it could not resolve the APIs it was used with, but we now know that v35 is of type PROCESSENTRY32 because it is used with Process32First/Process32Next, let’s retype it and we get an even cleaner view of what’s happening.

Process Walking Code Retyped

The malware is doing process walking, iterating over each process and comparing names in order to find explorer.exe and get a handle to it. The desired access flags 1082 = 0x43A = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD indicates that the malware is certainly going to inject into this process.

Malware reusing variable

We also note that the malware reuses variables a lot voluntarily, making it harder to see what’s going on by just skimming over the code, so we have to rename variables as we analyse code everytime.

Writing Data Remotely

Remote Process Writing

Next the malware allocates and writes the strings table we decoded in the previous blog post to the memory of the remote process. The written data looks like this, hence the ptr_strings += 8

Written data

It does the same with some other weird empty strings, which do not make sense either at the moment

Written data

It then writes functions to the remote process.

Writing functions with VirtuallAlloc/WriteVirtualMemory

The nullsub_1 - sub_XXXX you see looks like a trick to make sure it copies each function entirely. The nullsub_1 is just a dummy function located at the end of the .text section, so the operation nullsub_1 - sub_XXXX result in the potentially maximum size of the function being copied.

The v75 array is important because it contains the pointers to where the functions were copied in the remote process, we rename it to InjectedFunctionsList.

Saving more functions to be exported

Finally they resolve Windows API functions and save their addresses into an array too. Then writes them to the remote process: if ( !(ZwWriteVirtualMemory)(hRemoteProc, buffer, WinApiFunctions, 180, &BytesWritten) ) Only the WinApiFunctions array seems written, but you can see they pass in a length of 180 bytes to be copied which is way bigger than our 32 bytes WinApiFunctions array

Look at the stack

Now you see that all the functions are contiguous on the stack, we can merge it all into a big array of 180 / 4 = 45 entries to make more sense out of this.

Merging stack variables into one big array

Finalizing Process Injection

I cleaned up the end of the main function and this is final thing the malware is doing:

// Allocate memory for savig WinApiFunctions array
WinApiFunctionsRemoteAddr = VirtualAllocEx(hRemoteProc, 0, 180, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if ( WinApiFunctionsRemoteAddr)
{
    // Copy WinAPI function from the current process to the remote process
    if ( !ZwWriteVirtualMemory(hRemoteProc, buffer, WinApiFunctions, 180, &BytesWritten) )
    {
        MalwareContext[0] = WinApiFunctionsRemoteAddr;
        
        // Allocate memory for saving the malware defined functions array
        MalwareFunctionsRemoteAddr = VirtualAllocEx(hRemoteProc, 0, 16, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if ( MalwareFunctionsRemoteAddr )
        {
            // Write the array of functions to be injected
            if ( !ZwWriteVirtualMemory(hRemoteProc, MalwareFunctionsRemoteAddr, InjectedFunctionsList, 16, &BytesWritten) )
            {
                MalwareContext[1] = MalwareFunctionsRemoteAddr;

                // Allocate memory for saving all the functions passed to the remote process 
                RemoteMalwareContext = VirtualAllocEx(hRemoteProc, 0, 968, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
                if ( RemoteMalwareContext )
                {
                    // Write all the functions and strings passed to the remote process
                    if ( !ZwWriteVirtualMemory(hRemoteProc, RemoteMalwareContext, MalwareContext, 968, &BytesWritten) )
                    {
                        CreateRemoteThread = GetProcAddress(hKernel32DLL, aCreateremoteth_0);

                        // Create a remote thread to the explorer.exe process
                        if ( CreateRemoteThread(hRemoteProc, 0, 0, StartRoutine, RemoteMalwareContext, 0, lpThreadId) )
                        {
                            (WinApiFunctions[8])(hRemoteProc);  // CloseHandle(hRemoteProc);
                            ExitProcess(0);
                        }
                    }
                }
            }
        }
    }
}

if ( (CreateRemoteThread)(hRemoteProc, 0, 0, StartRoutine, RemoteMalwareContext, 0, lpThreadId) ) The StartRoutine argument will be the thread’s entry point, and RemoteMalwareContext the entry point’s argument. RemoteMalwareContext contains all the addresses of the injected functions, and decrypted strings written into explorer.exe so that the malware can use them.

Conclusion

We analysed the techniques used by the malware to perform process injection. The malware uses the very well known and easy VirtualAllocEx/ZwWriteVirtualMemory/CreateRemoteThread (or their lower level NT equivalent) combination.

  1. VirtualAllocEx is responsible for allocating memory inside the remote process (where the malware will write its malicious code).
  2. ZwWriteVirtualMemory will simply copy from the malware process to the remote process.
  3. CreateRemoteThread creates a thread inside the remote process, executing the injected code.

Thanks for reading this post, see you in the next one :D