在挖掘客户端漏洞的时候,通常会关注应用对什么文件进行了读写操作,当我们能控制被读的文件或观测到敏感写入的文件,通常可以造成一定危害。本文详细介绍了如何通过frida监控文件读写操作。
在Linux系统下,文件的创建、读取、追加、写入和删除等操作涉及到以下系统调用:
在Frida中,**Interceptor.attach()**是一个用于拦截函数调用的函数。它可以让我们在目标进程中拦截任意函数调用,并在函数调用前或函数调用后执行自定义的JavaScript代码。
Interceptor.attach()函数的语法如下:
Interceptor.attach(target, callbacks)
其中,target参数指定要拦截的函数,可以是函数名或函数地址。callbacks参数是一个回调函数对象,用于定义拦截函数调用时要执行的代码。回调函数对象可以包含以下函数:
在Frida中,Interceptor对象是一个用于拦截函数调用和修改函数实现的工具集。除了**Interceptor.attach()**函数外,Interceptor对象还提供了以下几个常用的方法:
// Replace the implementation of strcmp()
function Interceptor.replace(
Module.findExportByName("libc.so", "strcmp"),
new NativeCallback(function (str1, str2) {
console.log("strcmp() called with arguments: " + Memory.readUtf8String(str1) + ", " + Memory.readUtf8String(str2));
return 0;
}, 'int', ['pointer', 'pointer']));
Interceptor.attachOnce(target, callbacks):用于一次性拦截函数调用。与Interceptor.attach()函数不同,Interceptor.attachOnce()函数只会拦截一次函数调用,然后自动解除拦截。这个方法的参数和Interceptor.attach()函数相同。
Interceptor.detachAll():用于解除所有拦截器。这个方法会解除所有通过Interceptor.attach()和Interceptor.attachOnce()函数添加的拦截器。
Interceptor.flush():用于刷新所有修改过的函数实现。在修改函数实现后,需要调用Interceptor.flush()函数来刷新目标进程中的函数缓存。这样做可以确保修改后的函数实现能够生效。
Module.findExportByName()是一个用于查找指定模块中导出函数地址的函数。它可以通过模块名称和函数名来查找函数地址,并返回一个指向函数地址的NativePointer对象。
Module.findExportByName()函数的语法如下:
Module.findExportByName(moduleName, symbolName)
其中,moduleName参数是要查找的模块名称,可以是模块文件名或模块名称字符串;symbolName参数是要查找的函数名称,可以是函数名字符串或函数地址。需要注意的是,如果moduleName是模块文件名,则需要包含文件路径信息,并且路径分隔符需要使用反斜杠(\)转义。
如果第一个参数是NULL,则表示在全局进行搜索函数。
以下是一个使用Module.findExportByName()函数查找libc.so模块中的printf()函数地址的示例:
var printfAddr = Module.findExportByName("libc.so", "printf");
console.log("printf() address: " + printfAddr);
在上述示例中,我们使用Module.findExportByName()函数查找libc.so模块中的printf()函数地址,并将结果输出到控制台。
需要注意的是,Module.findExportByName()函数只能在已经加载的模块中查找函数地址。如果要查找未加载的模块中的函数地址,可以使用Module.load()函数动态加载模块,并使用Module.getExportByName()函数查找函数地址。
基于上述相关知识,写出下列frida脚本
function hook_file_read() {
// Hook open、fopen、read函数
Interceptor.attach(Module.findExportByName(null, "open"), {
onEnter: function (args) {
var path = Memory.readUtf8String(args[0]);
this.path = path;
},
onLeave: function (retval) {
if (retval > 0) {
console.log("[open] path: " + this.path);
}
}
});
Interceptor.attach(Module.findExportByName(null, "fopen"), {
onEnter: function (args) {
var path = Memory.readUtf8String(args[0]);
this.path = path;
},
onLeave: function (retval) {
if (retval != 0) {
console.log("[fopen] path: " + this.path);
}
}
});
Interceptor.attach(Module.findExportByName(null, "read"), {
onEnter: function (args) {
this.fd = args[0].toInt32();
this.buf = args[1];
this.size = args[2].toInt32();
},
onLeave: function (retval) {
if (retval > 0 && this.fd > 0) {
var data = Memory.readByteArray(this.buf, retval >>> 0);
console.log("[read] fd: " + this.fd + ", size: " + retval + ", data: " + data);
}
}
});
// 3. Hook the write system call
Interceptor.attach(Module.findExportByName(null, "write"), {
onEnter: function (args) {
// Get the file descriptor and buffer address
var fd = args[0].toInt32();
var buf = args[1];
// Log the write call
console.log("[write] fd: " + fd + ", buf: " + buf);
}
});
// 4. Hook the lseek system call
Interceptor.attach(Module.findExportByName(null, "lseek"), {
onEnter: function (args) {
// Get the file descriptor and offset
var fd = args[0].toInt32();
var off = args[1].toInt32();
// Log the lseek call
console.log("[lseek] fd: " + fd + ", offset: " + off);
}
});
// 5. Hook the close system call
Interceptor.attach(Module.findExportByName(null, "close"), {
onEnter: function (args) {
// Get the file descriptor
var fd = args[0].toInt32();
// Log the close call
console.log("[close] fd: " + fd);
}
});
// 6. Hook the unlink system call
Interceptor.attach(Module.findExportByName(null, "unlink"), {
onEnter: function (args) {
// Get the file path
var path = Memory.readUtf8String(args[0]);
// Log the unlink call
console.log("[unlink] path: " + path);
}
});
}
// 调用hook_file_read函数
setImmediate(hook_file_read);
Interceptor.attach非常强大,主要还是看挖掘思路,往往能起到非常好的辅助效果。