这篇文章上次修改于 1168 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
说明
重点在于补环境,正确传入参数
过程
首先写一个基本的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住lrand48
和gettimeofday
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);
}
}
已有 2 条评论
大佬, 前边有一篇文章打不开了, getByte算法分析与还原
@小弟弟 typecho主题bug,已修复
https://github.com/xiamuguizhi/typecho-for-Pure/issues/4