吾爱破解安卓逆向入门教程

  • A+
所属分类:移动安全
吾爱破解安卓逆向入门教程

一、课程目标

  1. 了解 Frida-Native-Hook
  2. 借助 ida 脚本实现一键式 hook

二、工具

  1. 教程 Demo(更新)
  2. jadx-gui
  3. VS Code

三、课程内容

1.Process、Module、Memory 基础

1.Process

Process  对象代表当前被 Hook 的进程,能获取进程的信息,枚举模块,枚举范围等

API 含义
Process.id 返回附加目标进程的  PID
Process.isDebuggerAttached() 检测当前是否对目标程序已经附加
Process.enumerateModules() 枚举当前加载的模块,返回模块对象的数组
Process.enumerateThreads() 枚举当前所有的线程,返回包含  idstatecontext  等属性的对象数组

2.Module

Module  对象代表一个加载到进程的模块(例如,在 Windows 上的 DLL,或在 Linux/Android 上的 .so 文件), 能查询模块的信息,如模块的基址、名称、导入 / 导出的函数等

API 含义
Module.load() 加载指定 so 文件,返回一个 Module 对象
enumerateImports() 枚举所有 Import 库函数,返回 Module 数组对象
enumerateExports() 枚举所有 Export 库函数,返回 Module 数组对象
enumerateSymbols() 枚举所有 Symbol 库函数,返回 Module 数组对象
Module.findExportByName(exportName)、Module.getExportByName(exportName) 寻找指定 so 中 export 库中的函数地址
Module.findBaseAddress(name)、Module.getBaseAddress(name) 返回 so 的基地址

3.Memory

Memory是一个工具对象,提供直接读取和修改进程内存的功能,能够读取特定地址的值、写入数据、分配内存等

方法 功能
Memory.copy() 复制内存
Memory.scan() 搜索内存中特定模式的数据
Memory.scanSync() 同上,但返回多个匹配的数据
Memory.alloc() 在目标进程的堆上申请指定大小的内存,返回一个NativePointer
Memory.writeByteArray() 将字节数组写入一个指定内存
Memory.readByteArray 读取内存

2. 枚举导入导出表

  1. 导出表(Export Table):列出了库中可以被其他程序或库访问的所有公开函数和符号的名称。
  2. 导入表(Import Table):列出了库需要从其他库中调用的函数和符号的名称。

简而言之,导出表告诉其他程序:“这些是我提供的功能。”,而导入表则表示:“这些是我需要的功能。”

function hookTest1(){Java.perform(function(){
        // 打印导入表
        var imports = Module.enumerateImports("lib52pojie.so");
        for(var i =0; i < imports.length;i++){if(imports[i].name == "vip"){console.log(JSON.stringify(imports[i])); // 通过 JSON.stringify 打印 object 数据
                console.log(imports[i].address);
            }
        }
        // 打印导出表
        var exports = Module.enumerateExports("lib52pojie.so");
        for(var i =0; i < exports.length;i++){console.log(JSON.stringify(exports[i]));
        }

    })
}

3.Native 函数的基础 Hook 打印

  • 整数型、布尔值类型、char 类型
function hookTest2(){Java.perform(function(){
    // 根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
    console.log(helloAddr); 
    if(helloAddr != null){
            //Interceptor.attach 是 Frida 里的一个拦截器
        Interceptor.attach(helloAddr,{
                //onEnter 里可以打印和修改参数
            onEnter: function(args){  //args 传入参数
                console.log(args[0]);  // 打印第一个参数的值
                console.log(this.context.x1);  // 打印寄存器内容
                console.log(args[1].toInt32()); //toInt32()转十进制
                                    console.log(args[2].readCString()); // 读取字符串 char 类型
                                    console.log(hexdump(args[2])); // 内存 dump

            },
            //onLeave 里可以打印和修改返回值
            onLeave: function(retval){  //retval 返回值
                console.log(retval);
                console.log("retval",retval.toInt32());
            }
        })
    }
})
}
  • 字符串类型
function hookTest2(){Java.perform(function(){
        // 根据导出函数名打印地址
        var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
        if(helloAddr != null){
            Interceptor.attach(helloAddr,{
                //onEnter 里可以打印和修改参数
                onEnter: function(args){  //args 传入参数
                    // 方法一
                    var jString = Java.cast(args[2], Java.use('java.lang.String'));
                    console.log("参数:", jString.toString());
                    // 方法二
                    var JNIEnv = Java.vm.getEnv();
                    var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();        
                    console.log("参数:", originalStrPtr);                                
                },
                //onLeave 里可以打印和修改返回值
                onLeave: function(retval){  //retval 返回值
                    var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
                    console.log("返回值:", returnedJString.toString());
                }
            })
        }
    })
}

4.Native 函数的基础 Hook 修改

  • 整数型修改
function hookTest3(){Java.perform(function(){
    // 根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
    console.log(helloAddr);
    if(helloAddr != null){
        Interceptor.attach(helloAddr,{onEnter: function(args){  //args 参数
                args[0] = ptr(1000); // 第一个参数修改为整数 1000,先转为指针再赋值
                console.log(args[0]);

            },
            onLeave: function(retval){  //retval 返回值
                retval.replace(20000);  // 返回值修改
                console.log("retval",retval.toInt32());
            }
        })
    }
})
}
  • 字符串类型修改
function hookTest2(){Java.perform(function(){
    // 根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
    if(helloAddr != null){
        Interceptor.attach(helloAddr,{
            //onEnter 里可以打印和修改参数
            onEnter: function(args){  //args 传入参数
                var JNIEnv = Java.vm.getEnv();
                var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();        
                console.log("参数:", originalStrPtr);
                var modifiedContent = "至尊";
                var newJString = JNIEnv.newStringUtf(modifiedContent);
                args[2] = newJString;                                
            },
            //onLeave 里可以打印和修改返回值
            onLeave: function(retval){  //retval 返回值
                var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
                console.log("返回值:", returnedJString.toString());
                var JNIEnv = Java.vm.getEnv();
                var modifiedContent = "无敌";
                var newJString = JNIEnv.newStringUtf(modifiedContent);
                retval.replace(newJString);
            }
        })
    }
})
}

5.SO 基址的获取方式

var moduleAddr1 = Process.findModuleByName("lib52pojie.so").base;  
var moduleAddr2 = Process.getModuleByName("lib52pojie.so").base;  
var moduleAddr3 = Module.findBaseAddress("lib52pojie.so");

6.Hook 未导出函数与函数地址计算

function hookTest6(){Java.perform(function(){
        // 根据导出函数名打印基址
        var soAddr = Module.findBaseAddress("lib52pojie.so");
        console.log(soAddr);
        var funcaddr = soAddr.add(0x1071C);  
        console.log(funcaddr);
        if(funcaddr != null){
            Interceptor.attach(funcaddr,{onEnter: function(args){//args 参数},
                onLeave: function(retval){  //retval 返回值
                    console.log(retval.toInt32());
                }
            })
        }
    })
}

函数地址计算

  1. 安卓里一般 32 位的 so 中都是 thumb 指令,64 位的 so 中都是 arm 指令
  2. 通过 IDA 里的 opcode bytes 来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph)   输入 4)
  3. thumb 指令,函数地址计算方式:so 基址 + 函数在 so 中的偏移 + 1
    arm 指令,函数地址计算方式:so 基址 + 函数在 so 中的偏移

7.Hook_dlopen

  1. dlopen 源码
  2. android_dlopen_ext 源码
function hook_dlopen() {var dlopen = Module.findExportByName(null, "dlopen");
    Interceptor.attach(dlopen, {onEnter: function (args) {var so_name = args[0].readCString();
            if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
        }, onLeave: function (retval) {if (this.call_hook) hookTest2();}
    });
    // 高版本 Android 系统使用 android_dlopen_ext
    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var so_name = args[0].readCString();
            if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
        }, onLeave: function (retval) {if (this.call_hook) hookTest2();}
    });
}

8. 借助 IDA 脚本实现一键式 hook

吾爱破解安卓逆向入门教程
  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: