这篇文章上次修改于 953 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

说明

重点在于补环境,正确传入参数

过程

首先写一个基本的so加载

package com.tencent.starprotocol;

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.AbstractJni;
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 ByteDataTest extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    ByteDataTest() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.tencent.qqlive").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        // vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/tencent/starprotocol/com.tencent.qqlive_V8.3.95.26016.apk"));
        // 这里没有涉及签名的部分 这样可以减少执行耗时
        vm = emulator.createDalvikVM();
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tencent/starprotocol/libpoxy_star.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志
        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    };

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

输出日志如下

JNIEnv->FindClass(com/tencent/starprotocol/ByteData) was called from RX@0x400273f7[libpoxy_star.so]0x273f7
JNIEnv->RegisterNatives(com/tencent/starprotocol/ByteData, RW@0x400c45e4[libpoxy_star.so]0xc45e4, 2) was called from RX@0x40026009[libpoxy_star.so]0x26009
RegisterNative(com/tencent/starprotocol/ByteData, getByte(Landroid/content/Context;JJJJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)[B, RX@0x4000d9ad[libpoxy_star.so]0xd9ad)
RegisterNative(com/tencent/starprotocol/ByteData, putByte(Landroid/content/Context;JJJJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I, RX@0x40021995[libpoxy_star.so]0x21995)

可以看到getByte是动态注册的,在so的0xd9ad

结合hook的参数和已有信息,追加getByte函数,进行调用

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

public void getByte(){
    // ctx
    DvmClass Application = vm.resolveClass("android/app/Application");
    DvmClass QQLiveApplication = vm.resolveClass("com/tencent/qqlive/ona/base/QQLiveApplication", Application);
    DvmObject context = vm.resolveClass("com.tencent.qqlive.ona.base.QQLiveApplicationWrapper", QQLiveApplication).newObject(null);
    int ctx = vm.addLocalObject(context);
    // obj1
    DvmObject[] kvObj = new DvmObject[9];
    kvObj[0] = new StringObject(vm, "dl_10303");
    vm.addLocalObject(kvObj[0]);
    kvObj[1] = new StringObject(vm, "1");
    vm.addLocalObject(kvObj[1]);
    kvObj[2] = new StringObject(vm, "66666666666666666666666666666666");
    vm.addLocalObject(kvObj[2]);
    kvObj[3] = new StringObject(vm, "getCKey");
    vm.addLocalObject(kvObj[3]);
    kvObj[4] = new StringObject(vm, "888888888888888888888888888888888888");
    vm.addLocalObject(kvObj[4]);
    kvObj[5] = new StringObject(vm, "1626403551515");
    vm.addLocalObject(kvObj[5]);
    kvObj[6] = new StringObject(vm, "");
    vm.addLocalObject(kvObj[6]);
    kvObj[7] = new StringObject(vm, "8.3.95.26016");
    vm.addLocalObject(kvObj[7]);
    kvObj[8] = new StringObject(vm, "com.tencent.qqlive");
    vm.addLocalObject(kvObj[8]);
    // obj4
    String data = "1626403551,n0039ey1mmd,null";
    byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
    ByteArray dataByteArray = new ByteArray(vm, dataByte);
    // 参数准备完成 call function
    Number number = module.callFunction(
            emulator, 0xd9ad, vm.getJNIEnv(), 0,
            ctx,
            1L, 0L, 0L, 0L,
            vm.addLocalObject(new ArrayObject(kvObj)),
            vm.addLocalObject(new StringObject(vm,"")),
            vm.addLocalObject(new StringObject(vm,"66666666666666666666666666666666")),
            vm.addLocalObject(dataByteArray)
    )[0];
    System.out.println(number);
    Object result = vm.getObject(number.intValue()).getValue();
    System.out.println(bytesToHex((byte[]) result));
}

再次运行,出现错误,通过hook确定结果,补上

再次运行,又是报错,同上处理

同上处理

再次错误,也是一样补

结果发现这次出现的异常不太一样了

这种情况通常是继承关系引起的

有关资料可以参考一种对抗AndroidNativeEmu的方法

问题出在jmethodID上面

定位到报错附近,加上更详细的信息打印

可以看到,getFilesDir()结束之后,实际是在调用java.io.File@dd8ba08的方法

但是GetMethodID这里却显示是调用com.tencent.qqlive.ona.base.QQLiveApplicationWrapper.toString()

也就是说,这里得构造一个继承关系,让调用跑通

按理说它们应该没有关联,但是由于jmethodID的原因,才导致的这样

否则即使补上com.tencent.qqlive.ona.base.QQLiveApplicationWrapper.toString()返回,也还是会报错

直白点就是遇到这种错误的时候,看报错的类和调用的类分别是什么

不管它们有没有实际的关系

然后强行继承就完事儿了...

现在回归正常报错了

补上后跑通了

现在还有个问题,结果是变化的,那么要固定下来

去hook住lrand48gettimeofday

IHookZz hookZz = HookZz.getInstance(emulator);
Symbol lrand48 = module.findSymbolByName("lrand48");
hookZz.replace(lrand48, new ReplaceCallback() {
    @Override
    public void postCall(Emulator<?> emulator, HookContext context) {
        EditableArm32RegisterContext ctx = emulator.getContext();
        ctx.setR0(7);
    }
}, true);
Symbol gettimeofday = module.findSymbolByName("gettimeofday");
hookZz.replace(gettimeofday, new ReplaceCallback() {
    UnidbgPointer tv_ptr = null;
    @Override
    public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
        tv_ptr = context.getPointerArg(0);
        return super.onCall(emulator, context, originFunction);
    }
    @Override
    public void postCall(Emulator<?> emulator, HookContext context) {
        EditableArm32RegisterContext ctx = emulator.getContext();
        if(tv_ptr != null){
            ByteBuffer tv = ByteBuffer.allocate(8);
            tv.order(ByteOrder.LITTLE_ENDIAN);
            tv.putInt(0,1626403551);
            tv.putInt(4, 151606);
            byte[] data = tv.array();
            tv_ptr.write(0,data,0,8);
        }
    }
}, true);

最终输出结果与getByte算法分析与还原一致

Tips

  • unidbg调用传参时,如果是long类型,那么数字后面加L即可,否则要自行拆成两个int传参
  • 借助ByteBuffer可以方便的转换数组,另外要注意大小端
  • 两个方法jmethodID相同时,要将对应的类加上继承关系
  • 除了基本类型,比如int,long等,其他的对象类型一律要手动addLocalObject

完整代码

package com.tencent.starprotocol;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.EditableArm32RegisterContext;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
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.ArrayObject;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;

public class ByteDataTest extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

    ByteDataTest() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.tencent.qqlive").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        // vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/tencent/starprotocol/com.tencent.qqlive_V8.3.95.26016.apk"));
        // 这里没有涉及签名的部分 这样可以减少执行耗时
        vm = emulator.createDalvikVM();
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tencent/starprotocol/libpoxy_star.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志

        IHookZz hookZz = HookZz.getInstance(emulator);
        Symbol lrand48 = module.findSymbolByName("lrand48");
        hookZz.replace(lrand48, new ReplaceCallback() {
            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                EditableArm32RegisterContext ctx = emulator.getContext();
                ctx.setR0(7);
            }
        }, true);
        Symbol gettimeofday = module.findSymbolByName("gettimeofday");
        hookZz.replace(gettimeofday, new ReplaceCallback() {
            UnidbgPointer tv_ptr = null;
            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                tv_ptr = context.getPointerArg(0);
                return super.onCall(emulator, context, originFunction);
            }
            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                EditableArm32RegisterContext ctx = emulator.getContext();
                if(tv_ptr != null){
                    ByteBuffer tv = ByteBuffer.allocate(8);
                    tv.order(ByteOrder.LITTLE_ENDIAN);
                    tv.putInt(0,1626403551);
                    tv.putInt(4, 151606);
                    byte[] data = tv.array();
                    tv_ptr.write(0,data,0,8);
                }
            }
        }, true);

        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    };

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

    public void getByte(){
        // ctx
        DvmClass Application = vm.resolveClass("android/app/Application");
        DvmClass QQLiveApplication = vm.resolveClass("com/tencent/qqlive/ona/base/QQLiveApplication", Application);
        DvmObject context = vm.resolveClass("com.tencent.qqlive.ona.base.QQLiveApplicationWrapper", QQLiveApplication).newObject(null);
        int ctx = vm.addLocalObject(context);
        // obj1
        DvmObject[] kvObj = new DvmObject[9];
        kvObj[0] = new StringObject(vm, "dl_10303");
        vm.addLocalObject(kvObj[0]);
        kvObj[1] = new StringObject(vm, "1");
        vm.addLocalObject(kvObj[1]);
        kvObj[2] = new StringObject(vm, "66666666666666666666666666666666");
        vm.addLocalObject(kvObj[2]);
        kvObj[3] = new StringObject(vm, "getCKey");
        vm.addLocalObject(kvObj[3]);
        kvObj[4] = new StringObject(vm, "888888888888888888888888888888888888");
        vm.addLocalObject(kvObj[4]);
        kvObj[5] = new StringObject(vm, "1626403551515");
        vm.addLocalObject(kvObj[5]);
        kvObj[6] = new StringObject(vm, "");
        vm.addLocalObject(kvObj[6]);
        kvObj[7] = new StringObject(vm, "8.3.95.26016");
        vm.addLocalObject(kvObj[7]);
        kvObj[8] = new StringObject(vm, "com.tencent.qqlive");
        vm.addLocalObject(kvObj[8]);
        // obj4
        String data = "1626403551,n0039ey1mmd,null";
        byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
        ByteArray dataByteArray = new ByteArray(vm, dataByte);
        // 参数准备完成 call function
        Number number = module.callFunction(
                emulator, 0xd9ad, vm.getJNIEnv(), 0,
                ctx,
                1L, 0L, 0L, 0L,
                vm.addLocalObject(new ArrayObject(kvObj)),
                vm.addLocalObject(new StringObject(vm,"")),
                vm.addLocalObject(new StringObject(vm,"66666666666666666666666666666666")),
                vm.addLocalObject(dataByteArray)
        )[0];
        System.out.println(number);
        Object result = vm.getObject(number.intValue()).getValue();
        System.out.println(bytesToHex((byte[]) result));
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/tencent/starprotocol/t/s->c(Landroid/content/Context;)Ljava/lang/String;":
                // package name
                return new StringObject(vm, "com.tencent.qqlive");
            case "com/tencent/starprotocol/t/s->d(Landroid/content/Context;)Ljava/lang/String;":
                //  package signatures md5
                return new StringObject(vm, "106409A8F91A970D58BEB2263CE7550F");
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/tencent/starprotocol/t/s->e(Landroid/content/Context;)I":
                //  package versioncode
                return 106332255;
        }
        return super.callStaticIntMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "com.tencent.qqlive.ona.base.QQLiveApplicationWrapper->getFilesDir()Ljava/io/File;":
                return vm.resolveClass("java/io/File", vm.resolveClass("com.tencent.qqlive.ona.base.QQLiveApplicationWrapper")).newObject("/data/user/0/com.tencent.qqlive/files");
            case "com.tencent.qqlive.ona.base.QQLiveApplicationWrapper->toString()Ljava/lang/String;":
                return new StringObject(vm, "/data/user/0/com.tencent.qqlive/files");
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
}