当前位置: 首页 > 工具软件 > unidbg > 使用案例 >

使用Unidbg进行Hook和Patch

傅阳炎
2023-12-01

http://zhuoyue360.com/crack/75.html

什么是unidbg就不多说了,大家自行百度。

写个demo,搭架子

写个简单的加减法来给unidbg调用。

public native String add(int a,int b);
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_lesson2_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
    // TODO: implement add()
    if(a<0){
        a = -a;
    }
    if(b<0){
        b=-b;
    }
    return a+b;
}

unidbg的基础调用代码

package com.example.lesson2;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.sun.jna.Pointer;


import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MainActivity {
    // 1. 环境初始化
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Memory memory;
    private final Module module;

    public MainActivity(){
        // 2.1 创建模拟机
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();
        // 2.2 创建memory
        memory = emulator.getMemory();
        // 2.2 创建Android sdk版本. 支持19和23版
        memory.setLibraryResolver(new AndroidResolver(23));
        // 2.3 创建vm
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/lesson2/app-debug.apk"));

        // 载入so文件 且 初始化,返回的说so加载到虚拟机的一个对象
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/lesson2/liblesson2.so"), true);
        module = dalvikModule.getModule();
        vm.callJNI_OnLoad(emulator,module);

    }



    public void callAdd(int a,int b){
        DvmObject obj = ProxyDvmObject.createObject(vm,this);
        int result  = obj.callJniMethodInt(emulator,"add(II)I",a,b);
        System.out.println(result);

    }
   
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load the vm " + (System.currentTimeMillis() - start) + "ms");
        mainActivity.callAdd(1,8);
    }



}

Hookzz

hookzz基本代码

    public  void hook(){
        HookZz hook = HookZz.getInstance(emulator);
        hook.replace(module.base + 0xfdd4, new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                System.out.println("onCall no context");
                return super.onCall(emulator, originFunction);
            }

            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println("onCall");
                return super.onCall(emulator, context, originFunction);
            }

            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                System.out.println("postCall");
                super.postCall(emulator, context);
            }
        },true);
    }

postCall需要在hook.replace的参数3要为true的时候,才会执行。

onCall:调用的时候 可以在此处修改参数等.
onPost:离开的时候 可以在此处修改返回值

论证一下,调试看看执行的顺序, 从日志中, 可以看到是 onCall -> onCall(no context) -> postCall

load the vm 527ms
onCall
onCall no context
postCall
3

Hook实例

在开始进行真正的hook时,需要先说明一下ARM汇编子程序参数传递的调用约定相关内容

1. 汇编子程序参数传递的调用约定

  1. ARM32汇编子程序参数传递的调用约定
    规则内容

一.被调用模块的寄存器使用

 1.调用模块和被调用模块通过R0-R3传递参数,因此参数少于四个时可以随意
     使用剩余的而不必保存和恢复
 2.使用R4-R11之前一定要先在堆栈中保存起来,退出时再恢复
 3.可以使用堆栈,但一定要保证堆栈指针(R13)在进入时和退出时相等
 4.R14用于保存返回地址,使用前一定要备份

二.被调用模块的堆栈使用

 ATPCS规则规定堆栈是满递减(fD)型的,因此使用STMFD/LDMFD指令操作,
 注意保证进入和退出时堆栈指针相等

三.参数传递

 当少于四个时,按从左到右的顺序依次放在r0,r1,r2,r3中;
 当多于四个时,前四个放在r0,r1,r2,r3中,剩余的放在堆栈中,最后一个
 参数先入栈,第五个参数最后入栈,即从右到左入栈

四.返回值

 结果为32位时,通过R0返回
 结果为64位时,r0放低32位,r1放高32位

如果不涉及Thumb指令集,全部规则就这几条,

  1. ARM64汇编子程序参数的调用约定

寄存器
在arm64汇编中寄存器是64bit的,使用X[n]表示,低32位以w[n]表示

64位架构中有3164位的通用寄存器。

可以通过register read查看

(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x00000002805d8a20
        x2 = 0x00000002837e7980
        x3 = 0x00000002805d8a20
        x4 = 0x0000000000000001
        x5 = 0x0000000000000001
        x6 = 0x0000000100d54000  
        x7 = 0x0000000000000000
        x8 = 0x0000200000000000
        x9 = 0x000161a1d63f7945 (0x00000001d63f7945) (void *)0x01d63f7cb0000001
       x10 = 0x0000000000000000
       x11 = 0x000000000000006d
       x12 = 0x000000000000006d
       x13 = 0x0000000000000000
       x14 = 0x0000000000000000
       x15 = 0x00000001ca634e6d  "touchesBegan:withEvent:"
       x16 = 0x000000019c4cd47c  libobjc.A.dylib`objc_storeStrong
       x17 = 0x0000000000000000
       x18 = 0x0000000000000000
       x19 = 0x0000000100f17810
       x20 = 0x00000002837e7920
       x21 = 0x00000002805d8a20
       x22 = 0x00000001ca634e6d  "touchesBegan:withEvent:"
       x23 = 0x0000000100e11a30
       x24 = 0x00000002837e7980
       x25 = 0x0000000100e11a30
       x26 = 0x00000002805d8a20
       x27 = 0x00000001ca62d900  "countByEnumeratingWithState:objects:count:"
       x28 = 0x00000002805d8a20
        fp = 0x000000016f47d730 // x29
        lr = 0x00000001009866dc  ArmAssembly`-[ViewController  touchesBegan:withEvent:] + 84 at ViewController.m:38 // x30
        sp = 0x000000016f47d720
        pc = 0x0000000100986720  ArmAssembly`foo1 + 16 at ViewController.m:46
      cpsr = 0x80000000

(lldb) 

x0~x7:一般是函数的参数,大于8个的会通过堆栈传参
x0:一般表示返回值
pc:表示当前执行的指令的地址
lr:链接寄存器,存放着函数的返回地址。lr也就是x30,这个里面存放着函数的返回地址

更多关于arm64汇编的内容可以看看下面的文档

https://www.jianshu.com/p/99067af33f14

2. 获取和修改参数.

获取参数内容

经过上面的简单介绍,我们现在要如何去修改参数呢?看下面的代码。

Java_com_example_lesson2_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {

在32位的情况下
上面说了,如果小于等于四个参数的话,可以直接放在r0-r3.我们这边刚刚好.
r0是env,r1是jobject,r2是a,r3是b。写个代码打印r2,r3试试看
在64位的情况下
只要参数的个数小于8个,都会放在x0-7中。
x0表示返回值。
由于我的so的64位的,所以应该打印是x2,和x3

@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
    System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
    return super.onCall(emulator, context, originFunction);
}

可以看到,这个结果和我们传入的参数是一样的

load the vm 669ms
R2:1, R3:8
onCall no context
9
修改参数内容

通过emulator.getBackend().reg_write进行值的修改。

public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
    System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
    emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X3,2);
    return super.onCall(emulator, context, originFunction);
}

3.获取和修改返回结果

public void postCall(Emulator<?> emulator, HookContext context) {
    System.out.println("postCall");
    // 获取X0的内容
    System.out.println(String.format("X0:%d",emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0)));
    // 修改返回值
    emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,6666);
    super.postCall(emulator, context);
}

Patch

什么是Patch?

1.加改成减法

在线arm汇编指令与HEX转换网站:https://armconverter.com/
我需要把这个ADD改成SUB,那么就需要上面的这个网站

.text:000000000000FDD4             ; =============== S U B R O U T I N E =======================================
.text:000000000000FDD4
.text:000000000000FDD4
.text:000000000000FDD4                             EXPORT Java_com_example_lesson2_MainActivity_add
.text:000000000000FDD4             Java_com_example_lesson2_MainActivity_add
.text:000000000000FDD4                                                     ; DATA XREF: LOAD:00000000000038A8↑o
.text:000000000000FDD4
.text:000000000000FDD4             var_18          = -0x18
.text:000000000000FDD4             var_14          = -0x14
.text:000000000000FDD4             var_10          = -0x10
.text:000000000000FDD4             var_8           = -8
.text:000000000000FDD4
.text:000000000000FDD4             ; __unwind {
.text:000000000000FDD4 FF 83 00 D1                 SUB             SP, SP, #0x20
.text:000000000000FDD8 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
.text:000000000000FDDC E1 0B 00 F9                 STR             X1, [SP,#0x20+var_10]
.text:000000000000FDE0 E2 0F 00 B9                 STR             W2, [SP,#0x20+var_14]
.text:000000000000FDE4 E3 0B 00 B9                 STR             W3, [SP,#0x20+var_18]
.text:000000000000FDE8 E8 0F 40 B9                 LDR             W8, [SP,#0x20+var_14]
.text:000000000000FDEC 1F 01 00 71                 CMP             W8, #0
.text:000000000000FDF0 E8 B7 9F 1A                 CSET            W8, GE
.text:000000000000FDF4 A8 00 00 37                 TBNZ            W8, #0, loc_FE08
.text:000000000000FDF8 E8 0F 40 B9                 LDR             W8, [SP,#0x20+var_14]
.text:000000000000FDFC 09 00 80 52                 MOV             W9, #0
.text:000000000000FE00 28 01 08 6B                 SUBS            W8, W9, W8
.text:000000000000FE04 E8 0F 00 B9                 STR             W8, [SP,#0x20+var_14]
.text:000000000000FE08
.text:000000000000FE08             loc_FE08                                ; CODE XREF: Java_com_example_lesson2_MainActivity_add+20↑j
.text:000000000000FE08 E8 0B 40 B9                 LDR             W8, [SP,#0x20+var_18]
.text:000000000000FE0C 1F 01 00 71                 CMP             W8, #0
.text:000000000000FE10 E8 B7 9F 1A                 CSET            W8, GE
.text:000000000000FE14 A8 00 00 37                 TBNZ            W8, #0, loc_FE28
.text:000000000000FE18 E8 0B 40 B9                 LDR             W8, [SP,#0x20+var_18]
.text:000000000000FE1C 09 00 80 52                 MOV             W9, #0
.text:000000000000FE20 28 01 08 6B                 SUBS            W8, W9, W8
.text:000000000000FE24 E8 0B 00 B9                 STR             W8, [SP,#0x20+var_18]
.text:000000000000FE28
.text:000000000000FE28             loc_FE28                                ; CODE XREF: Java_com_example_lesson2_MainActivity_add+40↑j
.text:000000000000FE28 E8 0F 40 B9                 LDR             W8, [SP,#0x20+var_14]
.text:000000000000FE2C E9 0B 40 B9                 LDR             W9, [SP,#0x20+var_18]
.text:000000000000FE30 00 01 09 0B                 ADD             W0, W8, W9  // 我们要操作这里
.text:000000000000FE34 FF 83 00 91                 ADD             SP, SP, #0x20
.text:000000000000FE38 C0 03 5F D6                 RET
.text:000000000000FE38             ; } // starts at FDD4
.text:000000000000FE38             ; End of function Java_com_example_lesson2_MainActivity_add
.text:000000000000FE38
.text:000000000000FE3C
.text:000000000000FE3C             ; =============== S U B R O U T I N E =======================================
.text:000000000000FE3C
.text:000000000000FE3C             ; Attributes: bp-based frame

把命令输入到网站后,右边生成了HEX的代码

此时就可以写代码了。

方式一 writeByte

public void patch() {
    UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
    byte[] code = new byte[]{0x00,0x01,0x09,0x4B};
    pointer.write(code);
}

看看结果:

load the vm 728ms
-7

方式二:keystore

使用keystore生成

public void patch2() {
    UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
    Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
    String s = "sub w0, w8, w9";
    byte[] machineCode = keystone.assemble(s).getMachineCode();
    // System.out.println(Integer.toHexString(machineCode[3]));
    pointer.write(machineCode);
}

看看结果:

load the vm 728ms
-7

代码:

package com.example.lesson2;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.arm.backend.unicorn.Unicorn;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneMode;
import unicorn.Arm64Const;


import java.io.File;
import java.util.ArrayList;
import java.util.List;



public class MainActivity {
    // 1. 环境初始化
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Memory memory;
    private final Module module;

    public MainActivity(){
        // 2.1 创建模拟机
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();
        // 2.2 创建memory
        memory = emulator.getMemory();
        // 2.2 创建Android sdk版本. 支持19和23版
        memory.setLibraryResolver(new AndroidResolver(23));
        // 2.3 创建vm
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/lesson2/app-debug.apk"));

        // 载入so文件 且 初始化,返回的说so加载到虚拟机的一个对象
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/lesson2/liblesson2.so"), true);
        module = dalvikModule.getModule();
        vm.callJNI_OnLoad(emulator,module);

    }

    public void callMD5(){
        DvmObject obj = ProxyDvmObject.createObject(vm,this);
        String data = "123456";
        DvmObject dvmObject = obj.callJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",data);
        String result = (String) dvmObject.getValue();
//        System.out.println("call the md5 function result is ->" + result);
    }

    public void callAdd(int a,int b){
        DvmObject obj = ProxyDvmObject.createObject(vm,this);
        int result  = obj.callJniMethodInt(emulator,"add(II)I",a,b);
        System.out.println(result);

    }
    public  void hook(){
        HookZz hook = HookZz.getInstance(emulator);
        hook.replace(module.base + 0xfdd4, new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                System.out.println("onCall no context");

                return super.onCall(emulator, originFunction);
            }

            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println(String.format("X2:%d, X3:%d",context.getIntArg(2),context.getIntArg(3)));
                emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X3,2);
                return super.onCall(emulator, context, originFunction);
            }

            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                System.out.println("postCall");
                // 获取X0的内容
                System.out.println(String.format("X0:%d",emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0)));
                // 修改返回值
                emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,6666);
                super.postCall(emulator, context);
            }
        },true);
    }

    private void call_address() {

        Pointer jniEnv = vm.getJNIEnv();
        DvmObject obj = ProxyDvmObject.createObject(vm,this);
        String data = "dta";
        List<Object> args = new ArrayList<>();
        args.add(jniEnv);
        args.add(vm.addLocalObject(obj));
        args.add(vm.addLocalObject(new StringObject(vm,data)));
        Number[] numbers = module.callFunction(emulator, 0x8F00+ 1, args.toArray());
        String value = (String) vm.getObject(numbers[0].intValue()).getValue();
        System.out.println("[addr] call the md5 function result is ->" + value);

    }
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load the vm " + (System.currentTimeMillis() - start) + "ms");
//        mainActivity.hook();
        mainActivity.patch2();
        mainActivity.callAdd(1,8);
    }

    public void patch() {
        UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
        byte[] code = new byte[]{0x00,0x01,0x09,0x4B};
        pointer.write(code);
    }
    public void patch2() {
        UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0xFE30);
        Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
        String s = "sub w0, w8, w9";
        byte[] machineCode = keystone.assemble(s).getMachineCode();
        // System.out.println(Integer.toHexString(machineCode[3]));
        pointer.write(machineCode);
    }

}

 类似资料: