Skip to content

Latest commit

 

History

History
816 lines (686 loc) · 35.1 KB

README.md

File metadata and controls

816 lines (686 loc) · 35.1 KB

1.1 基础配置

1.1.1 前置

package com.lession1;

// 导入通用且标准的类库
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;

import java.io.File;

// 继承AbstractJni类
public class oasis extends AbstractJni{
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    oasis() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.oasis").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        // 如果创建Android虚拟机时,选择不传入APK,填入null,那么样本在JNI OnLoad中所做的签名校验,就需要手动补环境校验
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/lession1/lvzhou.apk"));
        // 加载目标SO, 加载so到虚拟内存
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/lession1/liboasiscore.so"), true); 
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        // 设置JNI
        vm.setJni(this); 
        //打印日志
        vm.setVerbose(true);
        // 调用JNI OnLoad,可以看到JNI中做的事情,比如动态注册以及签名校验等。如果没有使用可以注释掉。
        dm.callJNI_OnLoad(emulator); 
    };

    public static void main(String[] args) {
        oasis test = new oasis();
    }
}

1.2 执行目标函数--参数构造

  • 字节数组需要裹上unidbg的包装类,并加到本地变量里,两件事缺一不可。
  • 除了基本类型,比如int,long等,其他的对象类型一律要手动 addLocalObject。
  • vm.getObject(number.intValue()).getValue().toString() 解释:在DalvikVM中有Map存储了jni交互的对象,key是该对象的hash,value是该对象。你这个intValue就是这个对象的hash,通过vm.getObject方法,来取出这个hash对应的Object。

1.2.1 字节数组以及布尔值

    public String getS(){
        // args list
        List<Object> list = new ArrayList<>(10);
        // arg1 env
        list.add(vm.getJNIEnv());
        // arg2 jobject/jclazz 一般用不到,直接填0
        list.add(0);
        // arg3 bytes
        String input = "aid=01A-khBWIm48A079Pz_DMW6PyZR8" +
                "uyTumcCNm4e8awxyC2ANU.&cfrom=28B529501" +
                "0&cuid=5999578300&noncestr=46274W9279Hr1" +
                "X49A5X058z7ZVz024&platform=ANDROID&timestamp" +
                "=1621437643609&ua=Xiaomi-MIX2S__oasis__3.5.8_" +
                "_Android__Android10&version=3.5.8&vid=10190135" +
                "94003&wm=20004_90024";
        byte[] inputByte = input.getBytes(StandardCharsets.UTF_8);
        ByteArray inputByteArray = new ByteArray(vm,inputByte);
        list.add(vm.addLocalObject(inputByteArray));
        // arg4 ,boolean false 填入0
        list.add(0);
        // 参数准备完成
        // call function
        Number number = module.callFunction(emulator, 0xC365, list.toArray())[0];
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }

1.2.2 context以及字符串类型

    public String calculateS(){
        List<Object> list = new ArrayList<>(10);
        // 第一个参数是env
        list.add(vm.getJNIEnv()); 
        // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。
        list.add(0); 
        // 通过虚拟机创建一个context实例,其类型为DvmObject
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        list.add(vm.addLocalObject(context));
        list.add(vm.addLocalObject(new StringObject(vm, "12345")));
        list.add(vm.addLocalObject(new StringObject(vm, "r0ysue")));
		    // 因为代码是thumb模式,别忘了+1
        Number number = module.callFunction(emulator, 0x1E7C + 1, list.toArray())[0];
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    };

1.2.3 treemap

    public void s(){
        TreeMap<String, String> keymap = new TreeMap<String, String>();
        keymap.put("appkey", "1d8b6e7d45233436");
        keymap.put("autoplay_card", "11");
        keymap.put("banner_hash", "10687342131252771522");
        keymap.put("build", "6180500");
        keymap.put("c_locale", "zh_CN");
        keymap.put("channel", "shenma117");
        keymap.put("column", "2");
        keymap.put("device_name", "MIX2S");
        keymap.put("device_type", "0");
        keymap.put("flush", "6");
        keymap.put("ts", "1612693177")
    };
  • 可以照着StringObject重新写一个,也可以不这么麻烦,直接返回一个“空壳”,Native中对treemap做了操作再补对应的方法,这样比较经济实惠。
  • 代码中补齐了treeMap的继承关系:map→AbstractMap→TreeMap,这么做是必要的,否则在有些情况下会报错,具体讨论此处略过。
 public void s(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // 第一个参数是env
        list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。

        TreeMap<String, String> keymap = new TreeMap<String, String>();
        keymap.put("ad_extra", "E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223");
        keymap.put("appkey", "1d8b6e7d45233436");
        keymap.put("autoplay_card", "11");
        keymap.put("banner_hash", "10687342131252771522");
        keymap.put("build", "6180500");
        keymap.put("c_locale", "zh_CN");
        keymap.put("channel", "shenma117");
        keymap.put("column", "2");
        keymap.put("device_name", "MIX2S");
        keymap.put("device_type", "0");
        keymap.put("flush", "6");
        keymap.put("ts", "1612693177");
   
        DvmClass Map = vm.resolveClass("java/util/Map");
        DvmClass AbstractMap = vm.resolveClass("java/util/AbstractMap",Map);
        DvmObject<?> input_map = vm.resolveClass("java/util/TreeMap", AbstractMap).newObject(keymap);
        list.add(vm.addLocalObject(input_map));
        Number number = module.callFunction(emulator, 0x1c97, list.toArray())[0];
        DvmObject result = vm.getObject(number.intValue());
    };

1.2.4 对象数组

public String main203(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(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 __r0ysue".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();
    };

指定一个空对象的对象数组public void main111(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(111);
        DvmObject<?> obj = vm.resolveClass("java/lang/object").newObject(null);
        vm.addLocalObject(obj);
        ArrayObject myobject = new ArrayObject(obj);
        vm.addLocalObject(myobject);
        list.add(vm.addLocalObject(myobject));
        module.callFunction(emulator, 0x5a38d, list.toArray());
    };

1.3 奇技淫巧

1.3.1 PatchCode

报错日志:

JNIEnv->FindClass(android/content/ContextWrapper) was called from RX@0x40002c4f[libutility.so]0x2c4f
JNIEnv->GetMethodID(android/content/ContextWrapper.getPackageManager()Landroid/content/pm/PackageManager;) => 0x53f2c391 was called from RX@0x40002c69[libutility.so]0x2c69
JNIEnv->FindClass(android/content/pm/PackageManager) was called from RX@0x40002c79[libutility.so]0x2c79
[14:04:03 080]  WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:469) - handleInterrupt intno=2, NR=0, svcNumber=0x11e, PC=unidbg@0xfffe0274, LR=RX@0x40002c8d[libutility.so]0x2c8d, syscall=null
com.github.unidbg.arm.backend.BackendException
	at com.github.unidbg.linux.android.dvm.DalvikVM$31.handle(DalvikVM.java:498)
	at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:105)
	at com.github.unidbg.arm.backend.UnicornBackend$10.hook(UnicornBackend.java:323)

sub_1C60(这个函数会返回一个值,如果为真,就继续执行,为假,就返回0) --call-- sub_2C3C(PackageManager之流,联想到签名校验函数)

需要让sub_2C3C函数返回真正的值,不能返回0,否则sub_1C60也返回的是0,直接patch掉对sub_2C3C函数的调用,就是把这儿的函数跳转改成不跳转了。

.text:00001E7C F0 B5                       PUSH    {R4-R7,LR}
.text:00001E7E 11 1C                       MOVS    R1, R2
.text:00001E80 89 B0                       SUB     SP, SP, #0x24
.text:00001E82 1D 1C                       MOVS    R5, R3
.text:00001E84 04 1C                       MOVS    R4, R0
.text:00001E86 FF F7 EB FE                 BL      sub_1C60     PATCH掉对`sub_1C60`函数的调用
.text:00001E8A 05 95                       STR     R5, [SP,#0x38+var_24]
.text:00001E8C 00 28                       CMP     R0, #0
.text:00001E8E 00 D1                       BNE     loc_1E92
.text:00001E90 9D E0                       B       loc_1FCE

正常执行这个函数的话,如果校验没问题返回真,比如1,校验失败返回0。

根据ARM调用约定,入参前四个分别通过R0-R3调用,返回值通过R0返回,所以这儿可以通过“mov r0,1”实现我们的目标——不执行这个函数,并给出正确的返回值。除此之外还有一个幸运的地方在于,这个函数并没有产生一些之后需要使用的值或者中间变量,这让我们不需要管别的寄存器。

此处的机器码是FF F7 EB FE, 查看一下“mov r0,1”的机器码,这里我们使用[ARMConvert](https://armconverter.com/?code=mov r0,1)看一下,除此之外,使用别的工具查看汇编代码也是可以的。

01.a

即把 FF F7 EB FE 替换成 4FF00100 即可。

Unidbg提供了两种方法打Patch,简单的需求可以调用Unicorn对虚拟内存进行修改,如下:

  • 使用机器码,这里的地址不需要+1,Thumb的+1只在运行和Hook时需要考虑,打Patch不需要。

        public void  patchVerify() {
            // arm指令为 `mov r0,1` 转换为机器码
            int patchCode = 0x4FF00100;
            // 不用加1,Thumb的+1只在运行和Hook时需要考虑
            emulator.getMemory().pointer(module.base + 0x1E86).setInt(0, patchCode);
        }
  • 使用指令,先确认有没有找对地方,地址上是不是 FF F7 EB FE,再用Unicorn的好兄弟Keystone 把patch代码“mov r0,1"转成机器码,填进去,校验一下长度是否相等。

        public void patchVerify1() {
            Pointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x1E86);
            assert pointer != null;
            // 获取前4个机器码 FF F7 EB FE
            byte[] code = pointer.getByteArray(0, 4);
            // 判断是否相等,相等才操作
            if (!Arrays.equals(code, new byte[]{(byte) 0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE})) {
                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);
            }
        }

1.3.2 HookZz--参数位置

public void HookMDStringold() {
        // 加载HookZz
        IHookZz hookZz = HookZz.getInstance(emulator);
        // inline wrap导出函数
        hookZz.wrap(module.base + 0x1BD0 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
            @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("result:" + result.getString(0));
            }
        });
    }

1.3.3 HookZ--寄存器

  • 编写对该函数的Hook,首先因为不确定三个参数是指针还是数值,所以先全部做为数值处理,作为long类型看待,防止整数溢出
  • Inspector.inspect其效果类似于frida中hexdump
  • getR0long == emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0)
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 hook寄存器r0
      Inspector.inspect(ctx.getR0Pointer().getByteArray(0, 0x10), "Arg1");
      // 因为是长度,所以用getR1Long
      System.out.println(ctx.getR1Long());
      Inspector.inspect(ctx.getR2Pointer().getByteArray(0, 0x10), "Arg3");
      // push
      ctx.push(ctx.getR2Pointer());
    };

    @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.3.4 主动调用

public void callMd5(){
        List<Object> list = new ArrayList<>(10);
        // arg1
        String input = "r0ysue";
        // 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.3.5 Inline hook

  • 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
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) 				{
         System.out.println("R2:"+ctx.getR2Long());
            }
        })
    }

1.3.6 Trace

  • trace出来的结果几万行应该不存在高度的OLLVM混淆,也说明运算逻辑不会太复杂,否则应该百万行起步
// emulator.traceCode(module.base, module.base + module.size);
// 保存的path
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);

增加寄存器值信息:

  • AbstractARMEmulator.java

    // 添加值显示
    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回来而已

    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.3.7 console debugger

import com.github.unidbg.debugger.Debugger;
// 在类的构造函数中添加
Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base + 0x1ecc + 1);

//下面列举常见的断点命令
1. mr0查看r0所指向的内存块它等同于Frida native hook中的hexdump(this.context.r0)。
2. 按c继续运行
3. 输入bt指令回车bt即backtrace缩写打印调用栈4. 查看函数离开后r0寄存器的值r0此场景为buffera暂停到断点处的函数
   bmr0查看r0寄存器的值此时r0寄存器的地址为0xbffff660c) 输入blr此命令用于在函数返回时设置一个一次性断点
   d然后运行c运行函数它此时在返回处断下来有个问题mr0这时候并不代表入参时的r0了但没关系记住mr0的address即可em0xbffff660 查看0xbffff660
   fc继续执行

1.3.8 traceRead

  • 对内存读写/访问的trace

  • 举例:查看程序对内存地址0xbffff660的值的引用

    此时0xbffff660的值为:BD 39 85 65 07 4D F8 3A 3B 84 E1 4B 4E A0 F0 B5 EA

emulator.traceRead(0xbffff620L, 0xbffff620L+20L);

trace的结果:

### Memory READ at 0xbffff633, data size = 1, data value = 0xea pc=RX@0x40003c3e[libnative-lib.so]0x3c3e lr=RX@0x400fe617[libc.so]0x58617
### Memory READ at 0xbffff62a, data size = 4, data value = 0xa04e4be1 pc=RX@0x40003c56[libnative-lib.so]0x3c56 lr=RX@0x400fe617[libc.so]0x58617

上述结果的意思为,在后续运算中,结果只有五个字节被使用到了分别是:0x3c3e地址使用了最后一个值0xEA 以及 0x3c56地址使用了0xa04e4be1

1.3.9 traceWrite

  • 对结果的trace,此时程序最后结果为:
JNIEnv->SetByteArrayRegion([B@77167fb7, 0, 7, unidbg@0xbffff5f8) was called from RX@0x4000185b[libnative-lib.so]0x185b
JNIEnv->NewObject(class java/lang/String, <init>([B@77167fb7, "UTF-8") => "JCD2D38") was called from RX@0x4000186d[libnative-lib.so]0x186d
emulator.traceWrite(0xbffff5f8L, 0xbffff5f8L+7L);

trace的结果:

### Memory WRITE at 0xbffff5fc, data size = 4, data value = 0x373635 pc=RX@0x40003c9c[libnative-lib.so]0x3c9c lr=RX@0x400fe617[libc.so]0x58617
### Memory WRITE at 0xbffff5f8, data size = 4, data value = 0x34333231 pc=RX@0x40003ca0[libnative-lib.so]0x3ca0 lr=RX@0x400fe617[libc.so]0x58617
### Memory WRITE at 0xbffff5f8, data size = 1, data value = 0x4a pc=RX@0x40003cba[libnative-lib.so]0x3cba lr=RX@0x40003cb1[libnative-lib.so]0x3cb1
### Memory WRITE at 0xbffff5f9, data size = 1, data value = 0x43 pc=RX@0x40003cba[libnative-lib.so]0x3cba lr=RX@0x40003cb1[libnative-lib.so]0x3cb1
### Memory WRITE at 0xbffff5fa, data size = 1, data value = 0x44 pc=RX@0x40003cba[libnative-lib.so]0x3cba lr=RX@0x40003cb1[libnative-lib.so]0x3cb1
### Memory WRITE at 0xbffff5fb, data size = 1, data value = 0x32 pc=RX@0x40003cba[libnative-lib.so]0x3cba lr=RX@0x40003cb1[libnative-lib.so]0x3cb1
### Memory WRITE at 0xbffff5fc, data size = 1, data value = 0x44 pc=RX@0x40003cba[libnative-lib.so]0x3cba lr=RX@0x40003cb1[libnative-lib.so]0x3cb1
### Memory WRITE at 0xbffff5fe, data size = 1, data value = 0x38 pc=RX@0x40003d4e[libnative-lib.so]0x3d4e lr=RX@0x40003d3f[libnative-lib.so]0x3d3f
### Memory WRITE at 0xbffff5fd, data size = 1, data value = 0x33 pc=RX@0x40003d56[libnative-lib.so]0x3d56 lr=RX@0x40003d3f[libnative-lib.so]0x3d3f

1.3.10 补一个完整类

  • 涉及的环境缺失是JAVA环境,具体地说,主要就是com.bilibili.nativelibrary.SignedQuery这个类的问题。

    可以直接JADX中复制这个类,直接拿过来用,Unidbg提供了另外一种模拟Native调用JAVA的方式——缺啥补啥,其原理是JAVA的反射。

  • 主要两点改变,运行后会报错找不到缺少的那个类,补上就可以了。

    • LibBili1 不继承自AbstractJni
    • vm.setJni(this);改成 vm.setDvmClassFactory(new ProxyClassFactory());

1.3.11 打开系统调用日志

在main下面写:

public static void main(String[] args){
  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);
  demo2 test = new demo2();
  System.out.println("call demo2");
  System.out.println(test.call());
}

1.3.12 Unidbg VirtualModule

如果SO的依赖项中有Unidbg不支持的系统SO怎么办 ?

vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/lession8/demo2.apk"));
// 注册libandroid.so虚拟模块
new AndroidModule(emulator, vm).register(memory);

DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/lession8/readassets.so"), true);
module = dm.getModule();

需要注意,一定要在样本SO加载前加载它,道理也很简单,系统SO肯定比用户SO加载的早。VirtualModule并不是一种真正意义上的加载SO,它本质上也是Hook,只不过实现了SO中少数几个函数罢了。

1.4 补环境

1.4.1 构造最基本Context实例

@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
  switch (signature) {
    case "com/izuiyou/common/base/BaseApplication->getAppContext()Landroid/content/Context;":
      // 链接一个android.content.Context的类并通过构造方法创建一个实例
      return vm.resolveClass("android/content/Context").newObject(null);
  }
  return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}

1.4.2 Context实例的getClass方法

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
  switch (signature) {
    case "android/content/Context->getClass()Ljava/lang/Class;":{
      // 此时的dvmObject就是Context实例,.getObjectType方法获取类型
      // Context为主体所以用dvmObject获取
      return dvmObject.getObjectType();
    }
    case "java/util/UUID->toString()Ljava/lang/String;":{
      // 此时的dvmObject就是包裹Java.util.UUID实例
       String uuid = dvmObject.getValue().toString();
       return new StringObject(vm, uuid);
    }
    case "java/lang/Class->getSimpleName()Ljava/lang/String;":{
      // 使用Wallbreaker查看com.izuiyou.common.base.BaseApplication的getClass的getSimpleName
      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.4.3 返回PID

@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);
}

1.4.4 使用java的api返回一个对象

  • Java.util.UUID为Java的工具类,可以直接引用并构造。
  • 为什么不使用context那样构造,因为那个类不是Java的工具类,是和app有关,不可用直接引用。
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
  switch (signature) {
    case "java/util/UUID->randomUUID()Ljava/util/UUID;":{
      return dvmClass.newObject(UUID.randomUUID());
    }
  }
  return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
};

1.4.5 VarArg的使用

  • 主体: dvmObject.getValue是获取treeMap的对象,参数: key的对象用VarArg获取(从app中)不是自己构造。
    @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();
                // getValue得到的就是本身对象,可以各种使用对应的api
                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);
		}
    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            // SignedQuery的r方法自己定义
            case "com/bilibili/nativelibrary/SignedQuery->r(Ljava/util/Map;)Ljava/lang/String;":{
                DvmObject<?> mapObject = varArg.getObjectArg(0);
                // getValue 获取对象本身
                TreeMap<String, String> mymap = (TreeMap<String, String>) mapObject.getValue();
                String result = utils.r(mymap);
                return new StringObject(vm, result);
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

1.4.6 VaList

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "android/content/Context>getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;":
                return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObject(0));
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

1.4.7 文件访问

  • 第一种方式:

    文件访问的情况各种各样,比如从app的某个xml文件中读取key,读取某个资源文件的图片做运算,读取proc/self 目录下的文件反调试等等。当样本做文件访问时,Unidbg重定向到本机的某个位置,进入 src/main/java/com/github/unidbg/file/BaseFileSystem.java,打印一下路径

[15:14:10 318]  INFO [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:1890) - openat dirfd=-100, pathname=/data/app/com.sankuai.meituan-TEfTAIBttUmUzuVbwRK1DQ==/base.apk, oflags=0x20000, mode=0

接下来我们按照要求,在data目录下新建对应文件夹/data/app/com.sankuai.meituan-TEfTAIBttUmUzuVbwRK1DQ==,并把我们的apk复制进去,改名成base.apk,就可以了。

  • 第二种方式:

    1. public class NBridge extends AbstractJni implements IOResolver

    2. emulator.getSyscallHandler().addIOResolver(this)

    3. @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/lession7/mt.apk"), pathname));
              }
              return null;
          }

    或者:

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
      if ("/data/data/com.roysue.readsp/shared_prefs/two.xml".equals(pathname)) {
        return FileResult.success(new ByteArrayFileIO(oflags, pathname,"mytest".getBytes()));
      }
      return null;
    }