如何在间接调用中获取函数地址?函数调用检测技术问询
问题描述
在foo函数内部调用了sum函数,现有scanner函数用于读取foo函数的指令。当scanner遇到FF opcode时,指向操作数寄存器,如何从此寄存器中获取funsum()的地址?是否有其他方法?简言之,我希望检测传入scanner的函数中是否调用了特定函数,例如检测pf中是否调用了malloc。
示例代码
#include <stddef.h> #include <stdio.h> #include <stdint.h> // foo中要调用的函数 int sum(int x, int y){ return x + y; } // 用于存储绝对地址的函数指针 int(*funsum)(int, int) = sum; // 调用funsum的函数 void foo(){ int result = funsum(1, 1); } // 读取foo中的指令 void scanner(void *pf){ unsigned char *f = pf; while (*f != 0xc3)// 循环直到遇到ret指令 { if(*f == 0xff){// 遇到调用指令 unsigned char *reg = f + 1; // 在这里如何获取funsum()的地址? } f++; } } int main(){ scanner(foo); }
解决方案
一、从FF opcode获取funsum地址的具体实现
首先要明确:你代码里foo调用funsum时,编译器生成的指令大概率是CALL [funsum](对应opcodeFF 15),属于内存间接调用,而非寄存器间接调用。所以你需要调整扫描逻辑,识别完整的指令格式:
解析ModR/M字节:
遇到0xFF后,下一个字节是ModR/M字节,用来区分调用类型:- 如果ModR/M字节是
0x15,说明是CALL [内存地址],后面的4字节(32位系统)就是funsum的全局变量地址。 - 如果是寄存器间接调用(比如
CALL [EAX]),静态扫描无法获取寄存器值,只能通过动态方法处理。
- 如果ModR/M字节是
修改后的scanner代码:
void scanner(void *pf){ unsigned char *f = (unsigned char*)pf; while (*f != 0xc3)// 循环直到遇到ret指令 { if(*f == 0xff){// 匹配调用指令的opcode unsigned char modrm = *(f+1); // 判断是否是 CALL [mem] 指令(ModR/M高2位为00,Reg字段为0101) if ((modrm & 0xC0) == 0x00 && ((modrm >> 3) & 0x07) == 0x05) { // 32位系统下,内存地址在ModR/M字节后4个字节 uint32_t *funsum_addr_ptr = (uint32_t*)(f + 2); // 直接比较这个地址是否是funsum的地址 if ((void*)funsum_addr_ptr == &funsum) { printf("检测到调用funsum\n"); } // 也可以获取funsum指向的函数地址,和sum比较 void *target_func = *(void**)funsum_addr_ptr; if (target_func == sum) { printf("检测到调用sum函数\n"); } } // 跳过当前CALL指令(FF 15 + 4字节地址,共6字节) f += 6; continue; } f++; } }
二、其他检测函数调用的方法
- 静态符号分析:如果程序保留了符号表(编译时不加
-strip参数),可以直接读取符号表,查看目标函数是否被待检测函数引用。这种方法不用扫描机器码,简单高效,但依赖符号信息。 - 动态插桩:用Pin、Frida这类工具,在程序运行时拦截所有函数调用,实时检测目标函数是否被指定函数触发。这种方法能处理动态绑定的调用(比如动态生成的函数指针),准确性更高。
- 反汇编库辅助:使用libopcodes、Capstone这类成熟的反汇编库,自动解析机器码的指令类型和操作数,不用手动处理opcode和ModR/M字节的细节,兼容性更强。
注意事项
- 上述代码仅适配32位x86架构的GCC编译产物,64位系统或其他编译器生成的指令格式会有差异(比如64位下内存地址是8字节)。
- 静态扫描无法处理运行时动态计算的函数指针调用,这类场景只能用动态插桩解决。
内容的提问来源于stack exchange,提问作者kaan
相关产品推荐
相关产品推荐

