shellcode中动态定位API

xiaoxiao2021-03-01  33

 

定位API的原理:

所有的win_32程序都会加载ntdll.dll和kerner32.dll这两个最基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址

  1,首先通过段选择字FS在内存中找到当前的线程环境快TEB。

  2,线程环境快偏移位置为0x30的地方存放着指向进程环境块PEB的指针。

  3,进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装在的动态链接库的信息。

  4,PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.

  5,模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll。

  6,找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址。

  7,从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。

  8,PE头偏移0x78的地方存放着指向函数导出表的指针。

  9,导出表0x1C处的指针指向存储导出函数偏移地址(RVA)的列表

            导出表偏移0x20处的指针指向存储导出函数函数名的列表

            函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA

             获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址。

PE导出表结构如下:

 

代码实现流程

 

具体代码实现

 编译环境VS2013+x86 测试环境server 2008 r2 x64 

__asm { CLD push 0x1e380a6a // 压入 MessageBoxA 字符串的hash push 0x4fd18963 // 压入 ExitProcess 字符串的hash push 0x0c917432; // 压入 LoadLibraryA 字符串的hash mov esi, esp // esi = LoadLibraryA 字符串 hash 地址 lea edi, [esi - 0xc] // 用于存放后边找到的 三个函数地址 // 开辟一些栈空间 xor ebx, ebx mov bh, 0x04 sub esp, ebx // 压入一个指向 user32字符串的 栈指针 mov bx, 0x3233 push ebx push 0x72657375 push esp xor edx, edx // 得到 kernel32.dll 的基地址 mov ebx, fs:[edx + 0x30] // FS得到当前线程环境块TEB TEB+0x30 是进程环境块 PEB mov ecx, [ebx + 0x0c] // PEB+0x0c 是PEB_LDR_DATA结构体指针 存放这已经被进程加载的动态链接库的信息 mov ecx, [ecx + 0x1c] // PEB_LDR_DATA+0x1c 指向模块初始化链表的头指针 InInitalizationOrderModuleList mov ecx, [ecx] // ecx指向 第二个节点 KERNELBASE.dll mov ecx, [ecx] // ecx指向 第三个节点 kernel32.dll mov ebp, [ecx + 0x08] // InInitializationOrderLinks+0x08 得到 DllBase 字段 即kernel32.dll基地址 find_lib_functions : lodsd; // 将[esi]中的4字节 传到eax中 cmp eax, 0x1e380a6a; // 比较 MessageBoxA 字符串的hash值 jne find_functions xchg eax, ebp // 记录当前hash值 call[edi - 0x8]; loadLibraryA xchg eax, ebp // 还原当前hash值 并且把exa基地址更新为 user32.dll的基地址 find_functions : pushad // 保存寄存器环境 mov eax, [ebp + 0x3c] // 得到 当前DLL PE头 循环依次是kernel32.dll 和 user32.dll mov ecx, [ebp + eax + 0x78] // 得到 当前DLL 导出函数表 相对地址 add ecx, ebp // 得到 当前DLL 导出函数表 绝对地址 mov ebx, [ecx + 0x20] // 得到导出函数 名称表 相对地址 add ebx, ebp // 得到导出函数 名称表 绝对地址 xor edi, edi // 初始化计数器 next_function_loop : inc edi // 函数计数器+1 mov esi, [ebx + edi * 4] // 得到 当前函数名的 相对偏移 add esi, ebp // 得到 当前函数名的 绝对地址 cdq; // 因为知道EAX<80000000 所以edx会被置0 相当于 xor edx,edx的功能 // 此指令只有一个字节 节约空间 算是一种优化 hash_loop: // 循环得到当前函数名的hash movsx eax, byte ptr[esi] // 得到当前函数名称 第esi的一个字母 cmp al, ah // 比较到达函数名最后的0没有 jz compare_hash // 函数名hash 计算完毕后跳到 下一个流程 ror edx, 7 // 循环右移7位 add edx, eax // 累加得到hash inc esi // 计数+1 得到函数名的下一个字母 jmp hash_loop // 循环跳到 hash_loop compare_hash : cmp edx, [esp + 0x1c] // 比较 目标函数名hash 和 当前函数名的hash jnz next_function_loop // 如果 不等于 继续下一个函数名 mov ebx, [ecx + 0x24] // 得到 PE导出表中的 函数序号列表的 相对位置 add ebx, ebp // 得到 PE导出表中的 函数序号列表的 绝对位置 mov di, [ebx + 2 * edi] // 得到 PE导出表中的 当前函数的序号 mov ebx, [ecx + 0x1c] // 得到 PE导出表中的 函数地址列表的 相对位置 add ebx, ebp // 得到 PE导出表中的 函数地址列表的 绝对位置 add ebp, [ebx + 4 * edi] // 得到 PE导出表中的 当前函数的绝对地址 // 循环依次得到kernel32.dll中的 LoadLibraryA ExitProcess // 和user32.dll中的 MessageBoxA xchg eax, ebp // 把函数地址放入eax中 pop edi // pushad中最后一个压入的是edi 正好是开始预留 用于存放的三个函数地址 的栈空间 stosd // 把找到函数地址出入 edi对应的栈空间 push edi // 继续压栈 平衡栈 popad // 还原环境 cmp eax, 0x1e380a6a // 比较是否是 MessageBoxA 函数 如果是说明全部函数已经找齐 可以调用函数执行功能 jne find_lib_functions function_call : xor ebx, ebx push ebx // 压入字符串结尾的0 push 0x74736577 push 0x6c696166 // 压入字符串 failwest mov eax, esp // 得到字符串 failwest 地址 push ebx push eax push eax push ebx call[edi - 0x04] // 调用 MessageBoxA(0, "failwest", "failwest", 0); push ebx call[edi - 0x08] // 调用 ExitProcess(0); nop nop nop nop }

参考《0day安全:软件漏洞分析技术》

转载请注明原文地址: https://www.6miu.com/read-4050217.html

最新回复(0)