unidbg学习笔记

日常学习unidbg笔记。感谢龙哥、小肩膀两位大咖的指引。本文只做简单记录

emulator 的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 获取内存操作接口
Memory memory1 = emulator.getMemory();
// 获取进程id
int pid = emulator.getPid();
//创建虚拟机
VM dalvikVM = emulator.createDalvikVM();
//创建虚拟机并指定文件
VM dalvikVM1 = emulator.createDalvikVM(new File("ss/ss/apk"));
//获取已经创建的虚拟机
VM dalvikVM2 = 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();

unidbg打印调用栈的API

1
emulator.getUnwinder().unwind();

emulator.getUnwinder().unwind();

Console Debugger

1
2
Debugger attach = emulator.attach();
attach.addBreakPoint(module.base + 0xC365); //断点地址

监控内存读写

1
2
3
4
5
6
7
8
String traceFile = "myMonitorFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}

监控内存读

1
2
emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);

监控内存写

1
2
emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);

trace

如果代码被混淆,其中90%都是无用的代码,直接看汇编代码,分析会很困难。trace可以将程序运行后,实际用到的汇编代码输出到指定文件中,再去分析。

1
2
3
4
5
6
7
8
9
String traceFile = "myTraceCodeFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);

unidbg so层添加initarray初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
 
if(soName.equals("Libshield.so")){

int[] addressList new int[]{xa7f1, 0xc0d5, 0xd839, 0xf9b1, 0x10d8d,

0x2361d,0x2592d,0x2592f,0x26125,0x26bbd,0x27b4d,0x28525,0x28919, 0x29539,0x2a97d,0x2be15,0x2c215,0x2c2fd,0x2e3fd};

for (int address : addressList){

initFunctionList.add(new LinuxInitFunction(Load_base, soName, address)); }

}

日常报错函数处理:

libencrypt.so load dependency libandroid.so failed

1
new AndroidModule(emulator, vm).register(memory);

注意:一定要在样本SO加载前加载它(也就是vm.loadLibrary之前),道理也很简单,系统SO肯定比用户SO加载早

[crash]A/libc: Invalid address 0x40175000 passed to free: value not allocated

此提示是free 函数释放内存的时候出现了问题。最快解决问题的方式就是替换free函数,不进行内存释放
patch free

1
2
3
4
5
6
7
8
9
10
emulator.attach().addBreakPoint(dm.getModule().findSymbolByName("free").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
Arm32RegisterContext registerContext = emulator.getContext();
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, registerContext.getLR());
System.out.println("free函数执行成功,跳过释放函数");
return true;
}
});

[main]W/libc: pthread_create failed: clone failed: Out of memory

pthread_create 的问题。需要新版然后开启多线程解决

1
2
emulator.getSyscallHandler().setEnableThreadDispatcher(true);

context 构造

DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context

或者

DvmClass context = vm.resolveClass("android/content/Context"); DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", context); DvmClass Application = vm.resolveClass("android/app/Application",ContextWrapper); return Application.newObject(signature);

补环境的时候要注意

@Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature){ case "android/app/ActivityThread- >getApplication()Landroid/app/Application;":{ DvmClass context = vm.resolveClass("android/content/Context"); DvmClass Application = vm.resolveClass("android/app/Application",context); return Application.newObject(signature); } case "android/content/Context- >getContentResolver()Landroid/content/ContentResolver;":{ return vm.resolveClass("android/content/ContentResolver").newObject(signature); } } return super.callObjectMethodV(vm, dvmObject, signature, vaList); }

字符串类型如何构造,字节数组如何构造,对象数组如何构造

传入Native的JAVA参数,除了八个基本类型外(byte、char、short、int、long、float、double、boolean),都必须vm.addLocalObject添加到局部引用中去。其他的对象类型一律要手动 addLocalObject。

  • 字符串

list.add(vm.addLocalObject(new StringObject(vm, "12345"))); list.add(vm.addLocalObject(new StringObject(vm, "gladlywang")));

  • 字节数组

ByteArray plainText = new ByteArray(vm, "gladlywang".getBytes(StandardCharsets.UTF_8)); list.add(vm.addLocalObject(plainText));

  • 对象数组
    public static native Object[] main(int i,Object[] objarr);

    参数1是203 参数2是一个对象数组
    9b69f861-e054-4bc4-9daf-d36ae205ed3e (String)
    GET /aggroup/homepage/display __xxxxx(byte数组形式); 2 (int包装类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String main203(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一个参数是env
list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。
list.add(203);
StringObject input2_1 = new StringObject(vm, "9b69f861-e054-4bc4-9daf-d36ae205ed3e");
ByteArray input2_2 = new ByteArray(vm, "GET /aggroup/homepage/display __gladlywang".getBytes(StandardCharsets.UTF_8));
DvmInteger input2_3 = DvmInteger.valueOf(vm, 2);
vm.addLocalObject(input2_1);
vm.addLocalObject(input2_2);
vm.addLocalObject(input2_3);
// 完整的参数2
list.add(vm.addLocalObject(new ArrayObject(input2_1, input2_2, input2_3)));
Number number = module.callFunction(emulator, 0x5a38d, list.toArray())[0];
return vm.getObject(number.intValue()).getValue().toString();
};

参数2的实例对象怎么传?填0,这是偷懒并且有风险的做法,还是建议老老实实初始化类或对象,传hashCode进去,代码如下

1
2
3
4
5
6
7
public DvmClass cNative;
cNative = vm.resolveClass("com/roysue/test623/MainActivity");
DvmObject<?> cnative = cNative.newObject(null);

List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv()); // 第一个参数是env
list.add(cnative.hashCode()); // 第二个参数,实例方法是jobject,静态方法是jclazz,

代码是Thumb模式,调用的时候别忘了+1,Thumb的+1只在运行和Hook时需要考虑,打Patch和下断点不用

1
Number number = module.callFunction(emulator, 0x1E7C + 1, list.toArray())[0];

代码patch的两种方法

  • 直接修改内存指令(Patch)
1
2
3
4
public void patchVerify(){
int patchCode = 0x4FF00100; //mov r0,1 相对应的opcode就是4FF00100
emulator.getMemory().pointer(module.base + 0x1E86).setInt(0,patchCode);
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 通过 Unicorn Hook 代码拦截功能,在目标地址执行时修改其行为。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public void patchVerify1(){
    Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);
    assert pointer != null;
    byte[] code = pointer.getByteArray(0, 4);
    if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE })) { // BL sub_1C60
    throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" + Arrays.toString(code)));
    }
    try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
    KeystoneEncoded encoded = keystone.assemble("mov r0,1");
    byte[] patch = encoded.getMachineCode();
    if (patch.length != code.length) {
    throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" + patch.length));
    }
    pointer.write(0, patch, 0, patch.length);
    }
    };

unidbg各种hook例子

  • hookZz例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public void HookMDStringold(){
    // 加载HookZz
    IHookZz hookZz = HookZz.getInstance(emulator);

    hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap导出函数
    @Override
    // 类似于 frida onEnter
    public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
    // 类似于Frida args[0]
    Pointer input = ctx.getPointerArg(0);
    System.out.println("input:" + input.getString(0));
    };

    @Override
    // 类似于 frida onLeave
    public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
    Pointer result = ctx.getPointerArg(0);
    System.out.println("input:" + result.getString(0));
    }
    });
    }
  • Inline hook例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void hook_315B0(){
    IHookZz hookZz = HookZz.getInstance(emulator);
    hookZz.enable_arm_arm64_b_branch();

    hookZz.instrument(module.base + 0x315B0 + 1, new InstrumentCallback<Arm32RegisterContext>() {
    @Override
    public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
    System.out.println("R2:"+ctx.getR2Long());
    }
    });

    }

  • Unidbg自带API hook

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public void hookByUnicorn(){
    emulator.getBackend().hook_add_new(new CodeHook() {
    @Override
    public void hook(Backend backend, long address, int size, Object user) {
    if (address == module.base + 0x9D24){
    System.out.println("Hook By Unicorn");
    RegisterContext ctx = emulator.getContext();
    Pointer input1 = ctx.getPointerArg(0);
    Pointer input2 = ctx.getPointerArg(1);
    Pointer input3 = ctx.getPointerArg(2);
    // getString的参数i代表index,即input[i:]
    System.out.println("参数1:"+input1.getString(0));
    System.out.println("参数2:"+input2.getString(0));
    System.out.println("参数3:"+input3.getString(0));
    buffer = ctx.getPointerArg(3);
    }
    if(address == (module.base + 0x9d28)){
    Inspector.inspect(buffer.getByteArray(0,0x100), "Unicorn hook EncryptWallEncode");
    }
    }

    @Override
    public void onAttach(Unicorn.UnHook unHook) {
    System.out.println("onAttach");
    }

    @Override
    public void detach() {
    System.out.println("detach");
    }
    },module.base + 0x9D24,module.base + 0x9D28,null);
    }
    Unidbg Console Debugger
    public void HookByConsoleDebugger(){
    Debugger debugger = emulator.attach();
    debugger.addBreakPoint(module.base+0x9d24);
    debugger.addBreakPoint(module.base+0x9d28);
    }

加载so到虚拟内存

DalvikModule dm = vm.loadLibrary(new File(“unidbg-android\src\test\java\com\zuiyou\libnet_crypto.so”), true);

如果在加载so到虚拟内存的步骤中,参数二设为false(即不执行init相关函数),会出现乱码。其实其中的道理并不复杂,甚至可以说很简单——SO样本做了字符串的混淆或加密,以此来对抗分析人员,但字符串总是要解密的,不然怎么用呢?这个解密一般发生在Init array节或者JNI OnLoad中,又或者是该字符串使用前的任何一个时机。

各种补环境

  • 补Context
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
    case "com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;":
    return vm.resolveClass("android/content/Context").newObject(null);
    case "java/util/UUID->randomUUID()Ljava/util/UUID;":
    return dvmClass.newObject(UUID.randomUUID());

    }
    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补一个空类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
    case "android/content/Context->getClass()Ljava/lang/Class;":{
    return dvmObject.getObjectType();
    }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    };

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补具体类名
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
    case "android/content/Context->getClass()Ljava/lang/Class;":{
    return dvmObject.getObjectType();
    }
    case "java/lang/Class->getSimpleName()Ljava/lang/String;":{
    return new StringObject(vm, "AppController");
    }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    };

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补文件路径
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
    case "android/content/Context->getClass()Ljava/lang/Class;":{
    return dvmObject.getObjectType();
    }
    case "java/lang/Class->getSimpleName()Ljava/lang/String;":{
    return new StringObject(vm, "AppController");
    }
    case "android/content/Context->getFilesDir()Ljava/io/File;":
    case "java/lang/String->getAbsolutePath()Ljava/lang/String;": {
    return new StringObject(vm, "/data/user/0/cn.xiaochuankeji.tieba/files");
    }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    };

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 检测是否有调试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    @Override
    public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature){
    case "android/os/Debug->isDebuggerConnected()Z":{
    return false;
    }

    }
    throw new UnsupportedOperationException(signature);
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 使用Unidbg的API返回PID
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    @Override
    public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature){
    case "android/os/Process->myPid()I":{
    return emulator.getPid();
    }

    }
    throw new UnsupportedOperationException(signature);
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 判断map是否为空
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    @Override
    public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    if ("java/util/Map->isEmpty()Z".equals(signature)) {
    TreeMap<String, String> treeMap = (TreeMap<String, String>)dvmObject.getValue();
    return treeMap.isEmpty();
    }

    return super.callBooleanMethod(vm, dvmObject, signature, varArg);
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补map.get
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    switch (signature) {
    case "java/util/Map->get(Ljava/lang/Object;)Ljava/lang/Object;":
    StringObject keyobject = varArg.getObjectArg(0);
    String key = keyobject.getValue();
    TreeMap<String, String> treeMap = (TreeMap<String, String>)dvmObject.getValue();
    String value = treeMap.get(key);
    return new StringObject(vm, value);
    }

    return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补SignedQuery类的init,也就是初始化一个SignedQuery对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
    switch (signature) {
    case "com/bilibili/nativelibrary/SignedQuery-><init>(Ljava/lang/String;Ljava/lang/String;)V":
    StringObject stringObject1 = varArg.getObjectArg(0);
    StringObject stringObject2 = varArg.getObjectArg(1);
    String str1 = stringObject1.getValue();
    String str2 = stringObject2.getValue();
    return vm.resolveClass("com/bilibili/nativelibrary/SignedQuery").newObject(new SignedQuery(str1, str2));
    }

    return super.newObject(vm, dvmClass, signature, varArg);
    }

因为用jadx查看SignedQuery类的代码,有俩成员以及构造函数,所以根据他的成员和构造函数,newObject一个SignedQuery类,搞一个简化版的内部类给它用

1
2
3
4
5
6
7
8
9
public class SignedQuery {
public final String a;
public final String b;

public SignedQuery(String str, String str2) {
this.a = str;
this.b = str2;
}
};

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补文件访问

    当样本做文件访问时,Unidbg重定向到本机的某个位置,进入 src/main/java/com/github/unidbg/file/BaseFileSystem.java
    在构造函数第三行加上System.out.print("virtual path:" + rootDir);打印虚拟路径,接下来我们按照要求,在报错提示目录下新建对应文件夹,并把我们的apk复制进去,改名成报错提示需要的apk。

    1
    2
    3
    4
    public BaseFileSystem(Emulator<T> emulator, File rootDir) {
    this.emulator = emulator;
    this.rootDir = rootDir;
    System.out.print("virtual path:" + rootDir);

创建模拟器实例的时候,加上setRootDir(new File("target/rootfs"),运行代码的时候会自动创建生成target/rootfs目录

1
emulator = AndroidEmulatorBuilder.for32Bit().setRootDir(new File("target/rootfs")).setProcessName("com.xunmeng.pinduoduo").build();

除此之外,也可以通过代码的方式进行操作

我们的类实现文件重定向的接口即可,只需要三个步骤,如下:

  1. 第一步 实现IOResolve
1
2
3
4
5
6
7
8
9
10
11
12
public class NBridge extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;


NBridge(){
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.meituan").build();
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(new File("C:\\Users\\pr0214\\Desktop\\DTA\\unidbg\\versions\\unidbg-2021-5-17\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\lession7\\mt.apk")); // 创建Android虚拟机
  1. 第二步 绑定IO重定向接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    emulator.getSyscallHandler().addIOResolver(this);
    vm.setVerbose(true); // 设置是否打印Jni调用细节
    DalvikModule dm = vm.loadLibrary(new File("C:\\Users\\pr0214\\Desktop\\DTA\\unidbg\\versions\\unidbg-2021-5-17\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\lession7\\libmtguard.so"), true);

    module = dm.getModule(); //

    vm.setJni(this);
    dm.callJNI_OnLoad(emulator);
    }
  2. 第三步

    1
    2
    3
    4
    5
    6
    7
    8
    9

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    if (("/data/app/com.sankuai.meituan-TEfTAIBttUmUzuVbwRK1DQ==/base.apk").equals(pathname)) {
    // 填入想要重定位的文件
    return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android\\src\\test\\java\\com\\lession10\\mt.apk"), pathname));
    }
    return null;
    }

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补文件2
    1
    2
    3
    4
    5
    6
    7
    8

    @Override public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
    return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
    }

    return null;
    }

除此之外也可以像上面一样新建一个文件,传入文件

1
return FileResult.success(new SimpleFileIO(oflags, new File("D:\\unidbg-teach\\unidbg- android\\src\\test\\java\\com\\lession1\\cmdline"), pathname));

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 补签名
1
2
3
4
5
6
7
8
9
10
11
@Override
public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/content/pm/Signature->hashCode()I":
if (dvmObject instanceof Signature) {
Signature sig = (Signature) dvmObject;
return sig.getHashCode();
}
}
return super.callIntMethod(vm, dvmObject, signature, varArg);
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

  • 初始化异常类
1
2
3
4
5
6
7
8
9
@Override public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { 
switch (signature){
case "java/lang/Throwable-><init>()V":{
return vm.resolveClass("java/lang/Throwable").newObject(new Throwable());
}
}

return super.newObject(vm, dvmClass, signature, varArg);
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

打印地址所指向的内存,其效果类似于frida中hexdump,push保存,在后面再pop取出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public void hook65540(){
// 加载HookZz
IHookZz hookZz = HookZz.getInstance(emulator);

hookZz.wrap(module.base + 0x65540 + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap导出函数
@Override
// 类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
// 类似于Frida args[0]
Inspector.inspect(ctx.getR0Pointer().getByteArray(0, 0x10), "Arg1");
System.out.println(ctx.getR1Long());
Inspector.inspect(ctx.getR2Pointer().getByteArray(0, 0x10), "Arg3");
ctx.push(ctx.getR2Pointer()); //push保存
};

@Override
// 类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
// pop 取出
Pointer output = ctx.pop();
Inspector.inspect(output.getByteArray(0, 0x10), "Arg3 after function");
}
});
}

函数调用

  • 地址调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    public void callMd5(){
    List<Object> list = new ArrayList<>(10);

    // arg1
    String input = "gladlywang";
    // malloc memory
    MemoryBlock memoryBlock1 = emulator.getMemory().malloc(16, false);
    // get memory pointer
    UnidbgPointer input_ptr=memoryBlock1.getPointer();
    // write plainText on it
    input_ptr.write(input.getBytes(StandardCharsets.UTF_8));

    // arg2
    int input_length = input.length();

    // arg3 -- buffer
    MemoryBlock memoryBlock2 = emulator.getMemory().malloc(16, false);
    UnidbgPointer output_buffer=memoryBlock2.getPointer();

    // 填入参入
    list.add(input_ptr);
    list.add(input_length);
    list.add(output_buffer);
    // run
    module.callFunction(emulator, 0x65540 + 1, list.toArray());
    // print arg3
    Inspector.inspect(output_buffer.getByteArray(0, 0x10), "output");
    };
  • 符号调用

  • 只有静态函数才能符号调用。动态函数或去除符号表函数是无法进行符号调用*

1
2
3
4
Module module = emulator.getMemory().findModule("libtarget.so"); // 找到目标模块
Symbol symbol = module.findSymbolByName("target_function"); // 根据符号名查找函数
long result = symbol.call(emulator, arg1, arg2); // 调用函数

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

unidbg下断点

  • 普通断点
1
emulator.attach().addBreakPoint(module.base + 0x3161E);
  • 内存写入断点
1
emulator.traceWrite(module.base + 0x3A0C0,module.base + 0x3A0C0);

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

unidbg traceCode

1
2
emulator.traceCode(module.base, module.base + module.size);

  • traceCode保存到文件
1
2
3
4
String traceFile = "unidbg-android\\src\\test\\java\\com\\lession5\\qxstrace.txt";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);
emulator.traceCode(module.base, module.base+module.size).setRedirect(traceStream);

修改unidbg源码,保存关键的寄存器值信息

找到代码文件 src/main/java/com/github/unidbg/arm/AbstractARMEmulator.java
添加值显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void printAssemble(PrintStream out, Capstone.CsInsn[] insns, long address, boolean thumb) {
StringBuilder sb = new StringBuilder();
for (Capstone.CsInsn ins : insns) {
sb.append("### Trace Instruction ");
sb.append(ARM.assembleDetail(this, ins, address, thumb));
// 打印每条汇编指令里参与运算的寄存器的值
Set<Integer> regset = new HashSet<Integer>();

Arm.OpInfo opInfo = (Arm.OpInfo) ins.operands;
for(int i = 0; i<opInfo.op.length; i++){
regset.add(opInfo.op[i].value.reg);
}

String RegChange = ARM.SaveRegs(this, regset);
sb.append(RegChange);
sb.append('\n');
address += ins.size;
}
out.print(sb);
}

src/main/java/com/github/unidbg/arm/ARM.java 中,新建SaveRegs方法,实际上就是showregs的代码,只不过从print改成return回来而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public static String SaveRegs(Emulator<?> emulator, Set<Integer> regs) {
Backend backend = emulator.getBackend();
StringBuilder builder = new StringBuilder();
builder.append(">>>");
Iterator it = regs.iterator();
while(it.hasNext()) {
int reg = (int) it.next();
Number number;
int value;
switch (reg) {
case ArmConst.UC_ARM_REG_R0:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r0=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R1:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r1=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R2:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r2=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R3:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r3=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R4:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r4=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R5:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r5=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R6:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r6=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R7:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r7=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R8:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " r8=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R9: // UC_ARM_REG_SB
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " sb=0x%x", value));
break;
case ArmConst.UC_ARM_REG_R10: // UC_ARM_REG_SL
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " sl=0x%x", value));
break;
case ArmConst.UC_ARM_REG_FP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " fp=0x%x", value));
break;
case ArmConst.UC_ARM_REG_IP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " ip=0x%x", value));
break;
case ArmConst.UC_ARM_REG_SP:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " SP=0x%x", value));
break;
case ArmConst.UC_ARM_REG_LR:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " LR=0x%x", value));
break;
case ArmConst.UC_ARM_REG_PC:
number = backend.reg_read(reg);
value = number.intValue();
builder.append(String.format(Locale.US, " PC=0x%x", value));
break;
}
}
return builder.toString();
}

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

开启所有的日志

1
2
3
4
5
6
7
Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);

所需要的头文件

1
2
3
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

console debugger支持如下指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 : write specified register
wb(address), ws(address), wi(address) : write (byte, short, integer) memory of specified address, address must start with 0x
wx(address) : 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 0x40001ddc - 0x40001ddc + size bytes to c function

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

非法JNI Verision的错误

千万不要先管它,非法JNI Verision的错误往往代表JNI OnLoad的总体执行情况不符合预期,它是JNIOnLoad的最终结果,我们应该在修复完其他错误后看它是否存在。
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

如何固定PID?

修改src/main/java/com/github/unidbg/AbstractEmulator.java 中的如下位置,使PID固定,因为PID不停变动可能会影响后续分析,但这不是必须的操作。
this.pid = Integer.parseInt(pid);
修改为
this.pid = 23638

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

打印so函数获取哪些系统属性,这个对函数流程来说非常重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
System.out.println("lilac systemkey:"+key);
switch (key){
case "ro.build.user":{
return "builder";
}
case "ro.build.display.id":{
return "QKQ1.190828.002 test-keys";
}
case "ro.build.host":{
return "c3-miui-ota-bd134.bj";
}
}
return "";
};
});
memory.addHookListener(systemPropertyHook);