侧边栏壁纸
博主头像
Fup1p1 's Blog 博主等级

梦想是财富自由~

  • 累计撰写 38 篇文章
  • 累计创建 24 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【Android】算法转发和Unidbg(Working...)

Fup1p1
2023-07-07 / 0 评论 / 0 点赞 / 1385 阅读 / 0 字 / 正在检测是否收录...

使用Frida的python库

为什么要使用frida的python库捏
image-1688712819615

包名注入

import frida,sys
jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    console.log("retval",retval);
    return retval;
 }
});
"""
process=frida.get_usb_device().attach("com.dodonew.online")//注意这里是附加
script=process.create_script(jscode)
script.load()
sys.stdin.read()

pid注入

只要将attach后面的包名改成pid就行
process=frida.get_usb_device().attach(PID)

spawn模式

device=frida.get_usb_device()
print("device",device)
pid=device.spawn(['com.dodonew.online'])
device.resume(pid) 
print('pid:',pid)
jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    console.log("retval",retval);
    return retval;
 }
});
"""
process=device.attach(pid)
print("process",process)
script=process.create_script(jscode)
script.load()
sys.stdin.read()

image-1688725202807

指定端口

启动frida-server的时候,如果加上指定参数-l 指定端口
image-1688730237751
那么就需要指定ip+端口去访问了

jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    console.log("retval",retval);
    return retval;
 }
});
"""
process=frida.get_device_manager().add_remote_device("10.65.167.18:1145).attach('com.dodonew.online')
script = process.create_script(jscode)
script.load()
print("start")
sys.stdin.read()

send 与 console.log的区别

使用send发送 就要注册一个事件

jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    send(retval);
    return retval;
 }
});
"""
def messageFunc(message,data):
    if message["type"]=='send':  #指定类型
        print(u"[*]{0}".format(message['payload']))    #格式化输出
    else:
        print(message)
device=frida.get_usb_device()
process=device.attach('com.dodonew.online')
script = process.create_script(jscode)
script.on('message',messageFunc)
script.load()
print("start")
sys.stdin.read()

console.log只是单单的输出而已,而send 则是将js中的数据交给python去处理。
所以在messageFunc中可以对数据进行处理。

recv

如果我们想让js接受经过python处理后的数据,就要使用recv去接收

jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    send(retval);
    recv(function(obj){  //回调函数
        console.log("python:",obj.data);
        retval=obj.data;  //替换返回值
        }).wait();
    return retval;
 }
});
"""
def messageFunc(message,data):
    if message["type"]=='send':
        print(u"[*]{0}".format(message['payload']))
        script.post({"data":"78b174c6c83e373e98929b070bf2ffc6"})  #通过post去传送数据
    else:
        print(message)
device=frida.get_usb_device()
process=device.attach('com.dodonew.online')
script = process.create_script(jscode)
script.on('message',messageFunc)
script.load()
print("start")
sys.stdin.read()

image-1688991017956

Rpc远程调用

jscode="""
Java.perform(function(){
 Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String',"java.lang.String","java.lang.String").implementation=function(a,b,c){
    console.log("deskey",b);
    console.log("desIV",c);
    console.log("data",a);
    var retval=this.encodeDesMap(a,b,c);
    console.log("retval",retval);
    return retval;
 }
 Java.use('com.dodonew.online.util.Utils').md5.implementation=function(a){
    console.log("Md5",a);
    var retval=this.md5(a);
    console.log("retval",retval);
    return retval;
 }
});
function test(data){
    var result="";
    Java.perform(function(){
        Java.use('com.dodonew.online.util.Utils').md5(data);
    });
    return result;
}
rpc.exports={
    rpcfunc:test
};
"""
device = frida.get_usb_device()
print("device: ", device)
pid = device.spawn(["com.dodonew.online"])    # 以挂起方式创建进程
device.resume(pid)                            #恢复进程运行
print("pid: ", pid)
process = device.attach(pid)
print("process: ", process)
script = process.create_script(jscode)
script.load()
# device.resume(pid)  # 加载完脚本, 
result=script.exports.rpcfunc("equtype=ANDROID&loginImei=Android352678080375501&timeStamp=1688990595315&userPwd=3333&username=22222&key=sdlkjsdljf0j2fsjk") 
# 注意,如果js中设置的函数名中带大写,那么在这里就要将每一个大写字母改成小写,然后在前面加上一个下划线   比如  如果js中是  rpcFunc:test  那么这里就要写成 .exports.rpc_func
print(result)
print("开始运行")

sys.stdin.read()

Unidbg

Unidbg是基于unicorn开发的框架
• 支持模拟JNI调用
• 支持模拟系统调用指令
• 支持ARM32和ARM64
• 支持Hookzz、Dobby、xHook、原生unicorn Hook等Hook方式
• 支持Android、iOS
好比是在CPU上搭建了一个系统,因此可以很方便地在PC端模拟运行so学习成本较低,不需要复现so算法,补环境后直接运行即可

下载解压Unidbg并分析官方给的样例

package com.bytedance.frameworks.core.encrypt;

import ...

public class TTEncrypt {

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private final DvmClass TTEncryptUtils;

    private final boolean logging;

    TTEncrypt(boolean logging) { //构造函数
        this.logging = logging;

        emulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName("com.qidian.dldl.official") //设置进程名
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数   注意设置好so库的路径   后面true与false是初始化,一般给false
        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数   dm其实就就相当于一个so了
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

        TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils"); //加载类
    }

    void destroy() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }

    public static void main(String[] args) throws Exception {
        TTEncrypt test = new TTEncrypt(true);

        byte[] data = test.ttEncrypt();
        Inspector.inspect(data, "ttEncrypt"); Unidbg提供的接口,方便打印java中的byte数组

        test.destroy();
    }

    byte[] ttEncrypt() {
        if (logging) {
            Symbol sbox0 = module.findSymbolByName("sbox0"); // 在libttEncrypt.so模块中查找sbox0导出符号
            Symbol sbox1 = module.findSymbolByName("sbox1");
            Inspector.inspect(sbox0.createPointer(emulator).getByteArray(0, 256), "sbox0"); // 打印sbox0导出符号在unicorn中的内存数据
            Inspector.inspect(sbox1.createPointer(emulator).getByteArray(0, 256), "sbox1");

            IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
            hookZz.enable_arm_arm64_b_branch(); // 测试enable_arm_arm64_b_branch,可有可无
            hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
                @Override
                public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                    Pointer pointer = ctx.getPointerArg(2);
                    int length = ctx.getIntArg(3);
                    byte[] key = pointer.getByteArray(0, length);
                    Inspector.inspect(key, "ss_encrypt key");
                }
                @Override
                public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                    System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
                }
            });
            hookZz.disable_arm_arm64_b_branch();
            hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {
                @Override
                public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
                    System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));
                }
            });

            Dobby dobby = Dobby.getInstance(emulator);
            dobby.replace(module.findSymbolByName("ss_encrypted_size"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
                @Override
                public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                    System.out.println("ss_encrypted_size.onCall arg0=" + context.getIntArg(0) + ", originFunction=0x" + Long.toHexString(originFunction));
                    return HookStatus.RET(emulator, originFunction);
                }
                @Override
                public void postCall(Emulator<?> emulator, HookContext context) {
                    System.out.println("ss_encrypted_size.postCall ret=" + context.getIntArg(0));
                }
            }, true);

            IxHook xHook = XHookImpl.getInstance(emulator); // 加载xHook,支持Import hook,文档看https://github.com/iqiyi/xHook
            xHook.register("libttEncrypt.so", "strlen", new ReplaceCallback() { // hook libttEncrypt.so的导入函数strlen
                @Override
                public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                    Pointer pointer = context.getPointerArg(0);
                    String str = pointer.getString(0);
                    System.out.println("strlen=" + str);
                    context.push(str);
                    return HookStatus.RET(emulator, originFunction);
                }
                @Override
                public void postCall(Emulator<?> emulator, HookContext context) {
                    System.out.println("strlen=" + context.pop() + ", ret=" + context.getIntArg(0));
                }
            }, true);
            xHook.register("libttEncrypt.so", "memmove", new ReplaceCallback() {
                @Override
                public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                    RegisterContext context = emulator.getContext();
                    Pointer dest = context.getPointerArg(0);
                    Pointer src = context.getPointerArg(1);
                    int length = context.getIntArg(2);
                    Inspector.inspect(src.getByteArray(0, length), "memmove dest=" + dest);
                    return HookStatus.RET(emulator, originFunction);
                }
            });
            xHook.register("libttEncrypt.so", "memcpy", new ReplaceCallback() {
                @Override
                public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                    RegisterContext context = emulator.getContext();
                    Pointer dest = context.getPointerArg(0);
                    Pointer src = context.getPointerArg(1);
                    int length = context.getIntArg(2);
                    Inspector.inspect(src.getByteArray(0, length), "memcpy dest=" + dest);
                    return HookStatus.RET(emulator, originFunction);
                }
            });
            xHook.refresh(); // 使Import hook生效
        }

        if (logging) {
            emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,就可以单步地动态调试了,可输入c命令取消附加继续运行
        }
        byte[] data = new byte[16];
        ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 执行Jni方法  该函数比callFunction多封装了一些代码 所以就不用自己去寻找函数地址,不需要自己去包装参数了。     此例子中我们观察 函数签名 ttEncrypt([BI)[B  其中有两个参数 一个是Byte数组  一个是Int, 返回值是Int  ,所以后面传入了一个使用Unidbg接口打包的byte数组(注意一定要用这个API打包后的byte,直接给byte类型是不对的)以及一个data的长度作为第二个参数。由于有返回值,所以使用callStaticJniMethodObject函数,这样就可以接受返回的对象。
        return array.getValue();
    }

}

一路查询callStaticJniMethodObject的实现,可以发现
image-1689085262586
nativeMap中已经放入了动态注册函数的函数名签名 以及对于的键值:函数地址。
image-1689085573343
当然,也不一样要用这个函数去获得函数地址,就像之前so库逆向一样,直接得到so库的基址,通过基址+偏移+ (thumb?1:0)

Unidbg主动调用案例

主动调用一个静态加载的JNI函数

package com.xiaojianbang.ndk;
import ...
public class Nativedemo {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass NativeHelper;
    private final boolean logging;
    Nativedemo(boolean logging) {
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.xiaojianbang.app")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        //dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
    }
    void destroy() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }
    public static void main(String[] args) throws Exception {
        Nativedemo test = new Nativedemo(true);
        int retval= test.callFunc();
        System.out.println("retval: 0x"+Integer.toHexString(retval));
        test.destroy();
    }
    int callFunc() {
        byte[] data = new byte[16];
        int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I",0x10,0x20,0x30); // 执行Jni方法
        return retval;
    }
}

image-1689124395963
可以发现确实执行so库,并且得到了正确得到值

image-1689124481518

主动调用一个动态加载的JNI函数

package com.xiaojianbang.ndk;
import ...

import java.io.File;

public class NativeDemo2 {

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private final DvmClass NativeHelper;

    private final boolean logging;

    NativeDemo2(boolean logging) {
        this.logging = logging;

        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.xiaojianbang.app")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setJni(new AbstractJni() { //使用Unidbg实现好的方法
        });
        vm.setVerbose(logging); // 设置是否打印Jni调用细节

        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

        NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
    }

    void destroy() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }

    public static void main(String[] args) throws Exception {
        NativeDemo2 test = new NativeDemo2(true);

        test.callFunc();
        test.destroy();
    }
    void callFunc() {
        StringObject md5retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1")); // 执行Jni方法
        System.out.println("md5result = :"+md5retval.getValue());

    }
}

        vm.setJni(new AbstractJni() { //使用Unidbg实现好的方法
        });

image-1689127596292

然后发现一行报错
image-1689127946617
内存不够了???
加一行代码就行了

emulator.getSyscallHandler().setEnableThreadDispatcher(true);

最后能成功输出md5的值,并且我们在JNI_OnLoad创建的线程也能成功跑起来了。
image-1689127458295

一个so调用另一个so的函数

将另一个so也拖到目录下,然后加载一下就行

        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        DalvikModule dm1 = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbangA.so"), false);

通过符号去调用函数

如果是返回的是基本数据类型,那就是返回值

       Symbol symbol=module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add");
       Number number= symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(NativeHelper),0x40,0x20,0x10);
       int result= number.intValue();
       System.out.println("result "+result);

如果返回的是Java中的对象类型的话就要多些步骤了

        Symbol symbol=module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");
        Number number= symbol.call(emulator,vm.getJNIEnv(),vm.addLocalObject(NativeHelper));
        int result=number.intValue();
        System.out.println("result "+vm.getObject(result).getValue()); //虚拟机的引用,result不是真正的返回值,更像是一种索引,然后要从dvmObj里面去捞数据。
	DvmClass xxx= vm.resolveClass(path)  创建一个jclass
    	DvmObject xxxx=xxx.newObject(arg...) 根据jclass去实例化一个对象

Unidbg补环境

unidbg实现了大部分的JNI函数,如果有没有实现的,可以自己去实现
对于已经实现的,可以做一些覆写。
可以在 AbstractJni中覆写一些方法,比如下面这段就又再一次地修改了参数,使得最后调用的md5的时候,参数是Unidbg

        vm.setJni(new AbstractJni() {
            @Override
            public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
                System.out.println("signature"+signature);
                if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){
                    String arg= (String) dvmObject.getValue();
                    System.out.println("arg "+arg);
                    byte[] strBytes="Unidbg".getBytes();
                    return new ByteArray(vm,strBytes);
                }
                return super.callObjectMethodV(vm, dvmObject, signature, vaList);
            }
        });

还有一种写法,就是直接继承AbstractJni这个类,然后直接写 vm.setJni(this)就行,然后要覆写的方法直接写就行。

Unidbg Hook

Unidbg支持dobby hookzz whale xhook
image-1689257312031
hookzz是dobby的前身,对32位的支持比较好,后者对64位支持比较好

unidbg是基于unicorn开发的,所以也支持unicorn的hook(指令级的hook、块级的hook、内存读写hook、异常hook)以及unidbg封装的console debugger(可以下多个断点,类似于gdb的调试)

hookzz

可以通过符号hook(本质上也是通过符号去取地址) 也可以通过地址hook
image-1689302064197

preCall和postCall有点像Xposed的 beforeHookedMethod和afterHookedMethod。

void callFunc() {

        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
            @Override
            public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                md5_context = ctx.getPointerArg(0);   //获取参数
                Pointer plaintext= ctx.getPointerArg(1);
                int length = ctx.getIntArg(2);
                Inspector.inspect(md5_context.getByteArray(0,64),"preCall md5_context");
                Inspector.inspect(plaintext.getByteArray(0,64), "Fup1p1");
            }
            @Override
            public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                Inspector.inspect(md5_context.getByteArray(0,64),"postCall md5_context");
            }
        });
        StringObject md5result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));
    }

hook中注意参数的传递

hook一个jstring2cstr的函数,参数是jstring,返回值都是char*

        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
            @Override
            public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                StringObject obj=vm.getObject(ctx.getIntArg(1));  //参数是jstring,所以要通过getObject去vm虚拟机中捞数据
                System.out.println(obj.getValue());
            }
            @Override
            public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                byte[] bytes =ctx.getPointerArg(0).getByteArray(0,16);  //返回的数据是char*,先转成UnidbgPointer然后再去取其byte数组
                Inspector.inspect(bytes,"postcall:"+bytes);
            }
        });
          Number number =module.callFunction(
                  emulator,
                  "_Z12jstring2cstrP7_JNIEnvP8_jstring",
                  vm.getJNIEnv(),
                  vm.addLocalObject(new StringObject(vm,"Fup1p1") ));//jstring 包装成 String Object 然后啊丢到vm虚拟机里面
          long cstrAddr=number.longValue();
          byte[] bytes=emulator.getMemory().pointer(cstrAddr).getByteArray(0,16);//先转成pointer类型,然后调用pointer的方法去获取。
          Inspector.inspect(bytes,"jstring2cstr");

hook中修改参数

        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<HookZzArm64RegisterContext>() { // 注意要选择HookZzArm64RegisterContext
            @Override
            public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
                StringObject obj=vm.getObject(ctx.getIntArg(1));
                System.out.println(obj.getValue());
            }
            @Override
            public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
                String str=ctx.getPointerArg(0).getString(0);
                System.out.println(str);
                int hashcode=vm.addLocalObject(new StringObject(vm,"A1natas")); 
                ctx.setXLong(0,hashcode);
            }
        });
          Number number =module.callFunction(
                  emulator,
                  "_Z12jstring2cstrP7_JNIEnvP8_jstring",
                  vm.getJNIEnv(),
                  vm.addLocalObject(new StringObject(vm,"Fup1p1") ));//jstring 包装成 String Object 然后丢到vm虚拟机里面
          StringObject cstrobj=vm.getObject(number.intValue());
        System.out.println(cstrobj.getValue());

hookzz inline Hook

上面介绍的hook也就只能hook起始地址,如果要hook中间代码
image-1689318141906

        hookZz.instrument(module.base +0x1A2C , new InstrumentCallback<Arm64RegisterContext>() { //选择对应的架构
            @Override
            public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
                System.out.println("W8=0x" + Integer.toHexString(ctx.getXInt(8))+ ", W9=0x" + Integer.toHexString(ctx.getXInt(9)));
            }
        });
          int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I",0x10,0x20,0x30);
          System.out.println("retval: 0x"+Integer.toHexString(retval));

image-1689321758153
以hook上面这行汇编为例子
image-1689321663636

hookzz replace

        IHookZz hookZz = HookZz.getInstance(emulator);
        hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                System.out.println("have called");
                int hashcode=vm.addLocalObject(new StringObject(vm,"Hooked"));
                return HookStatus.LR(emulator,hashcode);   //如果要返回int也可以,但是注意下面就要改成callStaticJniMethodInt
//                return super.onCall(emulator, originFunction);   
            }
        });
         StringObject md5Result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));
        System.out.println("Md5Result:"+md5Result.getValue());

UnicornHook

使用unicorn 进行hook的时候不需要考虑是否要+1
以MD5为例,hook它的context与返回值
image-1689605242735

        emulator.getBackend().hook_add_new(new CodeHook() {
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                RegisterContext context=emulator.getContext();
                if(address==module.base+0x1f04){
                    Pointer plaintext=context.getPointerArg(0);
                    byte[] plain=plaintext.getByteArray(0,32);
                    Inspector.inspect(plain, "plaintext");
                    Pointer md5context=context.getPointerArg(1);
                    byte[] md5ctx=md5context.getByteArray(0,32);
                    Inspector.inspect(md5ctx,"md5ctx");
                }
                else if(address == module.base+0x1f14){
                    Pointer callback=context.getPointerArg(1);
                    byte[] md5final=callback.getByteArray(0,16);
                    Inspector.inspect(md5final,"return");

                }
//                System.out.println("hooked");
            }
            @Override
            public void onAttach(UnHook unHook) {
            }
            @Override
            public void detach() {
            }
        }, module.base+0x1F04, module.base+0x1F14,null );
        StringObject md5result=NativeHelper.callStaticJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm,"Fup1p1"));

打印调用栈

emulator.getUnwinder().unwind();

动态调试

操作很简单

        Debugger debugger=emulator.attach();
        debugger.addBreakPoint(module.base+0x1A28);
        debugger.addBreakPoint(module.base+0x1A3C);

命令行调试,类似与gdb

监测内存读写

之前在frida的so层逆向中也谈及监控内存的读写,但是不够精确,因为是靠修改页的读写权限实现的。

String traceFile = "filename";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);//由于输出太多,所以让其写入到一个文件中去


emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);//追踪内存读,并且重定向到你要写入的文件里去

emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);//追踪内存写,参数就是起始地址和结束地址

Unidbg trace

会记录下执行过的指令,使其记录寄存器的变动(老版本的Unidbg需要自己去修改源码实现)。

String traceFile = "filename";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);

0

评论区