//此代码出自《0day安全:软件漏洞分析技术》 //由于一开始读不懂,就慢慢一点点的加入了注释 //欢迎各位拍砖 //注释中实际内存地址,就是虚拟地址。 #include <stdlib.h> #include <stdio.h> int main() { _asm { CLD push 0x1e380a6a ;MessageBoxA push 0x4fd18963 ;ExitProcess push 0x0c917432 ;LoadLibraryA mov esi, esp ;esi指向的是LoadLibraryA lea edi, [esi - 0xc] ;edi指向的是未使用的栈地址起始位置,用来保存函数地址 xor ebx, ebx ;ebx = 0 mov bh, 0x04 ;ebx = 0x4 sub esp, ebx ;栈地址提升4个偏移 mov bx, 0x3233 ;32 push ebx push 0x72657375 ;user push esp ;将栈中放入user32 xor edx, edx ;清edx mov ebx, fs:[edx + 0x30] ;ebx进程环境块PEB的地址 mov ecx, [ebx + 0xc] ;ecx存放PEB_LDR_DATA结构体的指针 mov ecx, [ecx + 0x1c] ;ecx中存放指向模块初始化链表的头指针InInitializationOrderModuleList mov ecx, [ecx] ;InInitializationOrderModuleList中按顺序存放着PE装入运行时初始化模块的信息, ;第一个是ntdll.dll,ntdll.dll所指向的就是kernel32.dll mov ebp, [ecx + 0x8] ;ecx + 0x8就是kernel32.dll在内存中的加载基地址 find_lib_functions: lodsd ;lodsd是把DS:ESI所指向的地址处的数据放入eax中,也就是0x0c917432 ;然后ESI+4,此时ESI指向的是ExitProcess cmp eax, 0x1e380a6a ;此处用来判断是否把所有需要的函数地址都获取完成,MessageBoxA jne find_functions ;由于MessageBoxA在user32.dll中,因此,需要先加载user32.dll xchg eax, ebp ;使用ebp保存MessageBoxA的hash call [edi - 0x8] ;LoadLibraryA 参数在上面,push esp那里 xchg eax, ebp ;此时ebp为user32.dll的基地址,eax为MessageBoxA的hash find_functions: pushad ;Push(EAX);0x0c917432 LoadLibraryA ;Push(ECX);kernel32.dll链表节点 ;Push(EDX);为0 ;Push(EBX);进程环境块PEB的地址 ;Push(ESP);该ESP为执行pushad之前的ESP ;Push(EBP);kernel32.dll在内存中的加载基地址 ;Push(ESI);esi指向的是ExitProcess ;Push(EDI);edi指向的是未使用的栈地址起始位置,用来保存函数地址 ;此处入栈顺序必须记得,后面会使用到 mov eax, [ebp + 0x3c] ;Kernel32.dll基地址 + 0x3c是PE头 mov ecx, [ebp + eax + 0x78] ;此时ecx存放的是函数导出表的指针 add ecx, ebp ;导出表的此次加载实际内存地址 mov ebx, [ecx + 0x20] ;偏移0x20处指向导出函数函数名的列表 add ebx, ebp ;函数名的列表此次加载的实际内存地址 xor edi, edi ;edi清0,用来计数 next_function_loop: inc edi mov esi, [ebx + edi * 4] ;取出每个函数名的地址 add esi, ebp ;函数名的实际内存地址 cdq ;把EDX的所有位都设成EAX最高位的值,可以暂时简单理解为把EDX清0 hash_loop: ;此处循环计算hash movsx eax, byte ptr[esi] cmp al, ah ;判断是否把函数名计算完成 jz compare_hash ror edx, 7 add edx, eax inc esi jmp hash_loop compare_hash: cmp edx, [esp + 0x1c] ;由于刚刚的pushad,把寄存器都压入栈中,此时esp指向的是栈中的EDI, ;esp + 1c指向的则是EAX,LoadLibraryA jnz next_function_loop mov ebx, [ecx + 0x24] ;在导出表结构中,偏移0x24是导出函数序号的相对虚拟地址 add ebx, ebp ;导出函数序号的虚拟地址 mov di, [ebx + 2 * edi] ;获取第edi个函数的函数序号,由于函数序号是一个WORD型的数据,因此edi * 2 mov ebx, [ecx + 0x1c] ;此处为导出函数的相对虚拟地址 add ebx, ebp ;导出函数的虚拟地址 add ebp, [ebx + 4 * edi];获取函数序号所对应的函数地址,这就是我们最终所需要的函数地址。 xchg eax, ebp ;eax为所需要的函数地址 pop edi ;EDI出栈,用来保存LoadLibraryA的函数地址 stosd ;mov [edi],eax 函数地址保存 ;add edi,4 push edi popad ;此时,相对于pushad的时候,只有edi变化了,增加了4,其它寄存器均未变化 cmp eax, 0x1e380a6a ;当所有地址都获取完成后 jne find_lib_functions function_call: xor ebx, ebx push ebx push 0x2121216E ; push 0x3167304C ; mov eax, esp push ebx ;压入参数 push eax push eax push ebx call [edi - 0x04] ;调用MessageBoxA push ebx call [edi - 0x08] ;调用ExitProcess nop nop nop nop } system("pause"); return 0; }