使用Frida框架来挂钩(hook)Android应用程序中的动态链接库(so)中的函数
// 定义一个函数hook_so
function hook_so(){
// 找到libxiaojianbangA.so的基地址
var soAddr = Module.findBaseAddress("libxiaojianbangA.so");
// 获取bss段中某个函数的地址
var bssFunc = soAddr.add(0x14018);
// 读取bssFunc的真实函数地址
var realFunc = bssFunc.readPointer();
// 附加Interceptor到真实函数
Interceptor.attach(realFunc, {
onEnter: function(args){
// 真实函数进入时,打印args[0]的十六进制dump
console.log("realFunc args[0]: ", hexdump(args[0]));
},
onLeave: function(retval){
// 真实函数退出时,无操作
}
});
// 枚举进程中的所有模块
var modules = Process.enumerateModules();
// 遍历所有模块
for(var i = 0; i < modules.length; i++){
// 检查真实函数是否在当前模块的范围内
if(realFunc > parseInt(modules[i].base) && realFunc < (parseInt(modules[i].base) + modules[i].size)){
// 计算真实函数相对于当前模块基地址的偏移量
var realFuncOffset = realFunc - parseInt(modules[i].base);
// 打印当前模块的名称和真实函数的偏移量
console.log("modules[i].name: ", modules[i].name, realFuncOffset.toString(16));
}
}
}
// 定义主函数main
function main(){
// 调用hook_so()函数
hook_so();
}
// 使用setImmediate来延迟执行main函数
setImmediate(main);
这段代码的作用是:
- 找到
libxiaojianbangA.so
库的基地址。 - 获取bss段中某个函数的地址,并读取其指向的真实函数地址。
- 使用Frida的
Interceptor.attach
方法,将钩子(hook)附加到真实函数上。 - 当真实函数被调用时,记录函数的输入参数(args[0]),并打印其十六进制dump。
- 枚举进程中的所有模块,并检查真实函数是否在某个模块的范围内。
- 如果真实函数在某个模块的范围内,计算它相对于该模块基地址的偏移量,并打印模块的名称和偏移量。
- 使用
setImmediate
来延迟执行main
函数。 - 可以监控和分析Android应用程序中
libxiaojianbangA.so
库的真实函数的执行情况,这对于调试、分析和修改应用程序的行为非常有用。
hexToBytes用于将十六进制字符串转换为字节数组。
// 定义一个函数hexToBytes
function hexToBytes(str) {
// 初始化位置变量pos为0
var pos = 0;
// 获取字符串的长度
var len = str.length;
// 检查字符串长度是否为偶数,因为十六进制字符串的长度必须是偶数
if (len % 2 != 0) {
// 如果不是偶数,返回null
return null;
}
// 将字符串长度除以2,得到字节数组的长度
len /= 2;
// 创建一个新的数组hexA来存储字节值
var hexA = new Array();
// 遍历字节数组的长度
for (var i = 0; i < len; i++) {
// 获取当前字符串中2个字符的字符串
var s = str.substr(pos, 2);
// 将字符串转换为整数
var v = parseInt(s, 16);
// 将整数添加到hexA数组中
hexA.push(v);
// 增加pos的值,以便下次循环时读取下一个字符串
pos += 2;
}
// 返回字节数组
return hexA;
}
这段代码的作用是:
- 检查输入的十六进制字符串的长度是否为偶数,如果不是,则返回null。
- 如果字符串长度为偶数,则将其长度除以2,得到字节数组的长度。
- 创建一个新的数组
hexA
来存储字节值。 - 遍历字节数组的长度,每次从输入字符串中取出2个字符,并将其转换为整数。
- 将转换后的整数添加到
hexA
数组中。 - 返回字节数组
hexA
。 可以将十六进制表示的字符串转换为字节数组,这在处理二进制数据时非常有用。
stringToBytes用于将字符串转换为字节数组
// 定义一个函数stringToBytes
function stringToBytes(str) {
// 创建一个空数组re来存储字节
var re = [];
// 遍历输入字符串的每一个字符
for (var i = 0; i < str.length; i++) {
// 获取当前字符的Unicode编码
ch = str.charCodeAt(i);
// 创建一个空数组st来存储当前字符的字节
var st = [];
// 进行循环,将字符的Unicode编码转换为字节
do {
// 获取当前字符的最高位
st.push(ch & 0xFF);
// 右移ch,获取下一位
ch = ch >> 8;
} while (ch);
// 将转换后的字节数组re与st数组连接起来
re = re.concat(st.reverse());
}
// 返回字节数组re
return re;
}
这段代码的作用是:
- 创建一个空数组
re
来存储字节。 - 遍历输入字符串的每一个字符。
- 获取当前字符的Unicode编码。
- 创建一个空数组
st
来存储当前字符的字节。 - 进行循环,将字符的Unicode编码转换为字节。
- 将转换后的字节数组
re
与st
数组连接起来。 - 返回字节数组
re
。 可以将字符串转换为字节数组,这在处理文本和二进制数据时非常有用
枚举导入导出表
使用Frida框架来枚举和打印`libxiaojianbang.so`库的导入(imports)
和导出(exports)信息。
// 定义一个函数hookTest1
function hookTest1(){
// 使用Module.enumerateImports来枚举libxiaojianbang.so库的导入信息
var imports = Module.enumerateImports("libxiaojianbang.so");
// 遍历导入信息数组
for(var i = 0; i < imports.length; i++){
// 检查当前导入项的名称是否为"strncat"
if(imports[i].name == "strncat"){
// 如果找到,打印导入项的JSON表示
console.log(JSON.stringify(imports[i]));
// 打印导入项的地址
console.log(imports[i].address);
// 找到后跳出循环
break;
}
}
// 使用Module.enumerateExports来枚举libxiaojianbang.so库的导出信息
var exports = Module.enumerateExports("libxiaojianbang.so");
// 遍历导出信息数组
for(var i = 0; i < exports.length; i++){
// 打印导出项的JSON表示
console.log(JSON.stringify(exports[i]));
}
}
这段代码的作用是:
1. 枚举`libxiaojianbang.so`库的导入信息,并存储在`imports`数组中。
2. 遍历`imports`数组,查找名称是否为"strncat"的导入项。
3. 如果找到,打印该导入项的JSON表示和地址,并跳出循环。
4. 枚举`libxiaojianbang.so`库的导出信息,并存储在`exports`数组中。
5. 遍历`exports`数组,打印每个导出项的JSON表示。
通过这种方式,这段代码可以获取和分析`libxiaojianbang.so`库的导入和导出信息,
Hook导出函数
这段JavaScript代码使用Frida框架来查找并挂钩(hook)`libxiaojianbang.so`库中名
为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数。
// 定义一个函数hookTest2
function hookTest2(){
// 使用Module.findExportByName来查找libxiaojianbang.so库中名为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数
var helloAddr = Module.findExportByName("libxiaojianbang.so", "Java_com_xiaojianbang_app_NativeHelper_add");
// 打印导出函数的地址
console.log(helloAddr);
// 如果找到地址,则附加Interceptor
if(helloAddr != null){
Interceptor.attach(helloAddr,{
onEnter: function(args){
// 函数进入时,打印args中的所有参数
console.log(args[0]);
console.log(args[1]);
console.log(args[2]);
console.log(this.context.x2);
console.log(args[3]);
console.log(args[4].toInt32());
},
onLeave: function(retval){
// 函数退出时,打印返回值
console.log(retval);
// 打印返回值的整数表示
console.log("retval", retval.toInt32());
}
});
}
}
这段代码的作用是:
1. 查找`libxiaojianbang.so`库中名为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数。
2. 如果找到,打印该函数的地址。
3. 如果找到地址,使用Frida的`Interceptor.attach`方法,将钩子(hook)附加到该函数上。
4. 当函数被调用时,记录函数的输入参数,并打印它们的值。
5. 当函数退出时,打印返回值及其整数表示。
修改函数参数返回值
这段JavaScript代码使用Frida框架来查找并挂钩(hook)`libxiaojianbang.so`库中名
为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数,并在函数进入和离开时修改其参数
和返回值。
// 定义一个函数hookTest3
function hookTest3(){
// 使用Module.findExportByName来查找libxiaojianbang.so库中名为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数
var helloAddr = Module.findExportByName("libxiaojianbang.so", "Java_com_xiaojianbang_app_NativeHelper_add");
// 打印导出函数的地址
console.log(helloAddr);
// 如果找到地址,则附加Interceptor
if(helloAddr != null){
Interceptor.attach(helloAddr,{
onEnter: function(args){
// 函数进入时,尝试修改args中的参数
args[2] = ptr(1000); // 使用ptr()函数创建一个指针,指向值为1000的内存地址
// 打印修改后的第二个参数的整数表示
console.log(args[2].toInt32());
// 打印第三个参数
console.log(args[3]);
// 打印第四个参数
console.log(args[4]);
},
onLeave: function(retval){
// 函数退出时,修改返回值
retval.replace(20000); // 将返回值替换为20000
// 打印修改后的返回值的整数表示
console.log("retval", retval.toInt32());
}
});
}
}
1. 查找`libxiaojianbang.so`库中名为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数。
2. 如果找到,打印该函数的地址。
3. 如果找到地址,使用Frida的`Interceptor.attach`方法,将钩子(hook)附加到该函数上。
4. 当函数被调用时,尝试修改函数的第二个参数为指向值为1000的内存地址的指针,并打印修改后的参数和
第三个参数。
5. 当函数退出时,修改返回值为20000,并打印修改后的返回值。
通过这种方式,这段代码可以监控和分析`libxiaojianbang.so`库中名为"Java_com_xiaojianbang_app_NativeHelper_add"的导出函数的执行情况,并且可以修改函数的参数和返回值,这对于调试、分析和修改应用程序的行为非常有用。
Hook未导出函数
使用Frida框架来挂钩(hook)`libxiaojianbang.so`库中计算出的某个函数。
// 定义一个函数hookTest4
function hookTest4(){
// 使用Module.findBaseAddress来找到libxiaojianbang.so库的基地址
var soAddr = Module.findBaseAddress("libxiaojianbang.so");
// 打印基地址
console.log(soAddr);
// 计算要挂钩的函数的地址,这里使用了thumb指令集的偏移量
var funcAddr = soAddr.add(0x23F4);
// 打印函数地址
console.log(funcAddr);
// 如果找到地址,则附加Interceptor
if(funcAddr != null){
Interceptor.attach(funcAddr,{
onEnter: function(args){
// 函数进入时,无操作
},
onLeave: function(retval){
// 函数退出时,打印返回值的十六进制dump
console.log(hexdump(retval));
}
});
}
}
1. 找到`libxiaojianbang.so`库的基地址。
2. 计算要挂钩的函数的地址,这里使用了thumb指令集的偏移量。
3. 打印该函数的地址。
4. 如果找到地址,使用Frida的`Interceptor.attach`方法,将钩子(hook)附加到该函数上。
5. 当函数被调用时,无操作。
6. 当函数退出时,打印返回值的十六进制dump。
获取指针参数返回值
frida框架来挂钩(hook)`libxiaojianbang.so`库中计算出的某个函数,并在函数退出时修改其返回
的内存数据。
// 定义一个函数hookTest5
function hookTest5(){
// 使用Module.findBaseAddress来找到libxiaojianbang.so库的基地址
var soAddr = Module.findBaseAddress("libxiaojianbang.so");
// 打印基地址
console.log(soAddr);
// 计算要挂钩的函数的地址,这里使用了thumb指令集的偏移量
var sub_208C = soAddr.add(0x208C);
// 打印函数地址
console.log(sub_208C);
// 如果找到地址,则附加Interceptor
if(sub_208C != null){
Interceptor.attach(sub_208C,{
onEnter: function(args){
// 函数进入时,保存args中的第二个参数
this.args1 = args[1];
},
onLeave: function(retval){
// 函数退出时,修改内存数据
// 将十六进制字符串转换为字节数组
var newData = hexToBytes("0123456789abcdef0123456789abcdef");
// 写入修改后的数据到内存中
ptr(this.args1).writeByteArray(newData);
// 打印返回地址的十六进制dump
console.log(hexdump(this.args1));
}
});
}
}
这段代码的作用是:
1. 找到`libxiaojianbang.so`库的基地址。
2. 计算要挂钩的函数的地址,这里使用了thumb指令集的偏移量。
3. 打印该函数的地址。
4. 如果找到地址,使用Frida的`Interceptor.attach`方法,将钩子(hook)附加到该函数上。
5. 当函数被调用时,记录函数的输入参数(args中的第二个参数)。
6. 当函数退出时,修改内存中`this.args1`地址的数据,将其替换为预定义的十六进制字节数组。
7. 打印修改后的内存地址的十六进制dump。
通过这种方式,这段代码可以监控和分析`libxiaojianbang.so`库中某个函数的执行情况,
并在函数返回时修改其返回的内存数据,这对于调试、分析和修改应用程序的行为非常有用。
内存读写
使用Frida框架来读取和写入`libxiaojianbang.so`库中特定地址的字符串和字节数据。
// 定义一个函数hookTest7
function hookTest7(){
// 使用Module.findBaseAddress来找到libxiaojianbang.so库的基地址
var soAddr = Module.findBaseAddress("libxiaojianbang.so");
// 打印基地址
console.log(soAddr);
// 如果找到了基地址
if(soAddr != null){
// 读取指定地址的字符串
console.log(soAddr.add(0x2C00).readCString());
// 打印指定地址的十六进制dump
console.log(hexdump(soAddr.add(0x2C00)));
// 读取指定地址的字节数组
var strByte = soAddr.add(0x2C00).readByteArray(16);
console.log(strByte);
// 写入内存
soAddr.add(0x2C00).writeByteArray(stringToBytes("xiaojianbang"));
// 读取指定地址的字符串
console.log(hexdump(soAddr.add(0x2C00)));
// 使用旧API读取指定地址的字节数组
var bytes = Memory.readByteArray(soAddr.add(0x2C00), 16);
console.log(bytes);
}
}
这段代码的作用是:
1. 找到`libxiaojianbang.so`库的基地址。
2. 打印基地址。
3. 如果找到了基地址,执行以下操作:
- 读取基地址+0x2C00处的字符串,并打印。
- 读取基地址+0x2C00处的16字节数据,并打印其十六进制dump。
- 读取基地址+0x2C00处的16字节数据,并打印。
- 写入基地址+0x2C00处一个字符串"xiaojianbang"对应的字节数组。
- 再次读取基地址+0x2C00处的16字节数据,并打印其十六进制dump。
- 使用旧API读取基地址+0x2C00处的16字节数据,并打印。
通过这种方式,这段代码可以读取和写入`libxiaojianbang.so`库中特定地址的字符串和字节数据,
Hook dlopen
// 定义 hookTest6 函数,这是主要的入口点。
function hookTest6() {
// 使用 Frida 的 Module.findExportByName 方法查找当前进程的 'dlopen' 函数的地址。
// 'dlopen' 函数用于加载动态链接库(.so文件)。
var dlopen = Module.findExportByName(null, "dlopen");
// 打印 'dlopen' 函数的地址,用于调试。
console.log(dlopen);
// 判断 'dlopen' 是否存在。
if (dlopen != null) {
// 如果存在,使用 Frida 的 Interceptor.attach 方法挂钩到 'dlopen' 函数。
Interceptor.attach(dlopen, {
// 在 'dlopen' 函数调用前执行的代码。
onEnter: function (args) {
// 读取 'dlopen' 函数的第一个参数,即要加载的库的名称。
var soName = args[0].readCString();
// 打印库名,用于调试。
console.log(soName);
// 检查库名是否包含 "libxiaojianbang.so"。
if (soName.indexOf("libxiaojianbang.so") != -1) {
// 如果包含,则设置 this.hook 为 true,表示后续需要执行额外操作。
this.hook = true;
}
},
// 在 'dlopen' 函数调用后执行的代码。
onLeave: function (retval) {
// 检查之前是否设置了 this.hook。
if (this.hook) {
// 如果是,调用 hookTest5 函数。
hookTest5();
}
}
});
}
// 重复上述过程,但针对 Android 特有的 'android_dlopen_ext' 函数。
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
// 打印 'android_dlopen_ext' 函数的地址,用于调试。
console.log(android_dlopen_ext);
// 判断 'android_dlopen_ext' 是否存在。
if (android_dlopen_ext != null) {
// 如果存在,使用 Frida 的 Interceptor.attach 方法挂钩到 'android_dlopen_ext' 函数。
Interceptor.attach(android_dlopen_ext, {
// 在 'android_dlopen_ext' 函数调用前执行的代码。
onEnter: function (args) {
// 读取 'android_dlopen_ext' 函数的第一个参数,即要加载的库的名称。
var soName = args[0].readCString();
// 打印库名,用于调试。
console.log(soName);
// 检查库名是否包含 "libxiaojianbang.so"。
if (soName.indexOf("libxiaojianbang.so") != -1) {
// 如果包含,则设置 this.hook 为 true,表示后续需要执行额外操作。
this.hook = true;
}
},
// 在 'android_dlopen_ext' 函数调用后执行的代码。
onLeave: function (retval) {
// 检查之前是否设置了 this.hook。
if (this.hook) {
// 如果是,调用 hookTest5 函数。
hookTest5();
}
}
});
}
}
主动调用JNI函数
这个函数用于劫持特定的 C 函数,
并在其执行过程中修改返回值。此函数特别针对 Android 应用中使用的 JNI (Java Native Interface)
函数进行挂钩。这里的代码用 Frida 框架实现,它广泛用于动态分析和逆向工程。
// 定义 hookTest8 函数,用于挂钩并修改 JNI 函数的返回值。
function hookTest8() {
// 使用 Frida 的 Module.findExportByName 方法找到特定动态库中导出的函数地址。
// 这里的 "libxiaojianbang.so" 是库名,
//"Java_com_xiaojianbang_app_NativeHelper_helloFromC" 是函数名。
var funcAddr = Module.findExportByName("libxiaojianbang.so", "Java_com_xiaojianbang_app_NativeHelper_helloFromC");
// 打印找到的函数地址,用于调试。
console.log(funcAddr);
// 检查函数地址是否有效。
if (funcAddr != null) {
// 如果地址有效,使用 Frida 的 Interceptor.attach 方法挂钩到该函数。
Interceptor.attach(funcAddr, {
// 在函数调用前执行的代码块,此处未进行任何操作。
onEnter: function(args) {
},
// 在函数调用后执行的代码块。
onLeave: function(retval) {
// 尝试获取当前的 JNI 环境,这是与 Java 交互的接口。
var env = Java.vm.tryGetEnv();
// 使用 JNI 环境的 newStringUtf 方法创建一个新的 Java 字符串 "bbs.125.la"。
var jstr = env.newStringUtf("bbs.125.la");
// 使用 retval.replace 方法替换原始函数的返回值为新创建的 Java 字符串。
retval.replace(jstr);
// 使用 getStringUtfChars 方法将 Java 字符串转换为 C 字符串。
var cstr = env.getStringUtfChars(jstr, null);
// 打印 C 字符串的内容,用于验证转换是否成功。
console.log(cstr.readCString());
// 使用 hexdump 函数打印 C 字符串的内存布局,便于调试和分析内存内容。
console.log(hexdump(cstr));
}
});
}
}
这段代码主要在于监控和修改由 JNI 调用的 C 函数的行为。在 `onLeave` 钩子中,
通过 JNI 接口主动操作 Java 字符串和 C 字符串的转换,并修改了函数的返回值。
Hook libart 来Hook jni相关函数
函数主要用于监视和记录在 Android ART 运行时中由 `libart.so` 库导出的 `NewStringUTF` 函数的调用。
这里使用了 Frida 框架来实现函数挂钩和监控。
// 定义 hookTest10 函数,用于监控 ART 运行时中的 NewStringUTF 函数调用。
function hookTest10() {
// 使用 Frida 的 Module.enumerateSymbols 方法列举指定库中的所有符号(函数、变量等)。
var artSym = Module.enumerateSymbols("libart.so");
// 定义一个变量用于存储 NewStringUTF 函数的地址。
var NewStringUTFAddr = null;
// 遍历找到的符号列表。
for (var i = 0; i < artSym.length; i++) {
// 检查符号名是否包含 "NewStringUTF",但不包含 "CheckJNI"。
if (artSym[i].name.indexOf("CheckJNI") == -1 && artSym[i].name.indexOf("NewStringUTF") != -1) {
// 打印找到的 NewStringUTF 函数的相关信息,用于调试和确认。
console.log(JSON.stringify(artSym[i]));
// 存储 NewStringUTF 函数的地址。
NewStringUTFAddr = artSym[i].address;
}
}
// 检查 NewStringUTF 函数地址是否有效。
if (NewStringUTFAddr != null) {
// 如果地址有效,使用 Frida 的 Interceptor.attach 方法挂钩到该函数。
Interceptor.attach(NewStringUTFAddr, {
// 在函数调用前执行的代码块。
onEnter: function (args) {
// 读取函数的第一个参数,即要转换的 C 字符串。
console.log(args[1].readCString());
},
// 在函数调用后执行的代码块,此处未进行任何操作。
onLeave: function (retval) {
}
});
}
}
主要用于监控 ART 运行时中的 `NewStringUTF` 函数调用,并在函数调用前打印要转换的 C 字符串。
这对于了解应用程序在 JNI 层和 ART 运行时之间的交互非常有用。
通过 Frida 框架,可以方便地进行动态分析和调试。
so层主动调用任意函数
这个函数主要用于在 Android 应用中通过 Frida 和 JNI 接口直接调用一个指定的本地函数。
这种方法通常用于测试、调试或修改本地库中的特定功能。
// 定义 hookTest11 函数,用于在 Java 层执行环境中调用指定的本地函数。
function hookTest11() {
// 使用 Java.perform 方法确保在正确的线程和执行环境中执行以下代码。
Java.perform(function() {
// 使用 Module.findBaseAddress 加上偏移量来定位 libxiaojianbang.so 中的特定函数地址。
var funcAddr = Module.findBaseAddress("libxiaojianbang.so").add(0x23F4);
// 使用 Frida 的 NativeFunction 类型来声明一个新的函数指针,指定该函数的返回类型和参数类型。
var func = new NativeFunction(funcAddr, "pointer", ['pointer', 'pointer']);
// 尝试获取当前的 JNI 环境,用于与 Java 层交互。
var env = Java.vm.tryGetEnv();
// 打印环境变量的信息,用于调试。
console.log("env: ", JSON.stringify(env));
// 检查是否成功获取到 JNI 环境。
if (env != null) {
// 使用 JNI 环境的 newStringUtf 方法创建一个新的 Java 字符串。
var jstr = env.newStringUtf("xiaojianbang is very good!!!");
// 调用之前声明的本地函数,传递 JNI 环境和 Java 字符串作为参数。
var cstr = func(env, jstr);
// 读取并打印从本地函数返回的 C 字符串。
console.log(cstr.readCString());
// 使用 hexdump 函数打印 C 字符串的内存布局,便于调试和分析内存内容。
console.log(hexdump(cstr));
}
});
}
这段代码的关键在于它演示了如何在 Android 应用中结合使用 Frida 和 JNI 接口来直接调用内存中的
本地函数。通过这种方式,可以实现对本地代码的深入分析和调试。
此外,通过直接与 JNI 环境交互,可以更灵活地处理和修改应用程序的行为。
frida API 写文件
这个函数主要用于在 Android 设备上打开并写入文件,使用的是 JavaScript 的 `File` 对象,
这在用于测试、记录数据或进行文件操作时非常有用。
// 定义 hookTest12 函数,用于在 Android 设备的 sdcard 上创建并写入文件。
function hookTest12() {
// 创建一个新的 File 对象,指定文件路径为 "/sdcard/xiaojianbang.txt",并设置模式为 "w"(写入模式)。
var ios = new File("/sdcard/xiaojianbang.txt", "w");
// 使用 File 对象的 write 方法写入字符串 "xiaojianbang is very good!!!\\n" 到文件中。
ios.write("xiaojianbang is very good!!!\\n");
// 调用 flush 方法确保所有数据都被写入到文件系统。
ios.flush();
// 完成文件操作后,调用 close 方法关闭文件。
ios.close();
}
这段代码通过简单的 API 调用演示了如何在 Android 设备上进行文件写入操作。
这种操作可以用于保存日志、数据输出或其他需要持久化到设备存储的信息。在进行文件操作时,
需要确保应用有相应的权限来访问存储设备。
在 Android 平台上,通常需要在应用的 manifest 文件中声明对 `WRITE_EXTERNAL_STORAGE` 权限的需求,
以允许写入到外部存储。
Hook libc 写文件
这个函数主要用于在 Android 设备上使用 `libc.so` 中的 `fopen`、`fputs` 和 `fclose` 函数来打开、
写入和关闭文件。通过 Frida 框架,
// 定义 hookTest13 函数,用于在 Android 设备上使用 libc.so 中的文件操作函数进行文件写入。
function hookTest13() {
// 使用 Module.findExportByName 方法查找 libc.so 中的 fopen、fputs 和 fclose 函数的地址。
var addr_fopen = Module.findExportByName("libc.so", "fopen");
var addr_fputs = Module.findExportByName("libc.so", "fputs");
var addr_fclose = Module.findExportByName("libc.so", "fclose");
// 打印找到的函数地址,用于调试。
console.log("addr_fopen:", addr_fopen, "addr_fputs:", addr_fputs, "addr_fclose:", addr_fclose);
// 使用 NativeFunction 类型声明这些函数的函数指针,指定返回类型和参数类型。
var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);
// 使用 Memory.allocUtf8String 方法分配存储文件路径和打开模式的内存空间。
var filename = Memory.allocUtf8String("/sdcard/xiaojianbang.txt");
var open_mode = Memory.allocUtf8String("w");
// 调用 fopen 函数打开指定路径的文件,并传递文件路径和打开模式作为参数。
var file = fopen(filename, open_mode);
// 打印 fopen 返回的文件指针,用于调试。
console.log("fopen:", file);
// 使用 Memory.allocUtf8String 方法分配存储要写入文件的内容的内存空间。
var buffer = Memory.allocUtf8String("bbs.125.la\\n");
// 调用 fputs 函数将内容写入文件,并传递内容和文件指针作为参数。
var retval = fputs(buffer, file);
// 打印 fputs 函数的返回值,用于确认写入是否成功。
console.log("fputs:", retval);
// 调用 fclose 函数关闭文件,并传递文件指针作为参数。
fclose(file);
}
这段代码展示了如何使用 Frida 在运行时劫持 `libc.so` 中的文件操作函数,以实现文件写入操作。
通过调用 `fopen` 打开文件、`fputs` 写入内容和 `fclose` 关闭文件,
可以在 Android 设备上进行文件操作。确保在使用时拥有合适的权限以及对文件系统的访问权限。
inlineHook与寄存器Hook
该函数使用 Frida 框架在 Android 设备上拦截共享库中特定函数的调用。
此函数专注于在函数调用期间操作寄存器,以动态修改行为或结果。
// 定义 hookTest14 函数,用于劫持指定动态库中的函数。
function hookTest14() {
// 使用 Module.findBaseAddress 查找 "libxiaojianbang.so" 动态库的基地址。
var soAddr = Module.findBaseAddress("libxiaojianbang.so");
// 打印基地址,用于调试。
console.log(soAddr);
// 计算函数 "sub_2894" 的地址,根据平台差异可能需要调整地址计算方式(比如 THUMB 模式需要加1)。
var sub_2894 = soAddr.add(0x2894);
// 打印计算得到的函数地址,用于调试。
console.log(sub_2894);
// 检查 "sub_2894" 地址是否有效。
if(sub_2894 != null) {
// 使用 Interceptor.attach 方法挂钩到 "sub_2894" 函数。
Interceptor.attach(sub_2894, {
// 当函数进入时执行的代码块。
onEnter: function() {
// 打印进入函数时第一个参数 x0 的值。
console.log(this.context.x0.toInt32());
// 修改 x0 寄存器的值。
this.context.x0 = 0x1000;
// 打印修改后 x0 寄存器的值。
console.log(this.context.x0.toInt32());
},
// 当函数离开时执行的代码块。
onLeave: function() {
}
});
}
// 重复上述步骤,但针对另一个函数 "sub_2858"。
var sub_2858 = soAddr.add(0x2858);
// 打印 "sub_2858" 的地址。
console.log(sub_2858);
// 检查 "sub_2858" 地址是否有效。
if(sub_2858 != null) {
// 挂钩到 "sub_2858" 函数。
Interceptor.attach(sub_2858, {
// 当函数进入时执行的代码块。
onEnter: function() {
// 打印进入函数时第二个参数 x1 的值。
console.log(this.context.x1);
// 修改 x1 寄存器的值为指定地址。
this.context.x1 = soAddr.add(0x2C35);
// 打印修改后 x1 寄存器的值。
console.log(this.context.x1);
},
// 当函数离开时执行的代码块。
onLeave: function() {
}
});
}
}
这个函数展示了如何在特定函数内拦截并操作寄存器,提供了对内部计算的实时洞察和控制。
它特别适用于本地应用程序的调试、测试和逆向工程任务。
主要包括地址计算、拦截执行和动态修改寄存器值。