# 下载地址

https://github.com/zhkl0228/unidbg

源码下载完成后使用 IDEA 打开即可使用

# 快速使用

套用以下模板,将包名等参数替换成对应的字符串即可快速使用 unidbg

package com.oacia;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
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.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class Myunidbg {
    private final AndroidEmulator emulator;
    private final VM vm;
    private DalvikModule dm;
    private Module module;
    public Myunidbg()
    {
        // 创建模拟器
        emulator = AndroidEmulatorBuilder
                .for64Bit()
            	/*
                hypervisor 引擎可以在搭载了 Apple Silicon 芯片的设备上模拟执行
                KVM 引擎可以在树莓派上模拟执行
                Dynarmic 引擎是为了更快的模拟执行
                Unicorn 是最强大最完善的模拟执行引擎,但它的缺点是慢
                */
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.oacia.test")
                .build();
        // 获取操作内存的接口
        Memory memory = emulator.getMemory();
        // 设置 andorid 系统库版本
        memory.setLibraryResolver(new AndroidResolver(26));
        // 创建虚拟机
        vm = emulator.createDalvikVM();
        // 开启 log
        vm.setVerbose(true);
        // 加载目标库
        dm = vm.loadLibrary(new File("E:\\oacia\\libtprt.so"), false);
        module = dm.getModule();
        // 执行 JNIOnLoad(如果有的话)
        dm.callJNI_OnLoad(emulator);
    }
    public static void main(String[] args) {
    }
}

如果运行时提示缺少某些库,可以在加载目标库前添加如下代码预加载缺少的系统库,系统库的位置位于 /system/lib64/ , 将这些系统库从手机复制到电脑上即可

vm.loadLibrary(new File("f:\\androidlib\\libc.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libm.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libstdc++.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\ld-android.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libdl.so"),false);

如果在 JNI 层中调用了 java 层中的函数,同样会引起 unidbg 引擎报错,此时需要自行补充缺失的 java 层的方法,并重写调用 Java 层函数时用到的 JNI 方法,缺少变量的情况同理

举个例子,在这个 JNI 函数中调用了 java 层中 base64 这个方法

img

所以我们需要在 unidbg 的 Java 文件中补充上对应的方法

public class MainActivitymethod1 extends AbstractJni {
    private static DvmClass MainActivityClass;
    @Override
    /*
    * base64 是 java 层的方法,这里重写 callObjectMethodV 方法:一旦发现调用的是 java 层的 base64 方法,这里就用自己复现的 base64 方法替换
    * */
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        System.out.println("callObjectMethodV->"+signature);
        if(signature.equals("com/example/testjni/MainActivity->base64(Ljava/lang/String;)Ljava/lang/String;")){
            DvmObject dvmobj=vaList.getObjectArg(0);
            String arg= (String) dvmobj.getValue();
            String result=base64(arg);
            return new StringObject(vm,result);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
    
    
    @Override
    /*
    * staticcontent 是 java 层的静态变量;getStaticObjectField,一旦检测到 so 层引用这个变量,那么自己返回这个变量的值
    * */
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        System.out.println("getStaticObjectField->"+signature);
        if(signature.equals("com/example/testjni/MainActivity->staticcontent:Ljava/lang/String;")){
            return new StringObject(vm,"staticcontent");// 源码 public static string staticcontent = "staticcontent"
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }
    
    
    @Override
    /*
    * objcontent 是 java 层的变量;这里重写 getObjectField 方法,一旦检测到 so 层引用这个变量,那么自己返回这个变量的值
    * */
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        System.out.println("getObjectField->"+signature);
        if(signature.equals("com/example/testjni/MainActivity->objcontent:Ljava/lang/String;")){
            return new StringObject(vm,"objcontent");//public string objcontent
        }
        return super.getObjectField(vm, dvmObject, signature);
    }
    /*
    * java 层的方法,这里需要复现,否则不知道怎么执行
    * */
    public String base64(String arg3) {
        String result=Base64.encodeBase64String(arg3.getBytes());
        return result;
    }
    
}

# AndroidEmulator 操作

public void AndroidEmulator_use(){
    // 获取内存操作接口
    Memory memory = emulator.getMemory();
    // 获取进程 pid
    int pid = emulator.getPid();
    // 创建虚拟机
    VM dalvikVM = emulator.createDalvikVM();
    // 创建虚拟机并指定 APK 文件
    VM dalvikVM = emulator.createDalvikVM(new File("E:/oacia/my.apk"));
    // 获取已创建的虚拟机
    VM dalvikVM = emulator.getDalvikVM();
    // 显示当前寄存器状态 可指定寄存器
    emulator.showRegs();
    // 获取后端 CPU
    Backend backend = emulator.getBackend();
    // 获取进程名
    String processName = emulator.getProcessName();
    // 获取寄存器
    RegisterContext context = emulator.getContext();
    //Trace 读内存
    emulator.traceRead(1,0);
    //Trace 写内润
    emulator.traceWrite(1,0);
    //Trace 汇编
    emulator.traceCode(1,0);
    // 是否正在运行
    boolean running = emulator.isRunning();
}

# Memory 操作

public void Memory_use(){
    Memory memory = emulator.getMemory();
    // 指定 Android SDK 版本
    memory.setLibraryResolver(new AndroidResolver(23));
    // 拿到一个指针,指向内存地址,通过该指针可操作内存
    UnidbgPointer pointer = memory.pointer(0x4000000);
    // 获取当前内存映射情况
    Collection<MemoryMap> memoryMap = memory.getMemoryMap();
    // 根据模块名来拿到某个模块
    Module module = memory.findModule("module name");
    // 根据地址拿到某个模块
    Module module = memory.findModuleByAddress(0x40000000);
}

# VM 操作

public void VM_use(){
    // 推荐指定 APK 文件,Unidbg 会自动做许多固定的操作
    VM vm = emulator.createDalvikVM(new File("E:\\oacia\\my.apk"));
    // 是否输出 JNI 运行日志
    vm.setVerbose(true);
    // 加载 SO 模块 参数二设置是否自动调用 init 函数
    DalvikModule dalvikModule = vm.loadLibrary(new File("E:\\oacia\\libc.so"), true);
    // 设置 JNI 交互接口 参数需实现 Jni 接口,推荐使用 this 继承 AbstractJni
    vm.setJni(this);
    // 获取 JNIEnv 指针,可作为参数传递
    Pointer jniEnv = vm.getJNIEnv();
    // 获取 JavaVM 指针,可作为参数传递
    Pointer javaVM = vm.getJavaVM();
    // 调用 JNI_OnLoad 函数
    vm.callJNI_OnLoad(emulator,dalvikModule.getModule());
    // 向 VM 添加全局对象,返回该对象的 hash 值
    int hash = vm.addGlobalObject(dvmObj);
    // 获取虚拟机中的对象,参数为该对象的 hash 值
    DvmObject<?> object = vm.getObject(hash);
}

# 方法调用

# 调用导出函数

// 调用导出函数
public void call_export_function(){
    Module oacia_module = vm.loadLibrary(new File("E:/oacia/liboacia.so"),false).getModule();
    Number result =  oacia_module.callFunction(emulator,"_Z3addii", 1,2);
    System.out.println("_Z3addii result:"+result.intValue());
}

# 调用 JNI 函数

// 调用 JNI 函数
public void call_JNI_function(){
    // 调用 jni 函数,对于动态注册的 jni 函数必须在完成地址的绑定才能调用
    DvmClass MainActivity_dvmclass = vm.resolveClass("com/example/unicorncourse08/MainActivity");// 先把类找到,这里的原理很像反射
    DvmObject<?> resultobj = MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;","helloworld");// 再通过类去调用里面的函数
    System.out.println("resultobj:"+resultobj);
}

# 调用任意函数

// 调用任意函数
public void call_any_function(){
    // 获取 JNIEnv *
    Pointer jniEnv = vm.getJNIEnv();
    // 创建 jobject 对象
    DvmObject<?> thiz = vm.resolveClass("com.oacia.test").newObject(null);
    // 准备入参
    List<Object> args = new ArrayList<>();
    args.add(jniEnv);
    args.add(vm.addLocalObject(thiz));
    args.add(vm.addLocalObject(new StringObject(vm,"XuE")));
    // 根据地址调用
    Number numbers = module.callFunction(emulator, 0x9180 + 1, args.toArray());
    System.out.println(numbers.intValue());
}

# 调用形参是指针类型的函数

// 调用形参是指针类型的函数
// 假设我们需要调用的函数为 void md5 (const uint8_t *initial_msg, size_t initial_len, uint8_t *digest);
// 其中 initial_msg 和 digest 都是指针类型
public void call_argv_point_type_function(){
    String initial = "unidbg";
    int initial_length = initial.length();
    // 开辟一块的空间来存放第一个参数
    MemoryBlock initial_msg = emulator.getMemory().malloc(initial_length+1, false);
    UnidbgPointer initial_msg_ptr = initial_msg.getPointer();
    // 将参数 1 写入
    initial_msg_ptr.write(initial.getBytes());
    // 开辟一块 16 字节的空间来存放第 2 个参数
    MemoryBlock digest = emulator.getMemory().malloc(16, false);
    UnidbgPointer digest_ptr=digest.getPointer();
    // 准备入参
    List<Object> args = new ArrayList<>();
    args.add(initial_msg);
    args.add(initial_length);
    args.add(digest_ptr);
    // 执行
    module.callFunction(emulator, 0x7A8D + 1, args.toArray());
    // 打印结果
    Inspector.inspect(digest_ptr.getByteArray(0, 0x10), "digest");
}

# unidbg hook

Unidbg 在 Android 上支持的 Hook,可以分为两大类

  • Unidbg 内置的第三方 Hook 框架,包括 xHook/Whale/HookZz
  • Unicorn Hook 以及 Unidbg 基于它封装的 Console Debugger

选用哪种 hook 框架,可以参考以下两点

  • 如果以模拟执行为目的,建议使用第三方 Hook 方案,arm32 下 HookZz 的支持较好,arm64 下 Dobby 的支持较好,HookZz/Dobby Hook 不成功时,如果函数是导出函数,使用 xHook,否则使用 Whale。
  • 如果以算法还原为目的,建议使用 Console Debugger 和 Unicorn Hook,并建议不优先使用第三方 Hook 方案。

# 基础知识

# 获取 so 的基址

加载一个 so

// 加载 so 到虚拟内存
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
// 加载好的 so 对应为一个模块
module = dm.getModule();
// 打印 libnative-lib.so 在 Unidbg 虚拟内存中的基地址
System.out.println("baseAddr:"+module.base);

加载多个 so

// 获取某个具体 SO 的句柄
Module yourModule = emulator.getMemory().findModule("yourModuleName");
// 打印其基地址
System.out.println("baseAddr:"+yourModule.base);

如果只主动加载一个 so,其基址恒为 0x40000000 , 这是一个检测 Unidbg 的点,可以在 com/github/unidbg/memory/Memory.java 中做修改

public interface Memory extends IO, Loader, StackMemory {
    long STACK_BASE = 0xc0000000L;
    int STACK_SIZE_OF_PAGE = 256; // 1024k
    // 修改内存映射的起始地址
    long MMAP_BASE = 0x40000000L;
    UnidbgPointer allocateStack(int size);
    UnidbgPointer pointer(long address);
    void setStackPoint(long sp);
}

# 获取导出函数地址

// 加载 so 到虚拟内存
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
// 加载好的 libnative-lib.so 对应为一个模块
module = dm.getModule();
int address = (int) module.findSymbolByName("funcNmae").getAddress();

# 获取非导出函数地址

// 加载 so 到虚拟内存
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
// 加载好的 so 对应为一个模块
module = dm.getModule();
//offset,在 IDA 中查看
int offset = 0x1768;
// 真实地址 = baseAddr + offset
int address = (int) (module.base + offset);

# Console Debugger

# 基础 hook

使用 Console Debugger 下断点

// debug
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress());
  • 运行到对应地址时触发断点,类似于 GDB 调试或者 IDA 调试,时机为目标指令执行前
  • Console Debugger 用于辅助算法分析,快速分析、确认某个函数的功能。并且只能在 Unicorn 引擎下才可以使用

交互命令

c: continue
n: step over
bt: back trace
st hex: search stack
shw hex: search writable heap
shr hex: search readable heap
shx hex: search executable heap
nb: break at next block
s|si: step into
s[decimal]: execute specified amount instruction
s(blx): execute util BLX mnemonic, low performance
m(op) [size]: show memory, default size is 0x70, size may hex or decimal
mr0-mr7, mfp, mip, msp [size]: show memory of specified register
m(address) [size]: show memory of specified address, address must start with 0x
wr0-wr7, wfp, wip, wsp <value>: write specified register
wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x
wx(address) <hex>: write bytes to memory at specified address, address must start with 0x
b(address): add temporarily breakpoint, address must start with 0x, can be module offset
b: add breakpoint of register PC
r: remove breakpoint of register PC
blr: add temporarily breakpoint of register LR
p (assembly): patch assembly at PC address
where: show java stack trace
trace [begin end]: Set trace instructions
traceRead [begin end]: Set trace memory read
traceWrite [begin end]: Set trace memory write
vm: view loaded modules
vbs: view breakpoints
d|dis: show disassemble
d(0x): show disassemble at specify address
stop: stop emulation
run [arg]: run test
cc size: convert asm from 0x400008a0 - 0x400008a0 + size bytes to c function

持久化 hook

onHit 返回 ture 时,断点触发时不会进入交互界面;为 false 时会。当函数被调用了三五百次时,我们不希望它反复停下来,然后不停 “c” 来继续运行。

public void HookByConsoleDebugger(){
    emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            Pointer input = context.getPointerArg(0);
            int length = context.getIntArg(1);
            Pointer buffer = context.getPointerArg(2);
            Inspector.inspect(input.getByteArray(0, length), "base64 input");
            // OnLeave
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    String result = buffer.getString(0);
                    System.out.println("base64 result:"+result);
                    return true;
                }
            });
            return true;
        }
    });
}

# 修改参数

  1. 下断点,运行代码后进入 debugger
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress());
  1. 通过命令修改参数 1 和 2
wx0x40002403 68656c6c6f20776f726c64
>-----------------------------------------------------------------------------<
[14:06:46 165]RX@0x40002403[libhookinunidbg.so]0x2403, md5=5eb63bbbe01eeed093cb22bb8f5acdc3, hex=68656c6c6f20776f726c64
size: 11
0000: 68 65 6C 6C 6F 20 77 6F 72 6C 64                   hello world
^-----------------------------------------------------------------------------^
wr1 11
>>> r1=0xb

Console Debugger 支持下列写操作

wr0-wr7, wfp, wip, wsp <value>: write specified register
wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x
wx(address) <hex>: write bytes to memory at specified address, address must start with 0x

持久化中的修改参数

public void ReplaceArgByConsoleDebugger(){
    emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            String fakeInput = "hello world";
            int length = fakeInput.length();
            // 修改 r1 值为新长度
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
            MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
            fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
            // 修改 r0 为指向新字符串的新指针
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
            Pointer buffer = context.getPointerArg(2);
            // OnLeave
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    String result = buffer.getString(0);
                    System.out.println("base64 result:"+result);
                    return true;
                }
            });
            return true;
        }
    });
}

# 修改返回值

public void ReplaceRetByConsoleDebugger(){
    emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            // OnLeave
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
                    return true;
                }
            });
            return true;
        }
    });
}

# 替换函数

public void ReplaceFuncByConsoleDebugger(){
    emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            System.out.println("替换函数 verifyApkSign");
            RegisterContext registerContext = emulator.getContext();
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, registerContext.getLRPointer().peer);
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
            return true;
        }
    });
}

# Unicorn Hook

public void HookByUnicorn(){
    long start = module.base+0x97C;
    long end = module.base+0x97C+0x17A;
    emulator.getBackend().hook_add_new(new CodeHook() {
        @Override
        public void hook(Backend backend, long address, int size, Object user) {
            RegisterContext registerContext = emulator.getContext();
            if(address == module.base + 0x97C){
                int r0 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R0);
                System.out.println("0x97C 处 r0:"+Integer.toHexString(r0));
            }
            
            //capstone 引擎的使用
            Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM);
                byte[] bytes = emulator.getBackend().mem_read(address, 4);
                Instruction[] disasm = capstone.disasm(bytes, 0);
        }
        @Override
        public void onAttach(Unicorn.UnHook unHook) {
        }
        @Override
        public void detach() {
        }
    }, start, end, null);
}

# xHook

xHook 是爱奇艺开源的 Android PLT hook 框架,优点是挺稳定好用,缺点是不能 Hook Sub_xxx 子函数。

# 基础 hook

public void HookByXhook(){
    IxHook xHook = XHookImpl.getInstance(emulator);
    xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            Pointer input = context.getPointerArg(0);
            int length = context.getIntArg(1);
            Pointer buffer = context.getPointerArg(2);
            Inspector.inspect(input.getByteArray(0, length), "base64 input");
            context.push(buffer);
            return HookStatus.RET(emulator, originFunction);
        }
        @Override
        public void postCall(Emulator<?> emulator, HookContext context) {
            Pointer buffer = context.pop();
            System.out.println("base64 result:"+buffer.getString(0));
        }
    }, true);
    // 使其生效
    xHook.refresh();
}

# 修改参数

public void ReplaceArgByXhook(){
    IxHook xHook = XHookImpl.getInstance(emulator);
    xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            String fakeInput = "hello world";
            int length = fakeInput.length();
            // 修改 r1 值为新长度
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
            MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
            fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
            // 修改 r0 为指向新字符串的新指针
            emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
            Pointer buffer = context.getPointerArg(2);
            context.push(buffer);
            return HookStatus.RET(emulator, originFunction);
        }
        @Override
        public void postCall(Emulator<?> emulator, HookContext context) {
            Pointer buffer = context.pop();
            System.out.println("base64 result:"+buffer.getString(0));
        }
    }, true);
    // 使其生效
    xHook.refresh();
}

# 替换函数

public void ReplaceFuncByHookZz(){
    HookZz hook = HookZz.getInstance(emulator);
    hook.replace(module.findSymbolByName("verifyApkSign").getAddress(), new ReplaceCallback() {
        @Override
        public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
            emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,0);
            return HookStatus.RET(emulator,context.getLR());
        }
    });
}

# HookZz

HookZz 现在叫 Dobby,Unidbg 中是 HookZz 和 Dobby 是两个独立的 Hook 库,因为作者认为 HookZz 在 arm32 上支持较好,Dobby 在 arm64 上支持较好。HookZz 是 inline hook 方案,因此可以 Hook Sub_xxx,缺点是短函数可能出 bug,受限于 inline Hook 原理。

# 基础 hook

public void HookByHookZz(){
    IHookZz hookZz = HookZz.getInstance(emulator); // 加载 HookZz,支持 inline hook
    hookZz.enable_arm_arm64_b_branch(); // 测试 enable_arm_arm64_b_branch,可有可无
    hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() {
        @Override
        public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
            Pointer input = context.getPointerArg(0);
            int length = context.getIntArg(1);
            Pointer buffer = context.getPointerArg(2);
            Inspector.inspect(input.getByteArray(0, length), "base64 input");
            context.push(buffer);
        }
        @Override
        public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
            Pointer buffer = context.pop();
            System.out.println("base64 result:"+buffer.getString(0));
        }
    });
    hookZz.disable_arm_arm64_b_branch();
}

# 修改参数

public void ReplaceArgByHookZz(){
    IHookZz hookZz = HookZz.getInstance(emulator); // 加载 HookZz,支持 inline hook
    hookZz.enable_arm_arm64_b_branch(); // 测试 enable_arm_arm64_b_branch,可有可无
    hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() {
        @Override
        public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
            Pointer input = context.getPointerArg(0);
            String fakeInput = "hello world";
            input.setString(0, fakeInput);
            context.setR1(fakeInput.length());
            Pointer buffer = context.getPointerArg(2);
            context.push(buffer);
        }
        @Override
        public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
            Pointer buffer = context.pop();
            System.out.println("base64 result:"+buffer.getString(0));
        }
    });
    hookZz.disable_arm_arm64_b_branch();
}

# Whale

Whale 是一个跨平台的 Hook 框架,在 Andorid Native Hook 上也是 inline Hook 方案

public void HookByWhale(){
    IWhale whale = Whale.getInstance(emulator);
    whale.inlineHookFunction(module.findSymbolByName("base64_encode"), new ReplaceCallback() {
        Pointer buffer;
        @Override
        public HookStatus onCall(Emulator<?> emulator, long originFunction) {
            RegisterContext context = emulator.getContext();
            Pointer input = context.getPointerArg(0);
            int length = context.getIntArg(1);
            buffer = context.getPointerArg(2);
            Inspector.inspect(input.getByteArray(0, length), "base64 input");
            return HookStatus.RET(emulator, originFunction);
        }
        @Override
        public void postCall(Emulator<?> emulator, HookContext context) {
            System.out.println("base64 result:"+buffer.getString(0));
        }
    }, true);
}

# unidbg 常用脚本

# Patch

  1. 方法一

    public void Patch1(){
        // 00 20 00 bf
        int patchCode = 0xBF002000; // movs r0,0
        emulator.getMemory().pointer(module.base + 0x8CA).setInt(0,patchCode);
    }
  2. 方法二

    public void Patch2(){
        byte[] patchCode = {0x00, 0x20, 0x00, (byte) 0xBF};
        emulator.getBackend().mem_write(module.base + 0x8CA, patchCode);
    }
  3. 方法三

    public void Patch3(){
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
            KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop");
            byte[] patchCode = encoded.getMachineCode();
            emulator.getMemory().pointer(module.base + 0x8CA).write(0, patchCode, 0, patchCode.length);
        }
    }

# 内存检索

public void SearchAndPatch(){
    byte[] patterns = {(byte) 0x80, (byte) 0xb5,0x6f,0x46, (byte) 0x84, (byte) 0xb0,0x03, (byte) 0x90,0x02, (byte) 0x91};
    Collection<Pointer> pointers = searchMemory(module.base, module.base+module.size, patterns);
    if(pointers.size() > 0){
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
            KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop");
            byte[] patchCode = encoded.getMachineCode();
            ((ArrayList<Pointer>) pointers).get(0).write(10, patchCode, 0, patchCode.length);
        }
    }
}
private Collection<Pointer> searchMemory(long start, long end, byte[] data) {
    List<Pointer> pointers = new ArrayList<>();
    for (long i = start, m = end - data.length; i < m; i++) {
        byte[] oneByte = emulator.getBackend().mem_read(i, 1);
        if (data[0] != oneByte[0]) {
            continue;
        }
        if (Arrays.equals(data, emulator.getBackend().mem_read(i, data.length))) {
            pointers.add(UnidbgPointer.pointer(emulator, i));
            i += (data.length - 1);
        }
    }
    return pointers;
}

# 参考资料

  • Unidbg 文档更新 (一)
  • Unidbg 文档慢更 (二)
  • Unidbg Hook 大全
更新于 阅读次数