IDA 使用指南

本文介绍了Java中的JNI(JavaNativeInterface),展示了如何使用反汇编工具IDA处理so文件中的C/C++代码,以及如何通过JNI_OnLoad方法定位和调试Android应用中的Native模块。作者还讨论了不同处理器架构的so文件选择和Android设备处理器识别的方法。
摘要由CSDN通过智能技术生成

前言

在Java中有一个叫做JNI的东西,它的全称是Java Native Interface,即Java本地接口,这是Java调用Native语言的一种特性(这里说的Native语言通常指C/C++)。有了JNI,Java就可以调用由C/C++编写的代码了。

要想还原so文件中的C/C++代码,就不能用反编译工具了,要使用反汇编工具,目前比较流行的就是IDA。但需要注意,完整还原C/C++代码几乎是不可能的,工具只能帮助我们还原出大概的执行逻辑。

IDA,又叫IDA Pro,英文全称是 Interactive Disassembler Professional,即交互式反汇编器专业版。

定位 so 文件

将 apk 复制一份,改后缀为 zip,然后解压缩,在 lib -> armeabi-v7a 中寻找 libbili.so 文件:

在这里插入图片描述

**注意:**还有下面这种load方式
在这里插入图片描述

因为是在 armeabi-v7a 中寻找的 so 文件,因此需要打开 32 位的 IDA:

IDA 加载 so 文件后页面如下:
在这里插入图片描述

补充:

lib文件夹下可能有多个文件夹,分别是 arm64-v8a、armeabi-v7a、x86 和 x86_64,so 文件可以运行在使用对应指令架构的设备上,这些设备分别如下:

  • arm64-v8a:适配第 8 代、64 位 ARM 处理器,主要是 Android 真机。
  • armeabi-v7a:适配第 7 代、32 位 ARM 处理器,主要是 Android 真机。
  • x86:适配 x86 架构、32 位处理器,主要是模拟器或一些平板设备。
  • x86_64:适配 x86 架构、64 位处理器,主要是模拟器或一些平板设备。

可以运行如下命令来查看自己的手机用的是那种处理器:

adb shell getprop ro.product.cpu.abi

这样当 App 运行时,就会加载执行对应指令架构文件夹下的so文件。在选择反汇编的so文件时就选择对应指令架构的文件夹。

但武佩奇说选择那个指令架构的文件夹都可以,这里迷惑?

优化显示

点击进入 jni_onload 方法,然后按 F5 ,跳转到方法的具体实现中:

看起来不是很直观,右键->点击 Hide casts,隐藏转换后代码可读性会好一点:

把 JNI_OnLoad 方法的第一个参数进行转化成VM类型,可读性会更好:

在 int 上右键->Convert to struct *,选择 JavaVM:

(只有JNI_OnLoad 方法的第一个参数是JavaVM,其他方法的第一个参数都是JNIEnv对象)
在这里插入图片描述

在这里插入图片描述

补充:如果可选类型中没有VM类型怎么办?

File -> Load file -> Parse C header file,选择 jni.h 即可:

在这里插入图片描述

输出so文件的名称

当无法找到是那个so文件时,如海南航空,如何办?
在这里插入图片描述

可以使用下列脚本:

如果是静态注册:

Java.perform(function () {
    var dlsymadd = Module.findExportByName("libdl.so", 'dlsym');
    Interceptor.attach(dlsymadd, {
        onEnter: function (args) {
            this.info = args[1];

        }, onLeave: function (retval) {
            //那个so文件 module.name
            var module = Process.findModuleByAddress(retval);
            if (module == null) {
                return retval;
            }
            // native方法
            var funcName = this.info.readCString();

            if (funcName.indexOf("getHNASignature") !== -1) { // 需要修改
                console.log(module.name);
                console.log('\t', funcName);
            }
            return retval;
        }
    })
});


// Application(identifier="com.rytong.hnair", name="海南航空", pid=14958, parameters={})
// frida -U -f  com.rytong.hnair  -l static_find_so.js

在这里插入图片描述

如果是动态注册:

var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 &&
        symbol.name.indexOf("RegisterNatives") >= 0 &&
        symbol.name.indexOf("CheckJNI") < 0) {
        addrRegisterNatives = symbol.address;
        console.log("RegisterNatives is at ", symbol.address, symbol.name);
    }
}
console.log("addrRegisterNatives=", addrRegisterNatives);

if (addrRegisterNatives != null) {
    Interceptor.attach(addrRegisterNatives, {
        onEnter: function (args) {
            var env = args[0];
            var java_class = args[1];
            var class_name = Java.vm.tryGetEnv().getClassName(java_class);
            var taget_class = "com.xunmeng.pinduoduo.secure.DeviceNative"; // 需要修改
            
            if (class_name === taget_class) {
                console.log("\n[RegisterNatives] method_count:", args[3]);
                var methods_ptr = ptr(args[2]);
                var method_count = parseInt(args[3]);

                for (var i = 0; i < method_count; i++) {
                    // Java中的函数名
                    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                    // 参数和返回值类型
                    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                    // C中的函数指针
                    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

                    var name = Memory.readCString(name_ptr); // 读取java中函数名
                    var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
                    var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块

                    var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
                    // console.log("[RegisterNatives] java_class:", class_name);
                    console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
                }
            }
        }
    });
}

// frida -U -f  com.xunmeng.pinduoduo  -l dynamic_find_so.js

拓展:

IDA还可以对so文件进行动态调试分析,在崔庆才的书中有介绍,但我看完后感觉没啥用。记录一下,以后用到时翻阅。





【PS:Android 逆向进阶专栏地址:https://blog.csdn.net/dafan0/category_12682484.html】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值