Unidbg 入坑

最后更新于 23-3-18

基本操作

安装与基本操作可以看肉丝的教程 1 2 还是比较详细的 ## 主动调用 native 方法

public void callCheckCode() {DvmObject<?> dvmObject = vm.resolveClass( 需要调用的方法所在类).newObject(null); 
    dvmObject.callJniMethodObject(emulator, 调用函数的 signature,......); 
}

HOOK

32 位使用 HookZz,64 位下使用 dobby hook hook 可以进行对函数的替换 注意,R2 和 R3 才是参数,R0 是 env,R1 是 object 同时 unidbg 提供了 plt hook,名为 xhook,当然因为是 plt hook 所以只能 hook 符号表之中的函数 还有个叫 CodeHook 的,但是网上的资料比较少,好像它的功能和 hookzz 也差不了多少,所以这里就不再提及了 ## Console Debugger

先下断点和进行 attach

emulator.attach().addBreakPoint(module.findSymbolByName(funName).getAddress());

常用命令

continue(简写 c):继续执行,到下一个断点处(或运行结束)next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。step(简写 s):单步调试如果有函数调用,则进入函数;与命令 n 不同,n 是不进入调用的函数的
m: 读取内存,例如 mr1 可以读取 r1 寄存器,m0x40000 可以读取内存中的指定位置 

不过感觉不是很熟悉 gdb 的人没太大用处,除非 ida 没法调试否则用 ida 会比较方便 ## 内存修改

要修改内存需要使用 UnidbgPointer 就可以了 首先让程序断下来,然后重写 onHit 方法

@Override
public boolean onHit(Emulator<?> emulator, long address){RegisterContext registerContext = emulator.getContext();
    pointer = registerContext.getPointerArg(0);
}

接下来就可以通过 pointer 进行修改内存的操作了

环境修补

当运行报错的时候需要进行环境修补,举个例子

1. 调用 java 层的方法

public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList)

还有 callStaicObjectMethod,和很多调用情况,具体来说就是看报错的调用,然后重写同返回值和区分是否静态的函数,使用 switch 加 signature 来分别区分每个方法即可,一般来说代码可以直接复制粘贴反编译之后的 java 代码 然后之后,如果遇到某些奇怪的代码,需要进行打印参数进行调试区分。

list.get(vaList.getIntArg(<span class="hljs-number">0</span>))

通过上面的代码就能拿到第一个参数,是对象的话强制转换就可以了,暂时也没发现有什么问题 之后需要 return,一般来说通过 resolve class 就可以了

return vm.resolveClass(android/content/Context).newObject(null);

之所以一些函数看起来依赖于 Android 系统却能够被像上面那样子给 resolve 掉,是因为这是一些常用类,而且被 unidbg 所实现了,因此可以进行正常的返回。但是有些类却不是由 unidbg 实现的,因此可能不可以直接通过上面的方法进行调用 然后返回 String 的时候要使用 StringObject. pid 的话可以用 emulator.getPid() ### 2. 取 java 层的 field

同样需要区分是否静态,取得是什么类型的 field 举个例子 public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) 同样是通过 signature 来进行区分 ## Block Hook

常常被用于 fuction trace,可以看出函数调用链以便 ida 进行接下来的分析 其中需要注意的是 arm 和 thumb 模式的切换问题,还有如果需要 hook 代码块需要大于 20 字节,否则添加 hook 不成功(code from fenfei)

// 保存需要用 frida Hook 的 Block 的地址
public static Map<Integer, Integer> subTraceMap = new HashMap<Integer, Integer>();
// 保存命中的 Block 地址的次数,命中次数太多的就忽略掉。public static Map<Integer, Integer> calcMap = new HashMap<Integer, Integer>();
// 入参是 so 基地址,需要 Trace 代码的开始地址和结束地址
private void traceBlock(final long baseAddr, final long starAddr, final long endAddr) {emulator.getBackend().hook_add_new(new BlockHook() {
    @Override
    public void hookBlock(Backend backend, long address, int size, Object user) {
        // 代码块需要大于 20 个字节,块太小 影响 frida 的 hook
        if (size > 20) {Instruction[] insns = emulator.disassemble(address, 4, 0);
        int iSize = insns[0].getSize();
        int iUseAddr = 0;
        if (iSize == 4) {
           // ARM 模式 4 字节
            iUseAddr = (int) (address - baseAddr);
        } else {
        // THUMB 模式 2 字节,hook 的时候 + 1,iUseAddr = (int) (address - baseAddr) + 1;
        }
         if (calcMap.containsKey(iUseAddr)) {
         // 保存命中次数
        int iValue = calcMap.get(iUseAddr);
       calcMap.put(iUseAddr, iValue + 1);

           // 4 次以上的调用就不显示了, 也不用 frida Trace 了
        if (iValue > 3) {subTraceMap.remove(iUseAddr);
        } else {System.out.println(  + sub_ + Integer.toHexString((int) (address - baseAddr)) +  );
            }
        } else {calcMap.put(iUseAddr, 1);
       subTraceMap.put(iUseAddr, 1);

       System.out.println(+ sub_ + Integer.toHexString((int) (address - baseAddr)) +  );
}
}
}
}, starAddr, endAddr, 0);

}

IO 重定向

有些反调试会通过读取系统文件来实现反调试的效果,因为 unidbg 在无法找到需要的文件的时候会返回空值,并且产生像这样子的一条 info
INFO [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:1309) - openat dirfd=-100, pathname=/proc/xxx/cmdline, oflags=0x0, mode=0

看到这个可以试试直接 hook 修改反调试代码,也可以添加一个 IOResolver 来返回真机上面获取到的内容以达到对抗风控的目的 父类需要实现 IOResolver 接口,并且实现所需要的方法

emulator.getSyscallHandler().addIOResolver(<span class="local-variable">this</span>);

之后重写 resolve 方法,按照 pathname 来解析并且返回需要的值即可 # Trace

据说比 unicorn 好用?

emulator.traceCode(start , end);

这样就能直接 trace 出执行的 code 和寄存器的变化了

String traceFile = /unidbg/unidbg-android/src/test/java/com/tracecode.txt;
PrintStream traceStream;
try {traceStream = new PrintStream(Files.newOutputStream(Paths.get(traceFile)), true);
} catch (IOException e) {throw new RuntimeException(e);
}
emulator.traceCode(module.base, module.base+module.size).setRedirect(traceStream); 

上面这个更加直接一点

call native function(23/6/24 更新)

可以 call 一个需要 java 参数但是没有注册的函数

Number number = module.callFunction(
        emulator,
        0x2414,
        vm.getJNIEnv(),
        vm.addLocalObject(vm.resolveClass("com/yoloho/libcore/util/Crypt")),
        0,
        vm.addLocalObject(new StringObject(vm, "64e6176e45397c5...lKpHjtL0AQ==")),
        85
);
int result = number.intValue();
String v = (String) vm.getObject(result).getValue();
System.out.println(v);

也可以 call 一个 c 函数

public void call_1() {
        int v7 = 0;

        UnidbgPointer v9 = memory.malloc(100, false).getPointer();
        v9.write("64e6176e45397c5989504e76f98ecf2e63b2679euser/login15131255555A4rE0CKaCsUMlKpHjtL0AQ==".getBytes());

        int v8 = 85;

        UnidbgPointer v11 = memory.malloc(100, false).getPointer();

        module.callFunction(
                emulator,
                0x1DA0,
                v7,
                v9,
                v8,
                v11
        );

        System.out.println(v11.getString(0));
        // byte[] bArr = v11.getByteArray(0,100);
        // Inspector.inspect(bArr," 结果 ");
    }

未完待续,以后有用到新的好用的操作再加到这里

正文完
 0
评论(一条评论)
云之君
2023-04-15 17:57:37 回复

你好强

 Windows  Chrome  美国特拉华