You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在间接调用中获取函数地址?函数调用检测技术问询

问题描述

在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),属于内存间接调用,而非寄存器间接调用。所以你需要调整扫描逻辑,识别完整的指令格式:

  1. 解析ModR/M字节:
    遇到0xFF后,下一个字节是ModR/M字节,用来区分调用类型:

    • 如果ModR/M字节是0x15,说明是CALL [内存地址],后面的4字节(32位系统)就是funsum的全局变量地址。
    • 如果是寄存器间接调用(比如CALL [EAX]),静态扫描无法获取寄存器值,只能通过动态方法处理。
  2. 修改后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 23:22:22