如何使用Frida Hook带可变参数列表的C方法 提取参数并传递给其他C函数
Frida Hook可变参数C函数实现方案(以
execl为例) 针对execl(const char *filename, const char *arg0, ...)这类POSIX标准的可变参数函数,核心逻辑是利用其参数列表必须以NULL指针结尾的约定,结合Frida已经封装好的参数读取接口遍历拿值,不需要手动计算栈偏移。
可变参数读取实现
Interceptor.attach的回调里传入的args类数组对象,已经由Frida统一处理了不同架构下的寄存器传参、栈传参偏移,不管32位/64位程序,直接按索引读取即可:
- 固定参数按声明顺序对应
args[0]、args[1] - 可变参数从固定参数的下一个索引开始,逐位读取直到碰到NULL指针终止
const execlAddr = Module.getExportByName(null, "execl"); Interceptor.attach(execlAddr, { onEnter(args) { // 读取固定参数 this.filePath = args[0].readCString(); this.argList = [args[1].readCString()]; // 遍历读取可变参数 let argIdx = 2; while (true) { const curArg = args[argIdx]; if (curArg.isNull()) break; this.argList.push(curArg.readCString()); argIdx++; } console.log(`捕获execl调用,路径:${this.filePath},参数:${JSON.stringify(this.argList)}`); }, onLeave(retVal) { console.log(`execl调用返回值:${retVal.toInt32()}`); } });
注意:如果是
printf这类靠格式串判断参数个数的可变参函数,不能靠NULL终止遍历,需要先解析固定参数中的格式字符串,统计占位符数量和对应类型,再按索引读取对应位置的参数。
可变参数传递给其他C函数的方法
分两种常见场景处理:
- 目标函数和原函数签名一致(同样接收可变参数列表)
不要手动拼接栈结构,直接把所有参数(包括末尾必须的NULL指针)按顺序组成数组,用NativeFunction.apply传入即可。示例:
// 保存原函数引用,签名声明时固定参数后加...表示支持可变参 const origExecl = new NativeFunction(execlAddr, "int", ["pointer", "pointer", "..."]); // 组装新的参数列表,末尾必须带NULL指针 const callArgs = [ Memory.allocUtf8String("/bin/ls"), Memory.allocUtf8String("ls"), Memory.allocUtf8String("-al"), Memory.allocUtf8String("/root"), ptr(0) ]; // 直接调用即可 const ret = origExecl.apply(null, callArgs);
- 目标函数接收
va_list类型参数
不要手动构造va_list结构(不同架构的va_list内存布局差异极大,手动拼接极易崩溃),用NativeCallback做一层中转即可,Frida会自动把可变参数封装成可直接传递的va_list指针:
// 示例目标函数:接收va_list的execv类函数 const targetVFunc = new NativeFunction(Module.findExportByName(null, "execlv"), "int", ["pointer", "pointer", "pointer"]); // 中转函数:接收可变参,自动拿到va_list传给目标函数 const trampoline = new NativeCallback(function(path, arg0) { const vaList = arguments[2]; return targetVFunc(path, arg0, vaList); }, "int", ["pointer", "pointer", "..."]);
避坑提示
- 读取参数时要匹配类型:如果参数是int、long等基础数值类型,不要用
readCString(),对应调用toInt32()/toInt64()等方法转换。 - 不要手动计算栈偏移读取参数:Frida的
args对象已经兼容了x86、x86_64、arm、arm64的传参规则,手动算偏移在寄存器传参的场景下必然读错。 - 传递可变参时不要漏了末尾的终止符:NULL结尾的可变参函数如果没传最后的NULL指针,会读越界导致程序崩溃。
内容的提问来源于stack exchange,提问作者typeryougishiki
相关产品推荐
相关产品推荐

