frida hook so层方法大全

文章转载,仅供学习,如有需要请支持原文章创作:https://kevinspider.github.io/fridahookso/

1.感谢

2. frida env

https://github.com/frida/frida-java-bridge/blob/master/lib/env.js

3.IDA 判断 Thumb 指令集和 Arm 指令集

  • IDA - Options - General - number of opcode bytes - 设置为 4
  • 此时查看 IDA VIew 中 opcode 的长度, 如果出现 2 个字节和 4 个字节的, 说明为 thumb 指令集
  • 如果都是 4 个字节的, 说明是 arm 指令集;
  • 在 Thumb 指令集下, inline hook 的偏移地址需要进行 +1 操作;

4.枚举内存中的 so 文件

用于查看目标 module 是否被正常加载, 使用 Process.enumerateModules() 将当前加载的所有 so 文件打印出来

function hook_native(){
    var modules = Process.enumerateModules();
    for (var i in modules){
        var module = modules[i];
        console.log(module.name);
        if (module.name.indexOf("target.so") > -1 ){
            console.log(module.base);
        }
    }
}

5. 获取指定 so 文件的基地址

function hook_module() {
    var baseAddr = Module.findBaseAddress("libnative-lib.so");
    console.log("baseAddr", baseAddr);
}

6.获取指定 so 文件的函数

6.1通过导出函数名定位 native 方法

function hook_func_from_exports(){
    var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c");
    console.log("add_c_addr is :",add_c_addr);
}

6.2通过 symbols 符号定位 native 方法

function find_func_from_symbols() {
    var NewStringUTF_addr = null;
    var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
    for (var i in symbols) {
        var symbol = symbols[i];
        if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0
        ){
            if (symbol.name.indexOf("NewStringUTF") >= 0) {
                console.log("find target symbols", symbol.name, "address is ", symbol.address);
                NewStringUTF_addr = symbol.address;
            }
        }
    }

    console.log("NewStringUTF_addr is ", NewStringUTF_addr);

    Interceptor.attach(NewStringUTF_addr, {
        onEnter: function (args) {
            console.log("args0",args[0])
            console.log("args0", args[0], hexdump(args[0]));
            console.log("args1", args[1], hexdump(args[1]));
            var env = Java.vm.tryGetEnv();
            if (env != null) {
                // 直接读取 c 里面的 char
                console.log("Memory readCstring is :", Memory.readCString(args[1]));
            }else{
                console.log("get env error");
            }
        },
        onLeave: function (returnResult) {
            console.log("result: ", Java.cast(returnResult, Java.use("java.lang.String")));
            var env = Java.vm.tryGetEnv();
            if (env != null) {
                var jstring = env.newStringUtf("修改返回值");
                returnResult.replace(ptr(jstring));
            }
        }
    })
}

6.3通过地址偏移 inline-hook 任意函数

function main(){
    // get base address of target so;
    var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
    console.log("base module addr ->", libnative_lib_addr);
    if (libnative_lib_addr){
        var add_addr1 = Module.findExportByName("libnative-lib.so", "_Z5r0addii");
        var add_addr2 = libnative_lib_addr.add(0x94B2 + 1); // 32位需要加1
        console.log(add_addr1);
        console.log(add_addr2);
    }

    // 主动调用
    var add1 = new NativeFunction(add_addr1, "int", ["int", "int"]);
    var add2 = new NativeFunction(add_addr2, "int", ["int", "int"]);

    console.log("add1 result is ->" + add1(10, 20));
    console.log("add2 result is ->" + add2(10, 20));

}

setImmediate(main);

/*
base module addr -> 0xd430b000
0xd43144b3
0xd43144b3
add1 result is ->30
add2 result is ->30
*/

7.通过 Intercept 拦截器打印 native 方法参数和返回值, 并修改返回值

  • onEnter: 函数(args) : 回调函数, 给定一个参数 args, 用于读取或者写入参数作为 NativePointer 对象的指针;
  • onLeave: 函数(retval) : 回调函数给定一个参数 retval, 该参数是包含原始返回值的 NativePointer 派生对象; 可以调用 retval.replace(1234) 以整数 1234 替换返回值, 或者调用retval.replace(ptr("0x1234")) 以替换为指针;
  • 注意: retval 对象会在 onLeave 调用中回收, 因此不要将其存储在回调之外使用, 如果需要存储包含的值, 需要制作深拷贝, 如 ptr(retval.toString())
function find_func_from_exports() {
    var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c");
    console.log("add_c_addr is :",add_c_addr);
    // 添加拦截器
    Interceptor.attach(add_c_addr,{
        // 打印入参
        onEnter: function (args) {
            console.log("add_c called");
            console.log("arg1:",args[0].toInt32());
            console.log("arg2", args[1].toInt32());
        },
        // 打印返回值
        onLeave: function (returnValue) {
            console.log("add_c result is :", returnValue.toInt32());
            // 修改返回值
            returnValue.replace(100);
        }
    })
}

8.通过 Intercept 拦截器替换原方法

function frida_Interceptor() {
    Java.perform(function () {
       //这个c_getSum方法有两个int参数、返回结果为两个参数相加
       //这里用NativeFunction函数自己定义了一个c_getSum函数
       var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'), 
       'int',['int','int']);
       //输出结果 那结果肯定就是 3
       console.log("result:",add_method(1,2));
       //这里对原函数的功能进行替换实现
       Interceptor.replace(add_method, new NativeCallback(function (a, b) {
           //h不论是什么参数都返回123
            return 123;
       }, 'int', ['int', 'int']));
       //再次调用 则返回123
       console.log("result:",add_method(1,2));
    });
}

9.so 层方法注册到 js 中, 主动调用

new NativeFunction(address, returnType, argTypes[, options])
  • address : 函数地址
  • returnType : 指定返回类型
  • argTypes : 数组指定参数类型
  • 类型可选: void, pointer, int, uint, long, ulong, char, uchar, float, double, int8, uint8, int16, int32, uint32, int64, uint64; 参照函数所需的 type 来定义即可;
function invoke_native_func() {
    var baseAddr = Module.findBaseAddress("libnative-lib.so");
    console.log("baseAddr", baseAddr);
    var offset = 0x0000A28C + 1;
    var add_c_addr = baseAddr.add(offset);
    var add_c_func = new NativeFunction(add_c_addr, "int", ["int","int"]);
    var result = add_c_func(1, 2);
    console.log(result);
}
Java.perform(function () {
    // 获取 so 文件基地址
    var base = Module.findBaseAddress("libnative-lib.so");
    // 获取目标函数偏移
    var sub_834_addr = base.add(0x835) // thumb 需要 +1
    // 使用 new NativeFunction 将函数注册到 js
    var sub_834 = new NativeFunction(sub_834_addr, 'pointer', ['pointer']);
    // 开辟内存, 创建入参
    var arg0 = Memory.alloc(10);
    ptr(arg0).writeUtf8String("123");
    var result = sub_834(arg0);
    console.log("result is :", hexdump(result));
})

10.hook libart 中的 jni 方法

jni 全部定在在 /system/lib(64)/libart.so 文件中, 通过枚举 symbols 筛选出指定的方法

function hook_libart() {
    var GetStringUTFChars_addr = null;

    // jni 系统函数都在 libart.so 中
    var module_libart = Process.findModuleByName("libart.so");
    var symbols = module_libart.enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var name = symbols[i].name;
        if ((name.indexOf("JNI") >= 0) 
						&& (name.indexOf("CheckJNI") == -1) 
						&& (name.indexOf("art") >= 0)) {
            if (name.indexOf("GetStringUTFChars") >= 0) {
                console.log(name);
                // 获取到指定 jni 方法地址
                GetStringUTFChars_addr = symbols[i].address;
            }
        }
    }

    Java.perform(function(){
        Interceptor.attach(GetStringUTFChars_addr, {
            onEnter: function(args){
                // console.log("args[0] is : ", args[0]);
                // console.log("args[1] is : ", args[1]);
                console.log("native args[1] is :",Java.vm.getEnv().getStringUtfChars(args[1],null).readCString());
                console.log('GetStringUTFChars onEnter called from:\n' +
                    Thread.backtrace(this.context, Backtracer.FUZZY)
                    .map(DebugSymbol.fromAddress).join('\n') + '\n');
                // console.log("native args[1] is :", Java.cast(args[1], Java.use("java.lang.String")));
                // console.log("native args[1] is :", Memory.readCString(Java.vm.getEnv().getStringUtfChars(args[1],null)));
            }, onLeave: function(retval){
                // retval const char*
                console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());
            }
        })
    })
}

11.hook libc 中的系统方法

/system/lib(64)/libc.so 导出的符号没有进行 namemanline , 直接过滤筛选即可

// hook libc.so
var pthread_create_addr = null;

// console.log(JSON.stringify(Process.enumerateModules())); 
// Process.enumerateModules() 枚举加载的so文件
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++){
    if (symbols[i].name === "pthread_create"){
        // console.log("symbols name is -> " + symbols[i].name);
        // console.log("symbols address is -> " + symbols[i].address);
        pthread_create_addr = symbols[i].address;
    }
}

Interceptor.attach(pthread_create_addr,{
    onEnter: function(args){
        console.log("args is ->" + args[0], args[1], args[2],args[3]);
    },
    onLeave: function(retval){
        console.log(retval);
    }
});

}

libc.so 中方法替换

// hook 检测frida 的方法
function main() {
    // var exports = Process.findModuleByName("libnative-lib.so").enumerateExports(); 导出
    // var imports = Process.findModuleByName("libnative-lib.so").enumerateImports(); 导入
    // var symbols = Process.findModuleByName("libnative-lib.so").enumerateSymbols(); 符号

    var pthread_create_addr = null;
    var symbols = Process.getModuleByName("libc.so").enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        if (symbol.name === "pthread_create") {
            pthread_create_addr = symbol.address;
            console.log("pthread_create name is ->", symbol.name);
            console.log("pthread_create address is ->", pthread_create_addr);
        }
    }

    Java.perform(function(){
        // 定义方法 之后主动调用的时候使用
        var pthread_create = new NativeFunction(pthread_create_addr, 'int', ['pointer', 'pointer','pointer','pointer'])
        Interceptor.replace(pthread_create_addr,new NativeCallback(function (a0, a1, a2, a3) {
            var result = null;
            var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv");
            console.log("a0,a1,a2,a3 ->",a0,a1,a2,a3);
            if (String(a2) === String(detect_frida_loop)) {
                result = 0;
                console.log("阻止frida反调试启动");
            } else {
                result = pthread_create(a0,a1,a2,a3);
                console.log("正常启动");
            }
            return result;
        }, 'int', ['pointer', 'pointer','pointer','pointer']));
    })
}

12.hook native 调用栈

Interceptor.attach(f, {
  onEnter: function (args) {
    console.log('RegisterNatives called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  }
});

13.jnitrace

13.1安装

jnitrace: https://github.com/chame1eon/jnitrace

python` : `pip install jnitrace

13.2基础用法

ndk 开发是没有办法脱离 [libc.so](http://libc.so)[libart.so](http://libart.so) 进行开发, 所以只要降维打击, 通过 trace 的方式就可以监控到 so
frida hook so层方法大全_第1张图片

启动命令

jnitrace [options] -l libname packagename

例如: jnitrace -l [libnative-lib.so](http://libnative-lib.so) com.example.myapplication

必要参数

  • -l libname : 指定要trace的.so文件, 可以同时trace多个.so文件, 直接使用 *来trace所有的.so文件; 如: -l libnative-lib.so -l libanother-lib.so or -l *
  • packagename : 指定要trace的package name

可选参数

  • -m: 指定是spawn还是attach
  • -b: 指定是fuzzy还是accurate
  • -i : 指定一个正则表达式来过滤出方法名, 例如: -i Get -i RegisterNatives 就只会打印出名字里包含Get或者RegisterNativesJNI methods
  • -e 和i相反,同样通过正则表达式来过滤,但这次会将指定的内容忽略掉
  • -I trace导出的方法,jnitrace认为导出的函数应该是从Java端能够直接调用的函数,所以可以包括使用RegisterNatives来注册的函数,例如I stringFromJNI -I nativeMethod([B)V,就包括导出名里有stringFromJNI,以及使用RegisterNames来注册,并带有nativeMethod([B)V签名的函数。
  • -o path/output.json,导出输出到文件里。
  • -p path/to/script.js,用于在加载jnitrace脚本之前将指定路径的Frida脚本加载到目标进程中,这可以用于在jnitrace启动之前对抗反调试。
  • -a path/to/script.js,用于在加载jnitrace脚本之后将指定路径的Frida脚本加载到目标进程中
  • -ignore-env,不打印所有的JNIEnv函数
  • -ignore-vm,不打印所有的JavaVM函数

启动方式

默认使用 spawn 启动, 可以通过 -m attach 设置通过 attach 启动

jnitrace -m attach -l[libnative-lib.so](http://libnative-lib.so) com.kevin.demoso1

设置回溯器

默认情况下使用 accurate的精确模式来进行回溯, 可以通过 -b fuzzy 修改为模糊模式

jnitrace -l [libnative-lib.so](http://libnative-lib.so) -b fuzzy com.kevin.demoso1

监控指定规则的方法

用于指定应该跟踪的方法名, 该选项可以多次提供;

jnitrace -l libnative-lib.so -i RegisterNatives com.kevin.demoso1

只过滤出RegisterNatives相关的内容

忽略指定规则的方法

用于指定在跟踪中应被忽略的方法名, 这个选项可以被多次提供;

忽略以Find开头的所有方法;

jnitrace -l libnative-lib.so -e ^Find com.kevin.demoso

13.3 jnitace 计算偏移地址

frida hook so层方法大全_第2张图片
0x8e4f3b1 是方法 initSN 方法的绝对地址

0xd8e4e000 是 libmyjni.so 基地址

使用使用 initSN()V的绝对地址 0xd8e4f3b1 减去 libmyjni.so 的基地址 0xd8e4e000 , 得到偏移 0x13B1
frida hook so层方法大全_第3张图片
g 进行跳转到 0x13B1 即可进入方法

14.frida trace

文档地址: https://frida.re/docs/frida-trace/

options

Usage: frida-trace [options] target

 Options:
   --version             show program's version number and exit
   -h, --help            show this help message and exit
   -D ID, --device=ID    connect to device with the given ID
   -U, --usb             connect to USB device
   -R, --remote          connect to remote frida-server
   -H HOST, --host=HOST  connect to remote frida-server on HOST
   -f FILE, --file=FILE  spawn FILE
   -F, --attach-frontmost     attach to frontmost application
   -n NAME, --attach-name=NAME     attach to NAME
   -p PID, --attach-pid=PID     attach to PID
   --stdio=inherit|pipe      stdio behavior when spawning (defaults to “inherit”)
   --runtime=duk|v8          script runtime to use (defaults to “duk”)
   --debug                   enable the Node.js compatible script debugger
   -I MODULE, --include-module=MODULE       include MODULE
   -X MODULE, --exclude-module=MODULE       exclude MODULE
   -i FUNCTION, --include=FUNCTION    include FUNCTION
   -x FUNCTION, --exclude=FUNCTION   exclude FUNCTION
   -a MODULE!OFFSET, --add=MODULE!OFFSET    add MODULE!OFFSET
   -T, --include-imports    include program's imports
   -t MODULE, --include-module-imports=MODULE      include MODULE imports
   -m OBJC_METHOD, --include-objc-method=OBJC_METHOD    include OBJC_METHOD
   -M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD    exclude OBJC_METHOD
   -s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL    include DEBUG_SYMBOL
   -q, --quiet           do not format output messages
   -d, --decorate        Add module name to generated onEnter log statement
   -o OUTPUT, --output=OUTPUT    dump messages to file

基础使用

frida-trace [options] packagename

启动模式

默认使用 attach 模式, 可以指定 -f packageName 使用 spawn 模式启动

frida-trace -U -i strcmp -f com.gdufs.xman

文件输出

frida-trace -U -i "strcmp" -f com.gdufs.xman -o xman.json

-o filepath 指定输出的文件路径, 方便内容过多时进行查看

trace 任意 function

frida-trace -U -i "strcmp" com.example.demoso1

trace 任意 module

frida-trace -U -I "libnative-lib.so" com.example.demoso1

根据地址进行 trace

frida-trace -U -a "libnative-lib.so!0x9281" com.example.demoso1

批量 trace

源码地址: https://github.com/Pr0214/trace_natives

ps: 需要切换到 frida14 版本

  • 1.将traceNatives.py丢进IDA plugins目录中

在ida 的python console中运行如下命令即可找到plugins目录:os.path.join(idaapi.get_user_idadir(), "plugins")

  • 2.IDA中,Edit-Plugins-traceNatives –> IDA输出窗口就会显示如下字眼:使用方法如下: frida-trace -UF -O C:\Users\Lenovo\Desktop\2021\mt\libmtguard.txt

15.frida-hook-libart

下载地址: https://github.com/lasting-yang/frida_hook_libart

hook art

frida -U --no-pause -f package_name -l hook_art.js

hook_RegisterNatives

frida -U --no-pause -f package_name -l hook_RegisterNatives.js

hook_artmethod

init libext first time

adb push lib/libext64.so /data/local/tmp/libext64.so
adb push lib/libext.so /data/local/tmp/libext.so
adb shell su -c "cp /data/local/tmp/libext64.so /data/app/libext64.so"
adb shell su -c "cp /data/local/tmp/libext.so /data/app/libext.so"
adb shell su -c "chown 1000.1000 /data/app/libext*.so"
adb shell su -c "chmod 777 /data/app/libext*.so"
adb shell su -c "ls -al /data/app/libext*"

use hook_artmethod.js

frida -U --no-pause -f package_name -l hook_artmethod.js
# or
frida -U --no-pause -f package_name -l hook_artmethod.js > hook_artmethod.log

frida-fart-hook

  • 首先拷贝fart.sofart64.so/data/app目录下,并使用chmod 777 设置好权限,然后就可以使用了。

  • 如果目标 app 没有 sdcard 权限则需要手动添加; 或者可以修改 frida_fart_hook.js 中的源码, 将 savepath 改为 /data/data/应用包名/;

  • frida版fart是使用hook的方式实现的函数粒度的脱壳,仅仅是对类中的所有函数进行了加载,但依然可以解决绝大多数的抽取保护

  • 需要以spawn方式启动app,等待app进入Activity界面后,执行fart()函数即可。如app包名为com.example.test,则frida -U -f com.example.test -l frida_fart_hook.js --no-pause ,然后等待app进入主界面,执行fart()

  • 高级用法:如果发现某个类中的函数的CodeItem没有dump下来,可以调用dump(classname),传入要处理的类名,完成对该类下的所有函数体的dump,dump下来的函数体会追加到bin文件当中。

16.frida 文件写入(frida/hook libc)

frida api 写入文件

function writeFile(){
	var file = new File("/sdcard/reg.dat", "w");
	file.write("content from frida");
	file.flush();
	file.close();
}

frida 定义 NativeFunction 写入文件

function writeFileNative(){
	var addr_fopen = Module.findExportByName("libc.so", "fopen");
	var addr_fputs = Module.findExportByName("libc.so", "fputs");
	var addr_fclose = Module.findExportByName("libc.so", "fclose");
	
	// 将 libc 的系统方法注册到 js 层
	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"]);

	// 在 js 层主动调用 libc 的方法
	// 不能直接将 js 的字符串传给 libc中的方法, 需要进行转换
	var filename = Memory.allocUtf8String("/sdcard/reg.dat");
	var open_mode = Memory.allocUtf8String("w");
	var file = fopen(filename, open_mode);
	
	var buffer = Memory.allocUtf8String("content from frida");
	var result = fputs(buffer, file);
	console.log("fputs ret: ", result);

	// 关闭文件
	fclose(file);
	
}

17.hook 读写 std::string

function readStdString(str){
  var isTiny = (str.readU8 & 1) === 0;
  if (isTiny){
    return str.add(1).readUtf8String();
  }
  return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}

function writeStdString(str, content){
  var isTiny = (str.readU8() & 1) === 0;
  if (isTiny){
    str.add(1).writeUtf8String(content);
  }else{
    str.add(2 * Process.pointerSize).readPointer().writeUtf8String(content);
  }
}

18.hook so 文件加载后马上 hook

//第一种方式(针对较老的系统版本)
var dlopen = Module.findExportByName(null, "dlopen");
console.log(dlopen);
if(dlopen != null){
    Interceptor.attach(dlopen,{
        onEnter: function(args){
            var soName = args[0].readCString();
            console.log(soName);
            if(soName.indexOf("libc.so") != -1){
                this.hook = true;
            }
        },
        onLeave: function(retval){
            if(this.hook) { 
                dlopentodo();
            };
        }
    });
}

//第二种方式(针对新系统版本) android 8.1 使用该方法
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
console.log(android_dlopen_ext);
if(android_dlopen_ext != null){
    Interceptor.attach(android_dlopen_ext,{
        onEnter: function(args){
            var soName = args[0].readCString();
            console.log(soName);
            if(soName.indexOf("libc.so") != -1){
                this.hook = true;
            }
        },
        onLeave: function(retval){
            if(this.hook) {
                dlopentodo();
            };
        }
    });
}

function dlopentodo(){
    //todo ...
}

19.hook libc kill

function replaceKILL(){
	var kill_addr = Module.findExportByName("libc.so","kill");
	Interceptor.replace(kill_addr, new NativeCallback(function(arg0, arg1){
		console.log("arg0=> ", arg0);
		console.log("arg1=> ", arg1);
		console.log('libc.so!kill called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
	},"int",["int","int"]))
}

20.hook init_array

//应用以32位在64位终端环境下运行
//adb install --abi armeabi-v7a <path to apk>

function get_call_function() {
    var call_function_addr = null;
    var symbols = Process.getModuleByName("linker").enumerateSymbols();
    for (var m = 0; m < symbols.length; m++) {
        if (symbols[m].name == "__dl__ZL13call_functionPKcPFviPPcS2_ES0_") {
            call_function_addr = symbols[m].address;
            console.log("found call_function_addr => ", call_function_addr)
            hook_call_function(call_function_addr)
        }
    }
}

function hook_call_function(_call_function_addr){
    console.log("hook call function begin!hooking address :=>",_call_function_addr)
    Interceptor.attach(_call_function_addr,{
        onEnter:function(args){
            if(args[2].readCString().indexOf("base.odex")<0){
                console.log("============================")
                console.log("function_name =>",args[0].readCString())
                var soPath = args[2].readCString()
                console.log("so path : =>",soPath)
                var soName = soPath.split("/").pop();
                console.log("function offset =>","0x"+(args[1]-Module.findBaseAddress(soName)).toString(16))
                console.log("============================")
            }
        },onLeave:function(retval){
        }
    })
}

setImmediate(get_call_function)
function hook_constructor() {
    if (Process.pointerSize == 4) {
        var linker = Process.findModuleByName("linker");
    } else {
        var linker = Process.findModuleByName("linker64");
    }

    var addr_call_function =null;
    var addr_g_ld_debug_verbosity = null;
    var addr_async_safe_format_log = null;
    if (linker) {
        var symbols = linker.enumerateSymbols();
        for (var i = 0; i < symbols.length; i++) {
            var name = symbols[i].name;
            if (name.indexOf("call_function") >= 0){
                addr_call_function = symbols[i].address;
            }
            else if(name.indexOf("g_ld_debug_verbosity") >=0){
                addr_g_ld_debug_verbosity = symbols[i].address;
              
                ptr(addr_g_ld_debug_verbosity).writeInt(2);

            } else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){
            
                addr_async_safe_format_log = symbols[i].address;

            } 

        }
    }
    if(addr_async_safe_format_log){
        Interceptor.attach(addr_async_safe_format_log,{
            onEnter: function(args){
                this.log_level  = args[0];
                this.tag = ptr(args[1]).readCString()
                this.fmt = ptr(args[2]).readCString()
                if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){
                    this.function_type = ptr(args[3]).readCString(), // func_type
                    this.so_path = ptr(args[5]).readCString();
                    var strs = new Array(); //定义一数组 
                    strs = this.so_path.split("/"); //字符分割
                    this.so_name = strs.pop();
                    this.func_offset  = ptr(args[4]).sub(Module.findBaseAddress(this.so_name)) 
                     console.log("func_type:", this.function_type,
                        '\nso_name:',this.so_name,
                        '\nso_path:',this.so_path,
                        '\nfunc_offset:',this.func_offset 
                     );
                }
            },
            onLeave: function(retval){

            }
        })
    }
}
function main() {
    hook_constructor();
}
setImmediate(main);

21.frida dump

document: https://github.com/lasting-yang/frida_dump

21.1 frida dump so

function dump_so(so_name) {
    Java.perform(function () {
        var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
        var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
        var libso = Process.getModuleByName(so_name);
        console.log("[name]:", libso.name);
        console.log("[base]:", libso.base);
        console.log("[size]:", ptr(libso.size));
        console.log("[path]:", libso.path);
        var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
        var file_handle = new File(file_path, "wb");
        if (file_handle && file_handle != null) {
            Memory.protect(ptr(libso.base), libso.size, 'rwx');
            var libso_buffer = ptr(libso.base).readByteArray(libso.size);
            file_handle.write(libso_buffer);
            file_handle.flush();
            file_handle.close();
            console.log("[dump]:", file_path);
        }
    });
}

21.2 frida dump dex

function get_self_process_name() {
    var openPtr = Module.getExportByName('libc.so', 'open');
    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

    var readPtr = Module.getExportByName("libc.so", "read");
    var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);

    var closePtr = Module.getExportByName('libc.so', 'close');
    var close = new NativeFunction(closePtr, 'int', ['int']);

    var path = Memory.allocUtf8String("/proc/self/cmdline");
    var fd = open(path, 0);
    if (fd != -1) {
        var buffer = Memory.alloc(0x1000);

        var result = read(fd, buffer, 0x1000);
        close(fd);
        result = ptr(buffer).readCString();
        return result;
    }

    return "-1";
}


function mkdir(path) {
    var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');
    var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);



    var opendirPtr = Module.getExportByName('libc.so', 'opendir');
    var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);

    var closedirPtr = Module.getExportByName('libc.so', 'closedir');
    var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);

    var cPath = Memory.allocUtf8String(path);
    var dir = opendir(cPath);
    if (dir != 0) {
        closedir(dir);
        return 0;
    }
    mkdir(cPath, 755);
    chmod(path);
}

function chmod(path) {
    var chmodPtr = Module.getExportByName('libc.so', 'chmod');
    var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);
    var cPath = Memory.allocUtf8String(path);
    chmod(cPath, 755);
}

function dump_dex() {
    var libart = Process.findModuleByName("libart.so");
    var addr_DefineClass = null;
    var symbols = libart.enumerateSymbols();
    for (var index = 0; index < symbols.length; index++) {
        var symbol = symbols[index];
        var symbol_name = symbol.name;
        //这个DefineClass的函数签名是Android9的
        //_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
        if (symbol_name.indexOf("ClassLinker") >= 0 &&
            symbol_name.indexOf("DefineClass") >= 0 &&
            symbol_name.indexOf("Thread") >= 0 &&
            symbol_name.indexOf("DexFile") >= 0) {
            console.log(symbol_name, symbol.address);
            addr_DefineClass = symbol.address;
        }
    }
    var dex_maps = {};
    var dex_count = 1;

    console.log("[DefineClass:]", addr_DefineClass);
    if (addr_DefineClass) {
        Interceptor.attach(addr_DefineClass, {
            onEnter: function(args) {
                var dex_file = args[5];
                //ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
                //ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
                var base = ptr(dex_file).add(Process.pointerSize).readPointer();
                var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();

                if (dex_maps[base] == undefined) {
                    dex_maps[base] = size;
                    var magic = ptr(base).readCString();
                    if (magic.indexOf("dex") == 0) {

                        var process_name = get_self_process_name();
                        if (process_name != "-1") {
                            var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name;
                            mkdir(dex_dir_path);
                            var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex";
                            console.log("[find dex]:", dex_path);
                            var fd = new File(dex_path, "wb");
                            if (fd && fd != null) {
                                dex_count++;
                                var dex_buffer = ptr(base).readByteArray(size);
                                fd.write(dex_buffer);
                                fd.flush();
                                fd.close();
                                console.log("[dump dex]:", dex_path);

                            }
                        }
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
}

var is_hook_libart = false;

function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);
                if (path.indexOf("libart.so") >= 0) {
                    this.can_hook_libart = true;
                    console.log("[dlopen:]", path);
                }
            }
        },
        onLeave: function(retval) {
            if (this.can_hook_libart && !is_hook_libart) {
                dump_dex();
                is_hook_libart = true;
            }
        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);
                if (path.indexOf("libart.so") >= 0) {
                    this.can_hook_libart = true;
                    console.log("[android_dlopen_ext:]", path);
                }
            }
        },
        onLeave: function(retval) {
            if (this.can_hook_libart && !is_hook_libart) {
                dump_dex();
                is_hook_libart = true;
            }
        }
    });
}


setImmediate(dump_dex);

21.3 frida dump dex class

function get_self_process_name() {
    var openPtr = Module.getExportByName('libc.so', 'open');
    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

    var readPtr = Module.getExportByName("libc.so", "read");
    var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);

    var closePtr = Module.getExportByName('libc.so', 'close');
    var close = new NativeFunction(closePtr, 'int', ['int']);

    var path = Memory.allocUtf8String("/proc/self/cmdline");
    var fd = open(path, 0);
    if (fd != -1) {
        var buffer = Memory.alloc(0x1000);

        var result = read(fd, buffer, 0x1000);
        close(fd);
        result = ptr(buffer).readCString();
        return result;
    }

    return "-1";
}

function load_all_class() {
    if (Java.available) {
        Java.perform(function () {

            var DexFileclass = Java.use("dalvik.system.DexFile");
            var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
            var DexPathListclass = Java.use("dalvik.system.DexPathList");

            Java.enumerateClassLoaders({
                onMatch: function (loader) {
                    try {
                        var basedexclassloaderobj = Java.cast(loader, BaseDexClassLoaderclass);
                        var pathList = basedexclassloaderobj.pathList.value;
                        var pathListobj = Java.cast(pathList, DexPathListclass)
                        var dexElements = pathListobj.dexElements.value;
                        for (var index in dexElements) {
                            var element = dexElements[index];
                            try {
                                var dexfile = element.dexFile.value;
                                var dexfileobj = Java.cast(dexfile, DexFileclass);
                                console.log("dexFile:", dexfileobj);
                                const classNames = [];
                                const enumeratorClassNames = dexfileobj.entries();
                                while (enumeratorClassNames.hasMoreElements()) {
                                    var className = enumeratorClassNames.nextElement().toString();
                                    classNames.push(className);
                                    try {
                                        loader.loadClass(className);
                                    } catch (error) {
                                        console.log("loadClass error:", error);
                                    }
                                }
                            } catch (error) {
                                console.log("dexfile error:", error);
                            }
                        }
                    } catch (error) {
                        console.log("loader error:", error);
                    }
                },
                onComplete: function () {

                }
            })
            console.log("load_all_class end.");
        });
    }
}
var dex_maps = {};

function print_dex_maps() {
    for (var dex in dex_maps) {
        console.log(dex, dex_maps[dex]);
    }
}

function dump_dex() {
    load_all_class();

    for (var base in dex_maps) {
        var size = dex_maps[base];
        console.log(base);

        var magic = ptr(base).readCString();
        if (magic.indexOf("dex") == 0) {
            var process_name = get_self_process_name();
            if (process_name != "-1") {
                var dex_path = "/data/data/" + process_name + "/files/" + base.toString(16) + "_" + size.toString(16) + ".dex";
                console.log("[find dex]:", dex_path);
                var fd = new File(dex_path, "wb");
                if (fd && fd != null) {
                    var dex_buffer = ptr(base).readByteArray(size);
                    fd.write(dex_buffer);
                    fd.flush();
                    fd.close();
                    console.log("[dump dex]:", dex_path);

                }
            }
        }
    }
}

function hook_dex() {
    var libart = Process.findModuleByName("libart.so");
    var addr_DefineClass = null;
    var symbols = libart.enumerateSymbols();
    for (var index = 0; index < symbols.length; index++) {
        var symbol = symbols[index];
        var symbol_name = symbol.name;
        //这个DefineClass的函数签名是Android9的
        //_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
        if (symbol_name.indexOf("ClassLinker") >= 0 &&
            symbol_name.indexOf("DefineClass") >= 0 &&
            symbol_name.indexOf("Thread") >= 0 &&
            symbol_name.indexOf("DexFile") >= 0) {
            console.log(symbol_name, symbol.address);
            addr_DefineClass = symbol.address;
        }
    }

    console.log("[DefineClass:]", addr_DefineClass);
    if (addr_DefineClass) {
        Interceptor.attach(addr_DefineClass, {
            onEnter: function (args) {
                var dex_file = args[5];
                //ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
                //ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
                var base = ptr(dex_file).add(Process.pointerSize).readPointer();
                var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();

                if (dex_maps[base] == undefined) {
                    dex_maps[base] = size;
                    console.log("hook_dex:", base, size);
                }
            },
            onLeave: function (retval) {}
        });
    }

}

var is_hook_libart = false;

function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);
                if (path.indexOf("libart.so") >= 0) {
                    this.can_hook_libart = true;
                    console.log("[dlopen:]", path);
                }
            }
        },
        onLeave: function (retval) {
            if (this.can_hook_libart && !is_hook_libart) {
                hook_dex();
                is_hook_libart = true;
            }
        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);
                if (path.indexOf("libart.so") >= 0) {
                    this.can_hook_libart = true;
                    console.log("[android_dlopen_ext:]", path);
                }
            }
        },
        onLeave: function (retval) {
            if (this.can_hook_libart && !is_hook_libart) {
                hook_dex();
                is_hook_libart = true;
            }
        }
    });
}


setImmediate(hook_dex);

22.指针运算符和读写 API

frida hook so层方法大全_第4张图片
frida hook so层方法大全_第5张图片

22.1 hook so readPointer()

Java.perform(function () {
        var libc_addr = Process.findModuleByName("libc.so").base;
        console.log("libc address is " + libc_addr);
        // 0x10 转为十进制为 16, 读取
        console.log(libc_addr.readByteArray(0x10));
        // readPointer(), 从此内存位置读取 NativePointer
        console.log("pointer size", Process.pointerSize);
        console.log("readPointer() is " + libc_addr.readPointer());
        console.log("Memory.readPointer()" + Memory.readPointer(libc_addr.add(Process.pointerSize)));
    })

22.2 hook so writePointer()

Java.perform(function () {
        var libc_addr = Process.findModuleByName("libc.so").base;
        console.log("libc_addr : " + libc_addr);
        // 分配四个字节的空间地址
        const r = Memory.alloc(4);
        // 将 libc_addr 指针写入刚申请的 r 中
        r.writePointer(libc_addr);
        // 读取 r 指针的数据
        var buffer = Memory.readByteArray(r, 4);
        console.log(buffer);
    })

//libc_addr : 0x7da7fdf000
        //    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
// 00000000  00 f0 fd a7                                      ....

22.3 hook so readS32(), readU32()

从指定内存地址读取有符号或者无符号 8/16/21/etc 或浮点数/双精度值, 并将其作为数字返回;

Java.perform(function () {
        var libc_addr = Process.findModuleByName("libc.so").base;
        console.log(hexdump(libc_addr));
        console.log(libc_addr.readS32(), (libc_addr.readS32()).toString(16));
        console.log(libc_addr.readU32(), (libc_addr.readU32()).toString(16));
    })

frida hook so层方法大全_第6张图片

22.4 hook so writeS32(), writeU32()

将有符号或无符号8/16/32/等或浮点数/双精度值写入此内存位置

Java.perform(function () {
        // 开辟四个字节的内存空间
        const r = Memory.alloc(4);
        r.writeS32(0x12345678);
        console.log(r.readByteArray(0x10));
    })

<!--  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  78 56 34 12 7d 00 00 00 98 c0 bb a8 7d 00 00 00  xV4.}.......}...
 -->

22.5 hook so readByteArray(), writeByteArray()

Java.perform(function () {
        // 定义一个需要写入的字节数组
        var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
       //这里申请以arr大小的内存空间
        var r = Memory.alloc(arr.length);
        // 将 arr 写入 r 中
        r.writeByteArray(arr);
        // Memory.writeByteArray(r, arr); 同样可以写入
        console.log("memory readbyteArray: ")
        console.log(r.readByteArray(arr.length));
        console.log(Memory.readByteArray(r, arr.length));
    })

22.6 hook so readCString(), writeUtf8String()

Java.perform(function () {
        // 开辟内存空间 存有字符串
        var r = Memory.allocUtf8String("你好,世界");
        // 读取内存中的字符串
        console.log(hexdump(r));
        console.log(r.readCString());
        // 往内存中写入新的字符串
        r.writeUtf8String("Hello,World");
        console.log(hexdump(r));
        console.log(r.readCString())
    })

23.hook 获取 jni array

// 获取 jbytesArray 的指针
var arg1Ptr = Java.vm.getEnv().getByteArrayElements(this.arg1, null) 
// 获取到指针后可以直接 hexdump 打印
console.log("arg1Ptr",hexdump(arg1Ptr));
// 如果是字符串可以直接转
console.log("arg1Ptr",arg1Ptr.readCString());

24.ida 动态调试

1.将 ida 中的 android-server 推入到手机中

adb push /Applications/IDA\ Pro\ 7.0/ida.app/Contents/MacOS/dbgsrv/android_server /data/local/tmp/as

android_server 负责调试 32 位的app, android_server64 负责调试 64 位的app, 改名为 as 可以防止部分 android_server 名称检测

2.给 android_server 增加权限

adb shell
su
chmod +x /data/local/tmp/as

3.进行端口转发 adb forward tcp:11678 tcp:11678
4.启动 android_server 并指定端口为 11678

adb shell
su
/data/local/tmp/as -p11678

5.调试之前先注入 frida

6.新建一个 ida 界面

7.Debugger - Remote ARMLinux/Android debugger

8.hostname: localhost; Port: 11678; 勾选 Save network settings as default

9.frida 打印出目标 function 最终的地址, ida 中 g 到目标 function 地址, 查看是否是

10.thumb 指令集, 如果是 thumboption + g, 将 T 修改为1, 再按 c;
File - Script file - 读取 ida trace 脚本, 需要更改目标 so 文件和 function 的起始地址和结束地址; 读取之后会出现断点; 快捷键: option F7;

11.frida 主动调用脚本, 检测断点是否触发

12.断点检测正常后, ida 中执行 starthook()命令, 进行 hook 操作; 执行suspend_other_thread()挂起其他线程(可选择)

13.ida 中 Debugger - tracing - tracing options - 设置 Trace file 路径 和 取消 Trace over debugger segments 的勾选

14.ida 中 Debugger - tracing - 勾选 instruction tracing

15.frida 主动调用触发断点

16.ida 点击运行按钮进行执行, 此时 ida 中黄色部分为已经执行完的指令, 点击 Debugger -tracing - tracing window 可以看到当前执行进度;

25.ida 添加自有 python 路径

修改路径下的文件 : /Applications/IDA Pro 7.0/ida.app/Contents/MacOS/python/init.py

# Prepare sys.path so loading of the shared objects works
lib_dynload = os.path.join(
    sys.executable,
    IDAPYTHON_DYNLOAD_BASE,
    "python", "lib", "python2.7", "lib-dynload")

# added by kevin
sys.path.insert(0, "/Users/zhangyang/anaconda3/envs/py2/lib/python2.7/site-packages")

26.idapython 脚本调试

在 pycharm 中开启调试
frida hook so层方法大全_第7张图片
在需要调试的脚本中添加断点

import pydevd_pycharm

pydevd_pycharm.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True)

idapython__init__.py文件中添加自有的 python 路径;

先在 pycharm 中打开调试监听, 在 ida 中运行要调试的脚本即可;

27.jni_helper

  • 进入目录 ~/androidFxxk/idaTools/jni_helper
  • java -jar JadxFindJNI/JadxFindJNI.jar
  • ida 中Script File运行 jni_help脚本, 路径 ~/androidFxxk/idaTools/jni_helper/ida/jni_helper.py
  • 导入刚才生成的 output.json 文件即可自动识别

你可能感兴趣的:(frida,【Python爬虫】,python)