This article will be about the spawned resource1.exe, that we extracted in
article number 2.
Part 1
Part 2
Part 3
Filename |
sha256 |
resource1.exe |
f18afbad0230af8c7ec7b4c1d25544f3d3445a825861a1de18432de5b4586c7b |
- The most interesting part happens at 0x4020d0, a call to EnumWindows
installs a hook that is called on every found window on the current desktop.
This function is located at 0x401c1b and makes again use of the hook
KiUserExceptionDispatcher -> ud2 trick. As the target function just increments
the Eip in the given CONTEXT struct with 2.
0x00401c1b 8bff mov edi, edi
0x00401c1d 55 push ebp
0x00401c1e 8bec mov ebp, esp
0x00401c20 81eca8060000 sub esp, 0x6a8
0x00401c26 a170524000 mov eax, dword [0x405270]
0x00401c2b 33c5 xor eax, ebp
0x00401c2d 8945fc mov dword [ebp - 4], eax
0x00401c30 53 push ebx
0x00401c31 56 push esi
0x00401c32 33f6 xor esi, esi
0x00401c34 57 push edi
0x00401c35 89b5bcf9ffff mov dword [ebp - 0x644], esi
0x00401c3b e89df8ffff call 0x4014dd
0x00401c40 85c0 test eax, eax
0x00401c42 7507 jne 0x401c4b
0x00401c44 56 push esi
0x00401c45 ff1574104000 call dword [reloc.KERNEL32.dll_ExitProcess_116]
0x00401c4b 8d45ec lea eax, dword [ebp - 0x14]
0x00401c4e 50 push eax
0x00401c4f e87a180000 call 0x4034ce
0x00401c54 8985c8f9ffff mov dword [ebp - 0x638], eax
0x00401c5a 0f0b ud2
- Right behind the three ud2 instructions, the installed hook at
KiUserExceptionDispatcher will be removed and it will be checked if the
current process is a Wow64bit process. In this article i will follow the
32 Bit part. After checking the priviliges of the process, a ressource will
be decrypted like the one in the last article. With some changes to the
offset used to get the resource and probably the id, i was able to use my
vala script from the last article again. Part 2
uint64 off = getressource2(core, (uint64)0x406000, 5, 101, &len);
uint64 off2 = getressource2(core, (uint64)0x406000, 5, 102, &len2);
- A search for some modules and processes will decide the kind of execution
of the decrypted resource. This happens at 0x004039DD.
The function first gets all modules and processes found in the current context.
Its searching for strings located at 0x401288, 0x401278, 0x401264 and 0x401254.
The resulting strings are: cmdguard.sys, cfp.exe, klif.sys and avp.exe.
Note to check the values at these position:
[0x004039dd]> px 25 @ 0x401288
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00401288 6300 6d00 6400 6700 7500 6100 7200 6400 c.m.d.g.u.a.r.d.
0x00401298 2e00 7300 7900 7300 00 ..s.y.s..
0x004039dd 8bff mov edi, edi
0x004039df 55 push ebp
0x004039e0 8bec mov ebp, esp
0x004039e2 56 push esi
0x004039e3 57 push edi
0x004039e4 e878fdffff call 0x403761
0x004039e9 8bf0 mov esi, eax
0x004039eb e8fffdffff call 0x4037ef
0x004039f0 8bf8 mov edi, eax
0x004039f2 6888124000 push 0x401288
0x004039f7 57 push edi
0x004039f8 e808ffffff call 0x403905
0x004039fd 85c0 test eax, eax
0x004039ff 7421 je 0x403a22
0x00403a01 6878124000 push 0x401278
0x00403a06 56 push esi
0x00403a07 e89ffeffff call 0x4038ab
0x00403a0c 85c0 test eax, eax
0x00403a0e 7412 je 0x403a22
0x00403a10 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=4
0x00403a13 c74004a0204. mov dword [eax + 4], 0x4020a0 ; [0x4:4]=3
0x00403a1a c700b8204000 mov dword [eax], 0x4020b8
0x00403a20 eb40 jmp 0x403a62
0x00403a22 6864124000 push 0x401264
0x00403a27 57 push edi
0x00403a28 e8d8feffff call 0x403905
0x00403a2d 85c0 test eax, eax
0x00403a2f 7421 je 0x403a52
0x00403a31 6854124000 push 0x401254
0x00403a36 56 push esi
0x00403a37 e86ffeffff call 0x4038ab
0x00403a3c 85c0 test eax, eax
0x00403a3e 7412 je 0x403a52
0x00403a40 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=4
0x00403a43 c740044e1a4. mov dword [eax + 4], 0x401a4e ; [0x4:4]=3
0x00403a4a c700831a4000 mov dword [eax], 0x401a83
0x00403a50 eb10 jmp 0x403a62
0x00403a52 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=4
0x00403a55 c74004ab1a4. mov dword [eax + 4], 0x401aab ; [0x4:4]=3
0x00403a5c c700e01a4000 mov dword [eax], 0x401ae0
0x00403a62 56 push esi
0x00403a63 e815feffff call 0x40387d
0x00403a68 57 push edi
0x00403a69 e80ffeffff call 0x40387d
0x00403a6e 33c0 xor eax, eax
0x00403a70 5f pop edi
0x00403a71 40 inc eax
0x00403a72 5e pop esi
0x00403a73 5d pop ebp
0x00403a74 c20400 ret 4
- For cmdguard.sys and cfp.exe the returned offsets are
0x00403a13 c74004a0204. mov dword [eax + 4], 0x4020a0 ; [0x4020a0:4]=0x8b55ff8b
0x00403a1a c700b8204000 mov dword [eax], 0x4020b8 ; [0x4020b8:4]=0x8b55ff8b
- The kaspersky offsets are:
0x00403a43 c740044e1a4. mov dword [eax + 4], 0x401a4e ; [0x401a4e:4]=0x8b55ff8b
0x00403a4a c700831a4000 mov dword [eax], 0x401a83 ; [0x401a83:4]=0x8b55ff8b
- If none of these could be found
0x00403a13 c74004a0204. mov dword [eax + 4], 0x4020a0 ; [0x4020a0:4]=0x8b55ff8b
0x00403a1a c700b8204000 mov dword [eax], 0x4020b8 ; [0x4020b8:4]=0x8b55ff8b
- The trick with these offsets is that the first one [eax + 4] gets called
if the binary got called without admin rights. The other one if admin
rights are available. It seems that the main goal of this is to start
the extracted binary from the resource section.
No Antivir/Kaspersky with available admin rights
- These two are handled through the same function:
0x00401ae0 8bff mov edi, edi
0x00401ae2 55 push ebp
0x00401ae3 8bec mov ebp, esp
0x00401ae5 6a00 push 0
0x00401ae7 6a00 push 0
0x00401ae9 8d4508 lea eax, dword [ebp + 8]
0x00401aec 50 push eax
0x00401aed ff7508 push dword [ebp + 8]
0x00401af0 ff1550104000 call dword [reloc.KERNEL32.dll_GetCurrentProcessId_80]
0x00401af6 50 push eax
0x00401af7 e8e7100000 call 0x402be3
0x00401afc 33c0 xor eax, eax
0x00401afe 40 inc eax
0x00401aff 5d pop ebp
0x00401b00 c20400 ret 4
- The main difference is the first argument pushed on the stack.
Its '0' if no antivir has been found and '1' in case of Kaspersky module and
client process was found.
0x00401a83 8bff mov edi, edi
0x00401a85 55 push ebp
0x00401a86 8bec mov ebp, esp
0x00401a88 6a01 push 1
0x00401a8a 6a00 push 0
0x00401a8c 8d4508 lea eax, dword [ebp + 8]
0x00401a8f 50 push eax
0x00401a90 ff7508 push dword [ebp + 8]
0x00401a93 ff1550104000 call dword [reloc.KERNEL32.dll_GetCurrentProcessId_80]
0x00401a99 50 push eax
0x00401a9a e844110000 call 0x402be3
0x00401a9f 33c0 xor eax, eax
0x00401aa1 40 inc eax
0x00401aa2 5d pop ebp
0x00401aa3 c20400 ret 4
- The interesting stuff happens at 'call 0x402be3'. First of all it allocates
two memory spaces with the size of the decrypted resource block (0x4c000).
It then copies the header to the memory and also all sections to the
correct VirtualAddresses in the new buffer in order to execute the code
correctly.
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 0x00
union {
DWORD PhysicalAddress // 0x08
DWORD VirtualSize; // 0x08
} Misc;
DWORD VirtualAddress; // 0x0C
DWORD SizeOfRawData; // 0x10
DWORD PointerToRawData; // 0x14
DWORD PointerToRelocations; // 0x18
DWORD PointerToLinenumbers; // 0x1C
WORD NumberOfRelocations; // 0x20
WORD NumberOfLinenumbers; // 0x22
DWORD Characteristics; // 0x24
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
0x00402cf0 ff7654 push dword [esi + 0x54] ; IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeader
0x00402cf3 ff750c push dword [ebp + 0xc] ; src
0x00402cf6 50 push eax ; dst
0x00402cf7 e84c100000 call 0x403d48 ; memcpy
0x00402cfc 8365e800 and dword [ebp - 0x18], 0
0x00402d00 83c40c add esp, 0xc
0x00402d03 66837e0600 cmp word [esi + 6], 0 ; [0x6:2]=0
0x00402d08 7636 jbe 0x402d40
0x00402d0a 8b45ec mov eax, dword [ebp - 0x14]
0x00402d0d 83c014 add eax, 0x14 ; IMAGE_SECTION_HEADER.PointerToRelocation
0x00402d10 8945ec mov dword [ebp - 0x14], eax
0x00402d13 eb03 jmp 0x402d18
0x00402d15 8b45ec mov eax, dword [ebp - 0x14]
0x00402d18 ff70fc push dword [eax - 4] ; SizeOfRawData
0x00402d1b 8b08 mov ecx, dword [eax]
0x00402d1d 034d0c add ecx, dword [ebp + 0xc]
0x00402d20 8b40f8 mov eax, dword [eax - 8] ; VirtualAddress
0x00402d23 0345f8 add eax, dword [ebp - 8]
0x00402d26 51 push ecx
0x00402d27 50 push eax
0x00402d28 e81b100000 call 0x403d48
0x00402d2d 0fb74606 movzx eax, word [esi + 6] ; [0x6:2]=0 ; NumberOfSections
0x00402d31 8345ec28 add dword [ebp - 0x14], 0x28 ; IMAGE_SECTION_HEADER++
0x00402d35 83c40c add esp, 0xc
0x00402d38 ff45e8 inc dword [ebp - 0x18]
0x00402d3b 3945e8 cmp dword [ebp - 0x18], eax
0x00402d3e 72d5 jb 0x402d15
- Now the code adds 0xA0 to the IMAGE_NT_HEADER pointer. The DataDirectory
Array starts at 0x78 offset and one IMAGE_DATA_DIRECTORY entry is 8 bytes
in size. So the resulting DataDirectory index is calculated
(0xA0 - 0x78) / 8 = 5.
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; /* "PE"\0\0 */ /* 0x00 */
IMAGE_FILE_HEADER FileHeader; /* 0x04 */
IMAGE_OPTIONAL_HEADER32 OptionalHeader; /* 0x18 */
} IMAGE_NT_HEADERS32
typedef struct _IMAGE_OPTIONAL_HEADER {
/* Standard fields */
WORD Magic; /* 0x10b or 0x107 */ /* 0x00 */
BYTE MajorLinkerVersion; // 0x1A
BYTE MinorLinkerVersion; // 0x1B
DWORD SizeOfCode; // 0x1C
DWORD SizeOfInitializedData; // 0x20
DWORD SizeOfUninitializedData; // 0x24
DWORD AddressOfEntryPoint; /* 0x10 */ // 0x28
DWORD BaseOfCode; // 0x2C
DWORD BaseOfData; // 0x30
DWORD ImageBase; // 0x34
DWORD SectionAlignment; /* 0x20 */ // 0x38
DWORD FileAlignment; // 0x3C
WORD MajorOperatingSystemVersion; // 0x40
WORD MinorOperatingSystemVersion; // 0x42
WORD MajorImageVersion; // 0x44
WORD MinorImageVersion; // 0x46
WORD MajorSubsystemVersion; /* 0x30 */ // 0x48
WORD MinorSubsystemVersion; // 0x4A
DWORD Win32VersionValue; // 0x4C
DWORD SizeOfImage; // 0x50
DWORD SizeOfHeaders; // 0x54
DWORD CheckSum; /* 0x40 */ // 0x58
WORD Subsystem; // 0x5C
WORD DllCharacteristics; // 0x5E
DWORD SizeOfStackReserve; // 0x60
DWORD SizeOfStackCommit; // 0x64
DWORD SizeOfHeapReserve; /* 0x50 */ // 0x68
DWORD SizeOfHeapCommit; // 0x6C
DWORD LoaderFlags; // 0x70
DWORD NumberOfRvaAndSizes; // 0x74
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; /* 0x60 */ // 0x78
/* 0xE0 */
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
- This block reads the VirtualAddress of the BaseRelocation Table and adds the
result to the offset of the allocated memory ( [ebp - 8]) to get the position
of the copied relocation table in the allocated memory.
0x00402d40 8b86a0000000 mov eax, dword [esi + 0xa0] ; [0xa0:4]=0xf6b99169
0x00402d46 85c0 test eax, eax
0x00402d48 7477 je 0x402dc1
0x00402d4a 8b4df8 mov ecx, dword [ebp - 8]
0x00402d4d 8b96a4000000 mov edx, dword [esi + 0xa4] ; [0xa4:4]=0xf6aab239
0x00402d53 03c1 add eax, ecx
- The relocating table contains a VirtialAddress and a size that defines
the count of offsets for the actual block. It simply adds the current offset
to the VirtualAddress to get the position that needs to be relocated.
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
/* WORD TypeOffset[1]; */
} IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION;
- But first we have to get the delta between the original ImageBase and the
position of the buffer (desired new position). [ebp - 0xc] contains the
buffer with the copied resource and [esi + 0x34] is
IMAGE_NT_HEADERS.OptionalHeader.ImageBase.
0x00402d55 8b4df4 mov ecx, dword [ebp - 0xc]
0x00402d58 2b4e34 sub ecx, dword [esi + 0x34]
- Subtract size of VirtualAddress and SizeOfBlock and divide that with
2 to get the count of relocations for the current block.
0x00402d73 83ea08 sub edx, 8
0x00402d76 d1ea shr edx, 1
- VirtualAddress+buffer+offset lead us to the correct position that we
have to relocate. Finally we add the delta to this position.
0x00402d97 8b55e4 mov edx, dword [ebp - 0x1c]
0x00402d9a 81e2ff0f0000 and edx, 0xfff
0x00402da0 03ca add ecx, edx
0x00402da2 8b55e0 mov edx, dword [ebp - 0x20]
0x00402da5 0111 add dword [ecx], edx
- The relocated content from the VirtualAlloc`ed memory gets copied
to the memory allocated with VirtualAllocEx.
0x00402dc1 6a00 push 0
0x00402dc3 ff7650 push dword [esi + 0x50]
0x00402dc6 ff75f8 push dword [ebp - 8]
0x00402dc9 ff75f4 push dword [ebp - 0xc]
0x00402dcc ff75fc push dword [ebp - 4]
0x00402dcf ff55f0 call dword [ebp - 0x10] ; WriteProcessMemory
- Now the needet offsets to the used library functions will be collected
and saved in a struct that is used later. The function at 0x4021bd is called
like: function(modulebase, verysecretnumber) and returns the address of a
library function. I wrote a little python script to get the name of the used
function.
0x00402dd2 688dbdc13f push 0x3fc1bd8d
0x00402dd7 ff7508 push dword [ebp + 8]
0x00402dda e8def3ffff call 0x4021bd
#!/bin/python2
import pefile
import sys
import numpy as np
from ctypes import c_uint
from ctypes import c_int
def calc (name):
eax = np.uint32(0xffffffff)
ecx = np.int32(0)
esi = np.uint32(0)
for c in name:
eax = eax ^ np.uint32(ord(c))
ecx = 7
while ecx >= 0:
esi = eax
esi = esi & np.uint32(1)
esi = esi * -1
esi = esi & 0xedb88320
eax = eax >> 0x1
eax = eax ^ esi
ecx = ecx - 1
return np.uint32(~eax)
pe = pefile.PE(sys.argv[1])
num = int(sys.argv[2], 0)
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
if calc(exp.name) == np.uint32(num):
print exp.name
break
- The size of the resulting struct is 0x2c as the following call
to VirtualAllocEx offers.
0x00402e7b 6a04 push 4
0x00402e7d 57 push edi
0x00402e7e 6a2c push 0x2c ; Size
0x00402e80 6a00 push 0
0x00402e82 ff75fc push dword [ebp - 4] ; 0xffffffff
0x00402e85 ffd3 call ebx ; VirtualAllocEx
struct ShellcodeArguments {
void* GetProcAddress;
void* LoadLibraryA;
void* VirtualProtect;
void* VirtualFree;
void* RtlExitUserThread;
void* CsrClientCallServer;
void* UnpackedLoadedResource;
void* unknown_1;
void* kernel32.dll_base;
int unknown_2;
int unknown_3;
};
- Next some kind of shellcode will be copied in a new allocated 0x300 bytes
memory space.
0x00402ea9 6a00 push 0
0x00402eab 57 push edi
0x00402eac 8bd8 mov ebx, eax
0x00402eae 680c234000 push 0x40230c
0x00402eb3 53 push ebx
0x00402eb4 ff75fc push dword [ebp - 4]
0x00402eb7 895d08 mov dword [ebp + 8], ebx ; [0x8:4]=4
0x00402eba ff55f0 call dword [ebp - 0x10]
- It will be used to call CreateRemoteThread with 0xffffffff as process-id
lpStartAddress is the copied shellcode and lpParameter our created struct.
0x00402ebd 8d45d4 lea eax, dword [ebp - 0x2c]
0x00402ec0 50 push eax
0x00402ec1 33c0 xor eax, eax
0x00402ec3 50 push eax
0x00402ec4 ff750c push dword [ebp + 0xc]
0x00402ec7 53 push ebx
0x00402ec8 50 push eax
0x00402ec9 50 push eax
0x00402eca ff75fc push dword [ebp - 4]
0x00402ecd ff55d8 call dword [ebp - 0x28]
- The shellcodes loads all needet libaries and their imports and
finally calls AddressOfEntryPoint of the extracted binary. In the next
article i will cover what happens if the executable does not have admin rights
and what if comodo is running on the system.