这篇文章上次修改于 1145 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
原文点此,这里仅记录一些复现知识点和心得
复现笔记
主要围绕几个要点展开
基于签名调用,nativeGenerate2
是静态注册的方法,基于签名调用传参代码会相对简洁
public void nativeGenerate2() {
System.out.println("start call nativeGenerate2");
DvmClass SecureNative_cls = vm.resolveClass("com/xunmeng/pinduoduo/secure/SecureNative");
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
int context_ptr = vm.addLocalObject(context);
int str1_ptr = vm.addLocalObject(new StringObject(vm, ""));
int str2_ptr = vm.addLocalObject(new StringObject(vm, "Ck5UqWFzreofeABcWvkAAg=="));
int str3_ptr = vm.addLocalObject(new StringObject(vm, "1Hdy4cQW"));
int str4_ptr = vm.addLocalObject(new StringObject(vm, "/storage/emulated/0"));
int str5_ptr = vm.addLocalObject(new StringObject(vm, "version=134&info=g6iUSuzNlWeDi%2FxPng%2FN%2B8ZyQEP%2FnQuHC42hkmSWvCOg79IqfkRW5Lu3jsAh0QwizbgZZSg1FOEI%0Ao4R%2F6pw6XXsv%2FxH%2FzUDXzxJ5UXUYGMSYhF%2BULFIhbWMihyiUWSRA%2FamuTFPOOd17oppNLL6QvlSp%0A9rC2BHcgOMfMaYgq0uuiVDJB4cXNREX10fgGf20jz56kh%2B6ejh1iHIEYffs3OKbtp9M7FqmSpiQY%0AuAHzn7rCorHuZDP8tyvStvBqpdDxO92eeEt%2BprLDqsM1HfA%2BX3ItGURbaT4%2BQSMCOA2dU2JVAv8V%0A4%2Fs%2BAghe%2BdfHFWF6Uy1GuRr%2BSZyh6WI0OLTdc10lh0N0t1cykN9qzyI3ybzIvZcqXXRw9AbyyahJ%0AkN8EluJRR4eTqZiM0DjRM6OOB4YComr6gWm7i8OSjQ41yqFhiA9%2BA0An%2BqSpsZWsXPPIcmQYI8Wi%0AmkZ4zBRKc8CPQ4EB6IzqeDfAz%2F7eWLhfEPr0Mbm1pVG0FKqTSPThBKFWVfcOi2SGWFFZZD2izyUw%0Ax1FJOB%2BPxlb2IMLXNU8AG5dkjxhyV6J8QgkQ4re1bY4vfZ2ti3FGHHT%2Bjv6he65vwzMTS5rX7DKs%0ARhM%2BfVR%2BybfcSz3hg277lYzeqx%2BSVTj7LYaC13XkFwIiBcN%2BD5ME%2BKcVOrlF4V8d1LBnl6RbnxA2%0AIF47j2zKcYFGya8ifgrxP3UoTnoPND5DZLQ%2BFXrzv9%2BvzB68A3N2NsuvgphKPg46gqAg76OvmuO2%0AZ6vw2CCfFx2LjQrEqA%2FzIAuW7clNBHpKg5Po0VfFYEHq6UHtIvIbbI49XVGIH7I7q0Y7UeAKgr9m%0AUXsLGDkwzduSuULTQzIttfxWG2HcRzPARV3LBLXjjcjit6F%2BiBcYC47rCax3MN4SQRz8%2BiVX5Cpr%0AN2GYoZs9zamIRJ3oYi828X8r5HucPyMjPDtybvoLQKDIcQMKWyqBj1IH5DW%2BDbR1tFFlJpCMUHqo%0A1wTCQs7qu9mBHJ5iz9WQTEclLCxgUYh484V1AvcKJmOtawNVL5yUOZJOC4yacTMi0Mc2Mt8ZHqRV%0ASk5JgtflVr94tlrW17bc3%2B9HZsd8tOXKrg7aW8RshsycQZXoqBMqHsGWLWzhVg9gBZsCR5%2F68Q6a%0A1mvizrOLX6V2%2Bv8D5z%2BZ1KOKE%2FnNYajg8Dqltk1abE%2BjL4wELUvkVSO18SvqK1YgBUVZ9NHp2t%2BC%0ALVaNxuFkaEEmpqOKTMzpQXJ23b%2FQR4KLtVC8xx%2BZ0Uy1HPYAYsFejtWwxj9%2B%2BNwvDTiZsC8mQ6IJ%0ADPUk4h6IC9s9yYqmq53qYxL7onc2gpCYS0G37XkXCxxNwDT8pIpCgP%2F2NTb1X8PGBtsil6lwnpew%0APdAduDVsF5axEOTkEH0oDjQqBWvVu%2BQwFNVt43oVYNoyk%2B3R3IPowz9qZ88rjBXg8UNiSFPic%2Fta%0Ad%2B6KMR52cM2BXW%2BRk9QWkhRiHr2s8lWBWmOKOZJBsVl3tUj%2FyEwDZqsXyZmAXM2xfX7SNMqQ9RLp%0AhsXXf%2Fqef0k8IMPvfTgzWuDKXLzIcUpqGISWf3n1G22jRZ8XnpClY5NgBeHz7XRFbdE87sb6tGok%0ALHGY8UTSX30y9WhyoSaxDrWk3L8McnFLQ3FD6SDRXU4zYIx447dLsSqLbMdeM6wnLF9lk3BtO9PF%0AXuZMkw7OA0P1vNSndalOljXJZPgj%2F0yjKXdUWdKDfpo9%2B%2FA7ZPm4ll9TVLrD%2FRFaX5VXs80lo44F%0AOun1b7F3NxGUEpGufe7WgC5gUpWuDusuLPJOuJzRGRLMBsfxKkMHCWGB1AaMY8uK8js6pQomg4WI%0AJZZkigafqU3N0is1%2FSJ6Fwefq%2FVFde8%2B2k%2FDbi%2FduShxbF594RscSFfsJ4mL4Q6AZkUo0SKzOqmi%0AfhlpocB0BYS1JhG68%2FmzIVvq%2FU3Y8%2Bw1L0aGA8D1qO0IVkS6Jsbj79rWemq2tERTJnpPm0WRIxfi%0A8ofM81HNYhGh9eh2b5fnpbalisbHnq546QWAglFo6qtNvFfISQmWXwpuyIP1chRB5am6SIqQNpG8%0ANbPPhbqi1%2FdfPNC%2FXEJkhHBIk%2B8XAOEECY5bBbIuj1%2B5%2B4tayrk%2BD4%2F3V%2FIb%2BReBi%2F8Y68Ha0JXJ%0ACekgkktuwtgfc9i0RvTFfA8hYz7YvP0sSHSHJk0ZVAO%2BMZJiUXX5QepmO7jlQ9ozafZgdArNnqFn%0AkBbW4RkGFs1ttIxXHjaXhH9qOjA341Zid7tAMeDCZWoskpScqMb0b5FA5szQfnQ%2BtOs33HChmdjW%0AFnq4wvCZHLJoPhKl1H1KNsGWyl8M2ihIAM3LMlM8jIQo%2FuFO8WmLorKxd9KeiidnRQ8b4S44tTT5%0A40uSb3fvO%2BGh7FfQFjIDwOEoGvasL71wGluVOL9QoHvrQp6BP9nsu%2FaktdEsTsit%2BanUHhRwQsvN%0AE7rU49S3QuQsqQeB%2B1MRezHD0nqWdqJpaTSCO9u7pfwJUUcLgv7arYjncMyPM7fjHjriKwmRdHMm%0A%2FRJF%2FmURbDZ0nzvJMGRGP8z8JM9T9Taw4cyBanPao0eomIxEdK6UEzArezqp17hGk51ag42dVXQe%0AF9fxEKcy1OYZbgdrJuZtBEVM8aUGRXyFXUW6EZ6EkV3bLI56DOEpPrQtF9Qiz6PfRe9OIeUdAbTG%0AG3buXJUGPEc1ZEQs8Vpmu%2BbE0xkfl1sav7pORpCibmhc2PfPxmzLCtpmSdvGfPsaw44SPM8IceTY%0AivKjxgHy75PwP%2BTRg8%2B56%2FFIVp%2FKJgb4Gqf32iqetgNOkU5nnQWGwM%2FwZajGOAhhdB5Eaa45SvDR%0AN%2Bg9fOOKB3E1kjyBSDnkGRrsf66GBsSM0yzKCqbfeoOxSaEUCOYOTrbPbR%2BAiknNML7P9tX6T0Yu%0A3npozYNHYRuOlVTk17s9A1MGMIYhscPMdHKetU8JCMoPd3M49NcH3qyoD%2FR4a6ixNKR%2B3s10pU%2Fm%0AVhjj8LkEL6DBmBXdDKFK3Yem2f1xAlbEPmvUG3%2BMp%2B9WCz9BbYKF4fuYFBrgWrXpIdvJDlkRC9Kw%0AkErw1oBBrWpCyUXIE0%2FX2H3u8iFWPvxseTd%2BhZarM77VfXjj02DfmzVLWU9DGij%2BL9qzgoGw%2FCj%2B%0ALDGoPUweTrWXmev%2FWpnXCzIj9PnoIqDmS3qa2W7q1nAxTmrEIKjkzy%2F%2B6dcn7jz8Z%2BXqAjtjuGFW%0APi6rT8HAmj4uf%2B%2FdbG1bfztKXHftkEo%2Bxnl37QFEeJ2OJPz5jpCZh9EickNrMyNJTQ3cO0OMuK45%0ADTJnb7Xc0xpxqJ54XxY%3D%0A"));
long num = 0x17b44082347L;
StringObject result = SecureNative_cls.callStaticJniMethodObject(
emulator,
"nativeGenerate2(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)Ljava/lang/String;",
context_ptr, str1_ptr, str2_ptr, str3_ptr, str4_ptr, str5_ptr, num
);
System.out.println("result:" + result);
}
文件访问,虽然可以手动实现IOResolver
的resolve
,在函数内进行文件进行操作
但我更倾向于在setRootDir
设置好根目录的中构造对应的目录和文件
重写的resolve
打印出信息,文件则直接从手机中pull一份,再放到对应的目录中去
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("evil elf want to access:" + pathname);
return null;
}
例如访问cpuinfo
文件
target/rootfs/proc/cpuinfo
自定义SyscallHandler,正常情况下,大家都是像下面这样来初始化模拟器,但跟着复现后,会在补完某个java环境后出现异常
emulator = AndroidEmulatorBuilder.for32Bit().setRootDir(new File("target/rootfs")).setProcessName("com.xunmeng.pinduoduo").build();
如图
WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:469) - handleInterrupt intno=2, NR=190, svcNumber=0x0, PC=RX@0x401c6b5c[libc.so]0x41b5c, LR=RX@0x401b45cb[libc.so]0x2f5cb, syscall=null
可以看到这里的syscall=null
这是因为unidbg还没有实现ARM下编号为190的syscall
具体可以看这个网站
可以知道ARM下编号为190的syscall是vfork
,相关说明如下
- vfork会产生一个新的子进程.但是vfork创建的子进程与父进程共享数据段,而且由vfork创建的
不太理解,但是可以知道它返回值是新子进程的pid,那么可以具体实现如下(FROM 龙哥)
private void vfork(Emulator<?> emulator) {
EditableArm32RegisterContext context = (EditableArm32RegisterContext) emulator.getContext();
int childPid = emulator.getPid() + ThreadLocalRandom.current().nextInt(256);
int r0 = childPid;
System.out.println("vfork pid=" + r0);
context.setR0(r0);
}
也就是返回一个新的pid,是不是要真的新产生一个子进程呢?根据测试这里不用也可以接着走下去
如何完全模拟vfork
呢?不知道TAT
然后现在是197
也就是fstat64
,具体和fstat
差不多,但是unidbg确实也没有具体实现
WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:469) - handleInterrupt intno=2, NR=197, svcNumber=0x0, PC=RX@0x401c61f8[libc.so]0x411f8, LR=RX@0x401bdf3f[libc.so]0x38f3f, syscall=null
可以看到报错具体在BaseAndroidFileIO.java:17
既然差不多,这里似乎在访问su文件?那直接返回0大小好了,
然后就能顺利跑后续了,如果出现memory failed
那么升级一下unidbg即可
另外使用了自定义的SyscallHandler
后,就不能再加上for32Bit
了,否则自定义SyscallHandler无法生效
至此,应该就可以跑通结果了
其他,龙哥增加了一个UserEnvModule
,以及对通过重写SystemPropertyProvider
对一些属性进行设定,这样模拟的参数计算的结果被风控的概率更小
后面fstat64
这里的错误,龙哥是通过修改pipe2
实现修复的
这部分是最开始写的,后续发现java层环境太多要补充了,就没有记录了,请忽略
步骤
so从apk中提取,文件结构如下
之前跟着复现了一遍info2
的调用,这里照着先写个模板
package com.xunmeng.pinduoduo.secure;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
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.memory.Memory;
import java.io.File;
public class SecureNative extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
SecureNative() {
emulator = AndroidEmulatorBuilder
.setRootDir(new File("target/rootfs"))
.setProcessName("com.xunmeng.pinduoduo")
.build();
System.out.println("当前进程PID -> " + emulator.getPid());
emulator.getSyscallHandler().addIOResolver(this);
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/20211023/拼多多官方.apk"));
vm.setVerbose(true);
vm.setJni(this);
DalvikModule dm_libUserEnv = vm.loadLibrary(new File("unidbg-android/src/test/resources/20211023/libUserEnv.so"),true);
dm_libUserEnv.callJNI_OnLoad(emulator);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/20211023/libpdd_secure.so"), true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("evil elf want to access:" + pathname);
return null;
}
public static void main(String[] args) {
SecureNative mSecureNative = new SecureNative();
mSecureNative.nativeGenerate2();
}
public void nativeGenerate2() {
System.out.println("start call nativeGenerate2");
}
}
很好,是要补java环境
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature){
case "android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;":{
return new StringObject(vm, "android_id");
}
}
return super.getStaticObjectField(vm, dvmClass, 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 contextwrapper = vm.resolveClass("android/content/ContextWrapper", context);
return vm.resolveClass("android/app/Application", contextwrapper).newObject(signature);
case "android/content/Context->getContentResolver()Landroid/content/ContentResolver;":
return vm.resolveClass("android/content/ContentResolver").newObject(signature);
case "java/util/UUID->toString()Ljava/lang/String;":
return new StringObject(vm, dvmObject.getValue().toString());
case "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
StringObject str = (StringObject) dvmObject;
StringObject s1 = vaList.getObjectArg(0);
StringObject s2 = vaList.getObjectArg(1);
assert s1 != null;
assert s2 != null;
return new StringObject(vm, str.getValue().replaceAll(s1.getValue(), s2.getValue()));
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
补上(这里是之前info2的代码,直接一步到位),然后下一步
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;":
return new StringObject(vm, "");
case "java/util/UUID->randomUUID()Ljava/util/UUID;":
return vm.resolveClass("java/util/UUID").newObject(UUID.randomUUID());
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
补上,下一步
中间这里出现了报错,暂时先放一边,先把java环境的报错解决
@Override
public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature){
case "com/tencent/mars/xlog/PLog->i(Ljava/lang/String;Ljava/lang/String;)V":
return;
}
super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
}
补上,然后现在没有其他异常了
这里的日志看起来好像没有nativeGenerate2
相关的,不要慌用grep看一下
补java环境ing
N分钟后,补完下面的环境,出现一个异常,不知道怎么处理
龙哥说到
com.github.unidbg.linux.android.dvm.api.SystemService.getObjectType(SystemService.java:44)
一看便知
依葫芦画瓢,现在接着补java环境
然后在getNetworkInterfaces
我觉得返回一个就行了
java.lang.UnsupportedOperationException: java/net/NetworkInterface->getNetworkInterfaces()Ljava/util/Enumeration;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:410)
...
大量java层的环境...
略...
没有评论