3.2 Windows API 32-Bit and 64-Bit Comparison

Let's look at an example of a 32-bit malware to understand how malware uses multiple API functions to interact with the operating system, and let's also try to understand how to interpret disassembly code to understand the operations performed by the malware. In the following disassembly output, the 32-bit malware calls the RegOpenKeyEx API to open a handle to the Run registry key. Since we are dealing with 32-bit malware, all the parameters to the RegOpenKeyEx API are pushed onto the stack. As per the documentation at https://msdn.microsoft.com/en-us/library/windows/desktop/ms724897(v=vs.85).aspx, the output parameter phkResult is a pointer variable (output parameter is indicated by the _Out_ annotation) that receives the handle to the opened registry key after the function call. Notice that at ➊, the address of phkResult is copied into the ecx register, and at ➋, this address is passed as the fifth parameter to the RegOpenKeyEx API:

lea  ecx, [esp+7E8h+phkResult] ➊
push ecx ➋ ; phkResult
push 20006h ; samDesired
push 0 ; ulOptions
push offset aSoftwareMicros ;Software\Microsoft\Windows\CurrentVersion\Run
push HKEY_CURRENT_USER ; hKey
call ds:RegOpenKeyExW

After the malware opens the handle to the Run registry key by calling RegOpenKeyEx, the returned handle (stored in the phkResult variable ➌) is moved into the ecx register and then passed as the first parameter ➍ to RegSetValueExW. From the MSDN documentation for this API, you can tell that the malware uses the RegSetValueEx API to set a value in the Run registry key (for persistence). The value that it sets is passed as the second parameter ➎, which is the string System. The data that it adds to the registry can be determined by examining the fifth parameter ➏, which is passed in the eax register. From the previous instruction ➐, it can be determined that eax holds the address of the variable pszPath. The pszPath variable is populated with some content during runtime; so, just by looking at the code, it's hard to say what data the malware is adding to the registry key (you can determine that by debugging the malware, which will be covered in the next chapter). But, at this point, by using static code analysis (disassembly), you can tell that malware adds an entry into the registry key for persistence:

mov   ecx, [esp+7E8h+phkResult] ➌
sub eax, edx
sar eax, 1
lea edx, ds:4[eax*4]
push edx ; cbData
lea eax, [esp+7ECh+pszPath] ➐
push eax ➏ ; lpData
push REG_SZ ; dwType
push 0 ; Reserved
push offset ValueName ; "System" ➎
push ecx ➍ ; hKey
call ds:RegSetValueExW

After adding the entry to the registry key, the malware closes the handle to the registry key by passing the handle it acquired previously (which was stored in the phkResult variable) to the RegCloseKey API function, as shown here:

mov   edx, [esp+7E8h+phkResult]
push edx ; hKey
call esi ; RegCloseKey

The preceding example demonstrates how malware makes use of multiple Windows API functions to add an entry into the registry key, which will allow it to run automatically when the computer reboots. You also saw how malware acquires a handle to an object (such as the registry key) and then shares that handle with other API functions to perform subsequent operations.

When you are looking at the disassembled output of the function from 64-bit malware, it might look different because of the way the parameters are passed in the x64 architecture (this was covered in the previous chapter). The following is an example of 64-bit malware calling the CreateFile function. In the previous chapter, while discussing the x64 architecture, you learned that the first four parameters are passed in registers (rcx,rdx, r8, and r9), and the rest of the parameters are placed on the stack. In the following disassembly, notice how the first parameter (lpfilename) is passed in the rcx register at ➊, the second parameter in the edx register at ➋, the third parameter in the r8 register at ➌, and the fourth parameter in the r9 register at ➍. The additional parameters are placed on the stack (notice that there is no push instruction) using mov instructions, at ➎ and ➏. Notice how IDA was able to recognize the parameters and add a comment next to the instructions. The return value of this function (which is the handle to the file) is moved from the rax register to the rsi register at ➐:

xor  r9d, r9d  ➍                           ; lpSecurityAttributes
lea rcx, [rsp+3B8h+FileName] ➊ ; lpFileName
lea r8d, [r9+1] ➌ ; dwShareMode
mov edx, 40000000h ➋ ; dwDesiredAccess
mov [rsp+3B8h+dwFlagsAndAttributes], 80h ➏ ; dwFlagsAndAttributes
mov [rsp+3B8h+dwCreationDisposition], 2 ➎ ; lpOverlapped
call cs:CreateFileW
mov rsi, rax ➐

In the following disassembly listing of WriteFile API, notice how the file handle which was copied into the rsi register in the previous API call, is now moved into the rcx register to pass it as the first parameter to the WriteFile function at ➑. In the same manner, the other parameters are placed in the register and on the stack, as shown here:

and  qword ptr [rsp+3B8h+dwCreationDisposition], 0
lea r9,[rsp+3B8h+NumberOfBytesWritten] ; lpNumberOfBytesWritten
lea rdx, [rsp+3B8h+Buffer] ; lpBuffer
mov r8d, 146h ; nNumberOfBytesToWrite
mov rcx, rsi ➑ ; hFile
call cs:WriteFile

From the preceding example, you can see that the malware creates a file and writes some content into the file, but when you are looking at the code statically, it is not clear what file the malware creates or what content it writes to the file. For example, to know the filename created by the program, you need to examine the content of the address specified by the variable lpFileName (passed as an argument to the CreateFile); but the lpFileName variable, in this case, is not hardcoded, and is populated only when the program runs. In the next chapter, you will learn the technique to execute the program in a controlled manner by using a debugger, which allows you to inspect the contents of the variable (memory locations).