调用方式之间的差异
C | syscall | stdcall | basic | fortran | pascal | |
参数入栈顺序 | 右 => 左 | 右 => 左 | 右 => 左 | 左 => 右 | 左 => 右 | 左 => 右 |
恢复平栈衡的位置 | 调用者 | 被调用者 | 被调用者 | 被调用者 | 被调用者 | 被调用者 |
函数调用约定
调用约定的声明 | 参数入栈顺序 | 恢复平栈衡的位置 |
__cdecl | 右 => 左 | 调用者 |
__fastcall | 右 => 左 | 被调用者 |
__stdcall | 右 => 左 | 被调用者 |
常用跳转指令与机器码的对应关系
机器码(十六进制) | 对应的跳转指令 | 机器码(十六进制) | 对应的跳转指令 |
FF E0 | JMP EAX | FF D0 | CALL EAX |
FF E1 | JMP ECX | FF D1 | CALL ECX |
FF E2 | JMP EDX | FF D2 | CALL EDX |
FF E3 | JMP EBX | FF D3 | CALL EBX |
FF E4 | JMP ESP | FF D4 | CALL ESP |
FF E5 | JMP EBP | FF D5 | CALL EBP |
FF E6 | JMP ESI | FF D6 | CALL ESI |
FF E7 | JMP EDI | FF D7 | CALL EDI |
Windows API 地址定位
Win 32:
- 首先通过段选择字 FS 在内存中找到当前的线程环境块 TEB;
- 线程环境块偏移位置为 0x30 的地方存放着指向进程环境块 PEB 的指针;
- 进程环境块中偏移位置为 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 的地方存放着指向函数导出表的指针;
- 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址。
- 导出表偏移 0x1C 处的指针指向存储导出函数偏移地址(RVA)的列表;
- 导出表偏移 0x20 处的指针指向存储导出函数函数名的列表;
- 函数的 RVA 地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的 RVA;
- 获得 RVA 后,再加上前边已经得到的动态链接库的加载基址,就获得了所需 API 此刻在内存中的虚拟地址,这个地址就是我们最终在 shellcode 中调用时需要的地址。