上一篇文章中我们已经找到了生成shiled参数的Native函数,以及函数的偏移位置,这篇会讲一下如何搭建unidbg环境,如何补缺失环境,最终生成shield参数。
一、搭建unidbg环境
首先把unidbg项目拉下来(项目地址:https://github.com/zhkl0228/unidbg)。
在test中新建一个类,然后把小红书apk和对应的libshiled.so文件放到resources资源目录下。
然后把最基本的unidbg框架搭下,代码如下:
public class ShieldTest extends AbstractJni {
//ARM模拟器
private final AndroidEmulator emulator;
//vm
private final VM vm;
//载入的模块
private final Module module;
public ShieldTest() {
// 创建模拟器实例
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xhs").build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/xhs/xhs_v6.97.0.apk"));
vm.setJni(this);
vm.setVerbose(true);
//加载so
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/xhs/libshield_v6.97.0.so"), true);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
}
}
二、分析Native函数
我们分析下这块代码:
- 首先需要调initializeNative这个初始化函数;
- 第二步调initialize函数,获取cPtr;
- 最终生成shield是调用intercept函数,需传入一个“okhttp3/Interceptor$Chain”,paramLong就是第二步获取的cPtr值。
三、补缺失环境
先把初始化函数initializeNative的代码逻辑写上(偏移位置怎么找可以看我的上篇文章)。
public void initializeNative() {
List<Object> params = new ArrayList<>();
params.add(vm.getJNIEnv());
params.add(0);
module.callFunction(emulator, 0x6c11d, params.toArray());
}
public static void main(String[] args) {
ShieldTest shieldTest = new ShieldTest();
shieldTest.initializeNative();
}
直接运行下,会发现报错了。
这个报错一般就说明要补环境了。
这里先解释下这个:“java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;”
这句话的意思是调用java/nio/charset/Charset这个类下的defaultCharset函数,返回值是Ljava/nio/charset/Charset,unidbg缺少这一块的处理逻辑,我们需要手动补上。
我们可以直接点“com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:388)”这个位置,进去后会看到一个方法,我们需要重写callStaticObjectMethodV方法,然后把Charset对象return回去就好。
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;":
return vm.resolveClass("java/nio/charset/Charset").newObject(Charset.defaultCharset());
}
throw new UnsupportedOperationException(signature);
}
继续运行,发现又报错了
这次是缺少一个versionCode参数,这个参数可以在AndroidManifest.xml文件中找到,我这个版本是6970181,直接返回即可。
@Override
public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
switch (signature) {
case "android/content/pm/PackageInfo->versionCode:I": {
return 6970181;
}
}
return super.getIntField(vm, dvmObject, signature);
}
然后继续运行,报错了就补环境,我就不把所有的都写出来了,比较多,挑几个讲一下。
sDeviceId:
这里是缺少一个sDeviceId参数,我这里是通过IDA分析这块代码,然后打印GetStringUTFChars函数结果找到的。
ps:
这里分析IDA代码有个技巧,像这块代码,v1大概率就是env,可以在v1上右键“set lvar type”,把类型改为JNIEnv*,然后确定,会发现很多函数能显示出来了,看起来会直观很多。
main_hmac:
这个参数可以直接adb连上去,在s.xml文件下找到。
然后依次把三个函数的所有环境补完,就可以运行生成shield了。
下面是三个Native函数的代码逻辑:
public void initializeNative() {
List<Object> params = new ArrayList<>();
params.add(vm.getJNIEnv());
params.add(0);
module.callFunction(emulator, 0x6c11d, params.toArray());
}
public long initialize() {
List<Object> params = new ArrayList<>();
params.add(vm.getJNIEnv());
params.add(0);
params.add(vm.addLocalObject(new StringObject(vm, "main")));
Number number = module.callFunction(emulator, 0x6b801, params.toArray());
return number.longValue();
}
public void intercept(long cPtr) {
List<Object> params = new ArrayList<>();
params.add(vm.getJNIEnv());
params.add(0);
DvmObject<?> chain = vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null);
params.add(vm.addLocalObject(chain));
params.add(cPtr);
Number number = module.callFunction(emulator, 0x6b9e9, params.toArray());
Object result = vm.getObject(number.intValue()).getValue();
}
public static void main(String[] args) {
ShieldTest shieldTest = new ShieldTest();
shieldTest.initializeNative();
long cPtr = shieldTest.initialize();
System.out.println(cPtr);
shieldTest.intercept(cPtr);
}
三、最终效果