4.5 拼多多

本文主要介绍怎么使用 unidbg 模拟 pdd 的算法调用,本文选取的 pdd 版本为 5.72,读者可以从以下链接进行下载:2021拼多多v5.72.0老旧历史版本安装包官方免费下载_豌豆荚 (wandoujia.com),本文分析的加密参数主要是 anti-token

入口定位

首先我们用 jadx 打开 app 进行逆向分析

先简单搜索一下关键字 "anti-token", 发现只有两个位置,先点第一个进去看看

发现 a2 就是我们需要寻找的参数,而 a2 则是由 com.aimi.android.common.service.d.a().a(a3, Long.valueOf(longValue)); 生成的

点进去看看实现,发现是一个接口,因此我们找到那个方法,再继续搜索看看是在哪里实现的!

搜索到了三个

第二个是刚刚那个接口,第一个和第三个接口点进去看发现全部指向了 SecureNative.deviceInfo2, 因此我们的加密入口应该就是 SecureNative.deviceInfo2, 一路跟踪代码,最终找到了我们的 jni 层了

so 文件定位

so 文件定位我们采用最通用的方式那就是 hookRegistNatives 方法,网上就有现成的脚本,地址为:lasting-yang/frida_hook_libart: Frida hook some jni functions (github.com)

首先我们把代码下载下来,然后启动手机里面的 frida, 最后运行 hook_RegisterNatives ,启动命令如下:

frida -Uf com.xunmeng.pinduoduo -l hook_RegisterNatives.js -o pdd.log

运行完毕后等一会,然后打开 pdd.log ,搜索一下 info2 , 可以看到 so 文件是 libpdd_secure.so,函数签名为:(Landroid/content/Context;J)Ljava/lang/String;

unidbg 模拟调用

info2

1. 模板搭建

首先我们先搭一个架子,基本上所有的项目都可以按照这个架子开始

package com.com.pdd;

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 deviceInfo2 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final String dirPath = "unidbg-android/src/test/resources";
    private final Module module;

    deviceInfo2() {
        // 模拟器,设置进程名为com.xunmeng.pinduoduo
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xunmeng.pinduoduo").build();
        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 23版本的sdk
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(dirPath + "/pdd5.72.apk"));
        // 打印日志
        vm.setVerbose(true);
        // 设置JNI,类必须extends AbstractJni -> 用于补环境
        vm.setJni(this);

        // 加载 so 文件, 因为传入 app, 所以可以直接写 so 文件的名字, 去掉 开头的 lib 和 结尾的 .so
        DalvikModule dm = vm.loadLibrary("pdd_secure", true);
        module = dm.getModule(); // 获取so模块的句柄
        dm.callJNI_OnLoad(emulator); // 调用JNI_OnLoad

    }

    public static void main(String[] args) {
        deviceInfo2 info2 = new deviceInfo2();
    }

}

上述代码我们创建了虚拟机之后,加载了 pdd_secure 这个 so 文件,然后进行实例化,运行发现报错了!

我们根据堆栈可以看到,unidbg 在尝试调用 com/tencent/mars/xlog/PLog 这个类的 i 方法,但是找不到,此时我们可以进 pdd 的源码来查看一下这个 i 方法!从源码中可以知道,这个方法使用另一个 i 方法执行了一些什么操作,然后是没有返回值的(这个从函数签名也可以看出来的),因此我们要补充这个环境,我们也可以直接返回,毕竟这个函数本来就没有返回值,但是如果你想要更完善,你也可以在 unidbg 里面实现这个逻辑,这就是部环境的基本逻辑,那么我们先补充完这个逻辑!

看到堆栈是在运行 com.github.unidbg.linux.android.dvm.AbstractJni.callStaticVoidMethod ,因此我们可以重载一下这个方法,使用以下代码:

    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if (Objects.equals(signature, "com/tencent/mars/xlog/PLog->i(Ljava/lang/String;Ljava/lang/String;)V")) {
            return;
        }
        
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

补完这行代码之后再运行发现没报错了,并且还输出了 info2info3 的偏移地址,info2 的偏移地址为:0xe3d5, 而 info3 的偏移地址为:0xf16d

在执行完上述操作后,接下来我们继续模拟调用 info2,从 jadx 的代码中我们可以看到这个函数接收两个参数,返回一个 string ,我们有两种调用方式,第一种是通过函数签名调用,第二种是通过偏移地址调用,首先我们来讲第一种。

基本的逻辑如下:

  1. 构造入参

  2. 调用函数

  3. 补环境

2. 入参构造

第一个参数是一个 context 对象,我们可以使用以下方法得到

DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);

然后从源码可以看到第二个参数是一个 long 类型,生成方法可以从以下代码中看到,其实就是一个时间戳

所以可以使用以下代码生成:

long timestamp = System.currentTimeMillis();

3. 模拟调用

基于上述步骤,我们先编写一个方法用于模拟调用我们的 jni 函数,从 jadx 的源码中可以看到:info2 这个函数返回的是一个 string,并且是一个 static 方法,因此我们可以自己通过反射找到那个类之后,调用 callStaticJniMethodObject 方法, 这个方法第一个参数是模拟器对象,第二个是函数签名(可以从 jadxsmail 里面看到,也可以直接通过 unidbg 得到),后面的参数就是函数的入参了,调用代码如下:

public void callInfo2WithFnSign() {
        DvmClass DeviceNative = vm.resolveClass("com.xunmeng.pinduoduo.secure.DeviceNative");
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        DvmObject<?> ret = DeviceNative.callStaticJniMethodObject(
                emulator,
                "info2(Landroid/content/Context;J)Ljava/lang/String;",
                vm.addLocalObject(context),
                timestamp
        );

        System.out.println("================================================");
        System.out.println(ret.getValue());
        System.out.println("================================================");


    }

编写完调用代码之后,我们尝试运行,会发现其会继续报错,接下来就开始缺啥补啥,进行补环境了!

android/content/Context->checkSelfPermission(Ljava/lang/String;)I

对于这个我们可以搜索一下这个方法的作用然后再来决定如何补环境,可以参考这篇文章:checkSelfPermission检查权限是否授予 - 简书 (jianshu.com)

文章中介绍到这个方法用于确定是否你授予了指定的权限,如果包含那么说明permission也是未授权所以返回值就是-1,否则的话就是说明该权限已经授权,返回值为0 ,因此我们就直接返回 0 就可以了!

if (Objects.equals(signature, "android/content/Context->checkSelfPermission(Ljava/lang/String;)I")) {
    return 0;
}

4. 环境补充

补环境的基本思路:

  1. 运行脚本 / 函数

  2. 查看报错堆栈,定位报错函数以及 signature 的作用

  3. unidbg 里面模拟实现,重写报错函数,处理 signature

  4. 重复步骤直到不再报错

android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;

基于第三步运行我们可以看到这样一个报错,这个错的意思是在 callObjectMethod 的时候报错了,原因为调用 android/content/ContextgetSystemService 报错,所以我们需要弄清楚 getSystemService 这个方法的作用,并且重写 callObjectMethod 方法!

参考文章:Context | Android Developers,发现其作用为:根据传入的NAME来取得对应的Object, 那么我们就需要看看这个 name 是什么了,我们可以先重载了打一下断点看看!

所有的参数都在 varArg 里面,我们看到这个函数会接受一个参数,因此我们取出第一个就可以了

可以看到 NAMEphone, 对照文章发现,其需要返回一个 TelephonyManager

可以继续点进去看看这个 TelephonyManager 应该怎么生成,发现这个类的位置:android/telephony/TelephonyManager,继承于 java.lang.Object,那我们就可以使用以下代码直接构造出来了

if (Objects.equals(signature, "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;")) {
    String key = (String) varArg.getObjectArg(0).getValue();
    DvmClass TelephonyManager = vm.resolveClass("android/telephony/TelephonyManager", vm.resolveClass("java/lang/Object")); // 构造一个TelephonyManager类
    return TelephonyManager.newObject(key);
}

android/telephony/TelephonyManager->getDeviceId()Ljava/lang/String;

这是在调用 TelephonyManagergetDeviceId 时没有获取到,顾名思义就是返回一个 device id 嘛,那我们自己可以生成一个,文档上也可以看出来应该怎么生成

if (Objects.equals(signature, "android/telephony/TelephonyManager->getDeviceId()Ljava/lang/String;")) {
    return new StringObject(vm, "99000828385596");
}

android/telephony/TelephonyManager->getPhoneType()I

我们这里这返回 PHONE_TYPE_CDMA 就可以,也就是 2

if (Objects.equals(signature, "android/telephony/TelephonyManager->getPhoneType()I")) {
    return 2;
}

android/telephony/TelephonyManager->PHONE_TYPE_GSM:I

上面已经知道了 PHONE_TYPE_GSM 就是 1,直接返回 1

if (Objects.equals(signature, "android/telephony/TelephonyManager->PHONE_TYPE_GSM:I")) {
    return 1;
}

android/telephony/TelephonyManager->PHONE_TYPE_CDMA:I

同理,不做过解释

if (Objects.equals(signature, "android/telephony/TelephonyManager->PHONE_TYPE_CDMA:I")) {
    return 2;
}

android/telephony/TelephonyManager->getDeviceId(I)Ljava/lang/String;

查一下文档知道返回的还是 device id,只不过是多卡,其实也可以写死,当然你也可以根据自己的情况来写

if (Objects.equals(signature, "android/telephony/TelephonyManager->getDeviceId(I)Ljava/lang/String;")) {
	return new StringObject(vm, "99000828385596");
}

android/telephony/TelephonyManager->getSimSerialNumber()Ljava/lang/String;

返回 sim 卡号

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimSerialNumber()Ljava/lang/String;")) {
	return new StringObject(vm, "89861121157316097253");
}	

android/telephony/TelephonyManager->getSimState()I

返回 sim 卡的状态, 那就直接返回 ready 就可以了,查看发现 5 就代表 ready

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimState()I")) {
    return 5;
}

android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;

返回服务提供者的名称,也就是服务商,那不就是运营商嘛

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;")) {
    return new StringObject(vm, "中国联通");
}

android/telephony/TelephonyManager->getSimCountryIso()Ljava/lang/String;

返回 sim 卡提供的城市代码,网上查一下就知道中国的代码是 cn

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimCountryIso()Ljava/lang/String;")) {
	return new StringObject(vm, "cn");
}

android/telephony/TelephonyManager->getSubscriberId()Ljava/lang/String;

返回 imsi, 可以自己查手机的,或者随机生成一下(强封控的时候最好还是使用真实的调试):IMSI生成器 - IMEI.info

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSubscriberId()Ljava/lang/String;")) {
    return new StringObject(vm, "083901100495211772");
}

android/telephony/TelephonyManager->getNetworkType()I

获取网络类型,找到 4G13

if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkType()I")) {
	return 13;
}

android/telephony/TelephonyManager->getSimState()I

返回 ready 也就是 5

if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimState()I")) {
    return 5;
}

android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;

获取 MCC+MNC ,我们的是中国联通的,就是 46001

if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;")) {
	return new StringObject(vm, "46001");
}	

android/telephony/TelephonyManager->getNetworkOperatorName()Ljava/lang/String;

返回运营商名称

if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkOperatorName()Ljava/lang/String;")) {
	return new StringObject(vm, "中国联通");
}

android/telephony/TelephonyManager->getNetworkCountryIso()Ljava/lang/String;

获取运营商国家代码,返回 cn

if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkCountryIso()Ljava/lang/String;")) {
	return new StringObject(vm, "cn");
}

android/telephony/TelephonyManager->getDataState()I

获取当前数据连接状态,这里可以返回 DATA_CONNECTED,也就是 2

if (Objects.equals(signature, "android/telephony/TelephonyManager->getDataState()I")) {
	return 2;
}

android/telephony/TelephonyManager->getDataActivity()I

返回数据连接激活结果,那就返回 DATA_ACTIVITY_INOUT, 也就是 3

if (Objects.equals(signature, "android/telephony/TelephonyManager->getDataActivity()I")) {
            return 3;
        }

android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;

直接返回 android_id

        if (Objects.equals(signature, "android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;")) {
            return new StringObject(vm, "android_id");
        }

android/content/Context->getContentResolver()Landroid/content/ContentResolver;

返回你的 app 实例的 ContentResolver,查询了一下文档发现 ContentResolver 继承于 java.lang.Object,那就自己构造一个

        if (Objects.equals(signature, "android/content/Context->getContentResolver()Landroid/content/ContentResolver;")) {
            return vm.resolveClass("android/content/ContentResolver").newObject(signature);
        }	

android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;

在数据库内查找 name, 打断点看看参数都是什么

发现他是要拿一个 android id,那就先直接返回一个空,如果不行可能要拿自己的设备调试一下,看看这个 android id 是什么再补上

        if (Objects.equals(signature, "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;")) {
            DvmObject<?> key = varArg.getObjectArg(1);
            if (key.getValue().equals("android_id")) {
                return new StringObject(vm, "");
            }
        }

android/os/Debug->isDebuggerConnected()Z

查看是否为调试模式,当然返回 false

        if (Objects.equals(signature, "android/os/Debug->isDebuggerConnected()Z")) {
            return false;
        }

java/lang/Throwable->()V

要实例化 java/lang/Throwable 这个类

        if (Objects.equals(signature, "java/lang/Throwable-><init>()V")) {
            return vm.resolveClass("java/lang/Throwable").newObject(null);
        }

java/lang/Throwable->getStackTrace()[Ljava/lang/StackTraceElement;

获取调用堆栈,那么我们需要构造一个堆栈了,这个比较麻烦,不过我们也可以使用 frida 打印一下,或者借助 jnitrace 输出堆栈来补充 (这边就直接照搬磊哥的代码了,大概知道这个思路就可以)

           StackTraceElement[] stackTraceElements = {
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.DeviceNative", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.SecureNative", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.s", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.a", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.j", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.k", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.PQuicInterceptor", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.config.i$c", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.basekit.http.manager.b$4", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.o", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.e", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.b", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.a", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.m", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.c", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.j", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.RealCall", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.UnityCallFactory$a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b.a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.a.a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b.b", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.a.a", "", "", 0),
                    new StackTraceElement("1", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.g$a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b", "", "", 0),
                    new StackTraceElement("java.util.concurrent.ThreadPoolExecutor", "", "", 0),
                    new StackTraceElement("java.util.concurrent.ThreadPoolExecutor$Worker", "", "", 0),
                    new StackTraceElement("java.lang.Thread", "", "", 0),
            };
            DvmObject[] objects = new DvmObject[stackTraceElements.length];
            for (int i = 0; i < objects.length; i++) {
                objects[i] = vm.resolveClass("java/lang/StackTraceElement").newObject(stackTraceElements[i]);
            }
            return new ArrayObject(objects);
        }

java/lang/StackTraceElement->getClassName()Ljava/lang/String;

获取堆栈 element 的类名

        if (Objects.equals(signature, "java/lang/StackTraceElement->getClassName()Ljava/lang/String;")) {
            StackTraceElement stackTraceElement = (StackTraceElement) dvmObject.getValue();
            String className = stackTraceElement.getClassName();
            return new StringObject(vm, className);
        }

java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

运行 string 对象的 replaceAll 方法

        if (Objects.equals(signature, "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
            String mystring = (String) dvmObject.getValue();
            String arg1 = (String) vaList.getObjectArg(0).getValue();
            String arg2 = (String) vaList.getObjectArg(1).getValue();
            String ret = mystring.replaceAll(arg1, arg2);
            return new StringObject(vm, ret);
        }

java/io/ByteArrayOutputStream->()V

        if (Objects.equals(signature, "java/io/ByteArrayOutputStream-><init>()V")) {
            return vm.resolveClass("java/io/ByteArrayOutputStream").newObject(null);
        }

java/util/zip/GZIPOutputStream->(Ljava/io/OutputStream;)V

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V")) {
            return vm.resolveClass("java/util/zip/GZIPOutputStream").newObject(varArg.getObjectArg(0).getValue());
        }

java/util/zip/GZIPOutputStream->write([B)V

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->write([B)V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            byte[] arg = (byte[]) varArg.getObjectArg(0).getValue();

            try {
                gZIPOutputStream.write(arg);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

补完之后运行发现报错了

这里面那个 GZIPOutputStream 是空的,所以可能是上面补错了,我们将上面和 GZIPOutputStream 有关的环境打断点调试一下,发现在 java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V 的结果就是 null ,而 java/io/ByteArrayOutputStream-><init>()V 的结果也是 null, 因此就是这两步补错了!

首先是 java/io/ByteArrayOutputStream-><init>()V,我们修改为自己实例化一个 ByteArrayOutputStream, 代码如下:

        if (Objects.equals(signature, "java/io/ByteArrayOutputStream-><init>()V")) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            return vm.resolveClass("java/io/ByteArrayOutputStream").newObject(byteArrayOutputStream);
        }

发现问题了,那么继续运行,暂时不修改第二步,因为第二步是有值了,运行发现又报错了!

报错提示无法将 java.io.ByteArrayOutputStream 强制转换为 java.util.zip.GZIPOutputStream,这就是我们第二步的问题,那么既然无法强转,我们就要自己构造了!因此第二步的代码应该修改为:

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V")) {
            OutputStream outputStream = (OutputStream) varArg.getObjectArg(0).getValue();
            DvmClass class_ = vm.resolveClass("java/util/zip/GZIPOutputStream");

            try {
                return class_.newObject(new GZIPOutputStream(outputStream));

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

再次运行发现没问题了!

java/util/zip/GZIPOutputStream->finish()V

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->finish()V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            try {
                gZIPOutputStream.finish();
                return;

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

java/io/ByteArrayOutputStream->toByteArray()[B

        if (Objects.equals(signature, "java/io/ByteArrayOutputStream->toByteArray()[B")) {
            ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) dvmObject.getValue();
            byte[] ret = byteArrayOutputStream.toByteArray();
            return new ByteArray(vm, ret);
        }

java/util/zip/GZIPOutputStream->close()V

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->close()V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            try {
                gZIPOutputStream.close();
                return;

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

补完这个之后,发现结果就可以执行出来了!

5. 补充说明

上面的调用方式是通过函数签名调用的,这边在演示一下如何使用函数偏移地址来进行调用,主要使用的是 module.callFunction 这个方法,第一个参数是模拟器,第二个参数是函数偏移,后面的就是入参了,但是需要注意的是,这边的入参固定的第一个是 jniEnv, 可以通过 vm.getJNIEnv() 得到,第二个是 jobject/jclazz,一般用不到直接填 0,不用,后面的就是真正的入参了,代码如下:

    public void callInfo2WithFnOffset() {

        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        Number number = module.callFunction(
                emulator,
                0xe3d5,
                vm.getJNIEnv(),
                0,
                vm.addLocalObject(context),
                timestamp
        );
        String result = vm.getObject(number.intValue()).getValue().toString();

        System.out.println("================================================");
        System.out.println(result);
        System.out.println("================================================");


    }

运行一下发现也是可以运行的(其实底层应该就是调的他)!

最终代码:

package com.com.pdd;

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.ArrayObject;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;

public class info2 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final String dirPath = "unidbg-android/src/test/resources";
    private final Module module;

    info2() {
        // 模拟器,设置进程名为com.xunmeng.pinduoduo
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xunmeng.pinduoduo").build();
        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 23版本的sdk
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(dirPath + "/pdd5.72.apk"));
        // 打印日志
        vm.setVerbose(true);
        // 设置JNI,类必须extends AbstractJni -> 用于补环境
        vm.setJni(this);
        // 加载 so 文件, 因为传入 app, 所以可以直接写 so 文件的名字, 去掉 开头的 lib 和 结尾的 .so
        DalvikModule dm = vm.loadLibrary("pdd_secure", true);
        module = dm.getModule(); // 获取so模块的句柄
        dm.callJNI_OnLoad(emulator); // 调用JNI_OnLoad

    }

    public static void main(String[] args) {
//        Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.ALL);
        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);
        info2 info2 = new info2();
        info2.callInfo2WithFnSign();
    }

    public void callInfo2WithFnSign() {
        DvmClass DeviceNative = vm.resolveClass("com.xunmeng.pinduoduo.secure.DeviceNative");
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        DvmObject<?> ret = DeviceNative.callStaticJniMethodObject(
                emulator,
                "info2(Landroid/content/Context;J)Ljava/lang/String;",
                vm.addLocalObject(context),
                timestamp
        );

        System.out.println("================================================");
        System.out.println(ret.getValue());
        System.out.println("================================================");


    }

    public void callInfo2WithFnOffset() {

        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        Number number = module.callFunction(
                emulator,
                0xe3d5,
                vm.getJNIEnv(),
                0,
                vm.addLocalObject(context),
                timestamp
        );
        String result = vm.getObject(number.intValue()).getValue().toString();

        System.out.println("================================================");
        System.out.println(result);
        System.out.println("================================================");


    }

    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if (Objects.equals(signature, "com/tencent/mars/xlog/PLog->i(Ljava/lang/String;Ljava/lang/String;)V")) {
            return;
        }

        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {

        if (Objects.equals(signature, "android/content/Context->checkSelfPermission(Ljava/lang/String;)I")) {
            return 0;
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getPhoneType()I")) {
            return 2;
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkType()I")) {
            return 13;
        }
        if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimState()I")) {
            return 5;
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getDataState()I")) {
            return 2;
        }
        if (Objects.equals(signature, "android/telephony/TelephonyManager->getDataActivity()I")) {
            return 3;
        }


        return super.callIntMethod(vm, dvmObject, signature, varArg);

    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if (Objects.equals(signature, "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;")) {
            String key = (String) varArg.getObjectArg(0).getValue();
            DvmClass TelephonyManager = vm.resolveClass("android/telephony/TelephonyManager", vm.resolveClass("java/lang/Object")); // 构造一个TelephonyManager类
            return TelephonyManager.newObject(key);
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getDeviceId()Ljava/lang/String;")) {
            return new StringObject(vm, "99000828385596");
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getDeviceId(I)Ljava/lang/String;")) {
            return new StringObject(vm, "99000828385596");
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimSerialNumber()Ljava/lang/String;")) {
            return new StringObject(vm, "89861121157316097253");
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;")) {
            return new StringObject(vm, "中国联通");
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getSimCountryIso()Ljava/lang/String;")) {
            return new StringObject(vm, "cn");
        }
        if (Objects.equals(signature, "android/telephony/TelephonyManager->getSubscriberId()Ljava/lang/String;")) {
            return new StringObject(vm, "083901100495211772");
        }
        if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;")) {
            return new StringObject(vm, "46001");
        }
        if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkOperatorName()Ljava/lang/String;")) {
            return new StringObject(vm, "中国联通");
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->getNetworkCountryIso()Ljava/lang/String;")) {
            return new StringObject(vm, "cn");
        }

        if (Objects.equals(signature, "android/content/Context->getContentResolver()Landroid/content/ContentResolver;")) {
            return vm.resolveClass("android/content/ContentResolver").newObject(signature);
        }
        if (Objects.equals(signature, "java/lang/Throwable->getStackTrace()[Ljava/lang/StackTraceElement;")) {
            StackTraceElement[] stackTraceElements = {
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.DeviceNative", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.SecureNative", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.secure.s", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.a", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.j", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.k", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.PQuicInterceptor", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.config.i$c", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.basekit.http.manager.b$4", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.o", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.e", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.b", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.a", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.m", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.c", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.internal.interceptor.j", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.internal.b.g", "", "", 0),
                    new StackTraceElement("okhttp3.RealCall", "", "", 0),
                    new StackTraceElement("com.aimi.android.common.http.unity.UnityCallFactory$a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b.a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.a.a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b.b", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.a.a", "", "", 0),
                    new StackTraceElement("1", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.g", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.g$a", "", "", 0),
                    new StackTraceElement("com.xunmeng.pinduoduo.arch.quickcall.a.b", "", "", 0),
                    new StackTraceElement("java.util.concurrent.ThreadPoolExecutor", "", "", 0),
                    new StackTraceElement("java.util.concurrent.ThreadPoolExecutor$Worker", "", "", 0),
                    new StackTraceElement("java.lang.Thread", "", "", 0),
            };
            DvmObject[] objects = new DvmObject[stackTraceElements.length];
            for (int i = 0; i < objects.length; i++) {
                objects[i] = vm.resolveClass("java/lang/StackTraceElement").newObject(stackTraceElements[i]);
            }
            return new ArrayObject(objects);
        }

        if (Objects.equals(signature, "java/lang/StackTraceElement->getClassName()Ljava/lang/String;")) {
            StackTraceElement stackTraceElement = (StackTraceElement) dvmObject.getValue();
            String className = stackTraceElement.getClassName();
            return new StringObject(vm, className);
        }

        if (Objects.equals(signature, "java/io/ByteArrayOutputStream->toByteArray()[B")) {
            ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) dvmObject.getValue();
            byte[] ret = byteArrayOutputStream.toByteArray();
            return new ByteArray(vm, ret);
        }


        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }


    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        if (Objects.equals(signature, "android/telephony/TelephonyManager->PHONE_TYPE_GSM:I")) {
            return 1;
        }

        if (Objects.equals(signature, "android/telephony/TelephonyManager->PHONE_TYPE_CDMA:I")) {
            return 2;
        }

        return super.getStaticIntField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        if (Objects.equals(signature, "android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;")) {
            return new StringObject(vm, "android_id");
        }

        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if (Objects.equals(signature, "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;")) {
            DvmObject<?> key = varArg.getObjectArg(1);
            if (key.getValue().equals("android_id")) {
                return new StringObject(vm, "");
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public boolean callStaticBooleanMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if (Objects.equals(signature, "android/os/Debug->isDebuggerConnected()Z")) {
            return false;
        }
        return super.callStaticBooleanMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if (Objects.equals(signature, "java/lang/Throwable-><init>()V")) {
            return vm.resolveClass("java/lang/Throwable").newObject(null);
        }
        if (Objects.equals(signature, "java/io/ByteArrayOutputStream-><init>()V")) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            return vm.resolveClass("java/io/ByteArrayOutputStream").newObject(byteArrayOutputStream);
        }
        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V")) {
            OutputStream outputStream = (OutputStream) varArg.getObjectArg(0).getValue();
            DvmClass class_ = vm.resolveClass("java/util/zip/GZIPOutputStream");

            try {
                return class_.newObject(new GZIPOutputStream(outputStream));

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return super.newObject(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (Objects.equals(signature, "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
            String mystring = (String) dvmObject.getValue();
            String arg1 = (String) vaList.getObjectArg(0).getValue();
            String arg2 = (String) vaList.getObjectArg(1).getValue();
            String ret = mystring.replaceAll(arg1, arg2);
            return new StringObject(vm, ret);
        }

        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->write([B)V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            byte[] arg = (byte[]) varArg.getObjectArg(0).getValue();

            try {
                gZIPOutputStream.write(arg);
                return;

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->finish()V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            try {
                gZIPOutputStream.finish();
                return;

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        if (Objects.equals(signature, "java/util/zip/GZIPOutputStream->close()V")) {
            GZIPOutputStream gZIPOutputStream = (GZIPOutputStream) dvmObject.getValue();
            try {
                gZIPOutputStream.close();
                return;

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }

}



info3

1. 模板搭建

按照上面的步骤继续补一下 info3,先搭建一个通用的模板

package com.com.pdd;

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.memory.Memory;

import java.io.File;

public class info3 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final String dirPath = "unidbg-android/src/test/resources";
    private final Module module;

    info3() {
        // 模拟器,设置进程名为com.xunmeng.pinduoduo
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xunmeng.pinduoduo").build();
        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 23版本的sdk
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(dirPath + "/pdd5.72.apk"));
        // 打印日志
        vm.setVerbose(true);
        // 设置JNI,类必须extends AbstractJni -> 用于补环境
        vm.setJni(this);
        // 加载 so 文件, 因为传入 app, 所以可以直接写 so 文件的名字, 去掉 开头的 lib 和 结尾的 .so
        DalvikModule dm = vm.loadLibrary("pdd_secure", true);
        module = dm.getModule(); // 获取so模块的句柄
        dm.callJNI_OnLoad(emulator); // 调用JNI_OnLoad

    }

    public static void main(String[] args) {
        info3 info3 = new info3();

    }

}

上述代码创建后运行发现直接报错了

这个错误我们在补 info2 的时候就已经完成了,因此直接拷贝过来就可以了,补完之后发现没有报错了,那么我们的模板就创建完毕了!

2. 入参构造

分析一下源码,相比 info2info3 多了一个 str, 我们可以 hook 以下看看这个 str 怎么构造,我们可以直接先 hook 以下那个函数

发现 str 就是 E11C5thK, 至于是常量还是会变化,这里不做分析,这边直接就把 str 写死为 E11C5thK,前面两个参数和构造 info2 的时候是一样的!

3. 模拟调用

不做过多的解释,直接编写代码

    public void callInfo3WithFnSign() {
        DvmClass DeviceNative = vm.resolveClass("com.xunmeng.pinduoduo.secure.DeviceNative");
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        DvmObject<?> ret = DeviceNative.callStaticJniMethodObject(
                emulator,
                "info3(Landroid/content/Context;JLjava/lang/String;)Ljava/lang/String;",
                vm.addLocalObject(context),
                timestamp,
                "E11C5thK"
        );

        System.out.println("================================================");
        System.out.println(ret.getValue());
        System.out.println("================================================");


    }

4. 环境补充

前期重复环境

有一些已经补过了环境的这边就不重复介绍了,只简单列出补了的环境

`android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;`

`android/content/Context->getContentResolver()Landroid/content/ContentResolver;`

`android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;`

`android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;`

`java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;`

java/lang/String->hashCode()I

        if (Objects.equals(signature, "java/lang/String->hashCode()I")) {
            String str = (String) dvmObject.getValue();
            return str.hashCode();

        }

com.github.unidbg.linux.file.PipedWriteFileIO

这个是因为在使用 popen 的时候报错了,至于这个怎么补,通过查阅资料找到了这个 issue ,地址为:使用popen执行命令行出错 · Issue #84 · zhkl0228/unidbg (github.com)

从该 issue 中可以看到,我们需要自己去处理那些 sysCall,所以步骤为在生成模拟器的时候需要去修改我们的 AndroidEmulatorBuilder,可以通过以下代码:

     AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
            public AndroidEmulator build() {
                return new AndroidARMEmulator(processName, rootDir,
                        backendFactories) {
                    @Override
                    protected UnixSyscallHandler<AndroidFileIO>
                    createSyscallHandler(SvcMemory svcMemory) {
                        // 这里面使用我们自己的 ARM32SyscallHandler
                        return new MyARM32SyscallHandler(svcMemory);
                    }
                };
            }
        };

然后再使用这个 builder 去创建模拟器,而 MyARM32SyscallHandler 就需要我们自己去实现了再导入进来使用!这边提供一下 SyscallHandler 的模板

package com.pdd;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.context.EditableArm32RegisterContext;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.DumpFileIO;
import com.github.unidbg.memory.SvcMemory;
import com.sun.jna.Pointer;

import java.util.concurrent.ThreadLocalRandom;

public class pddARM32SyscallHandler extends ARM32SyscallHandler {

    public pddARM32SyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
    }

    @Override
    protected boolean handleUnknownSyscall(Emulator emulator, int NR) {
        System.out.println("handleUnknownSyscall NR=" + NR);

        switch (NR) {
            case 114:
                wait4(emulator);
                return true;
            case 190:
                vfork(emulator);
                return true;
            case 359:
                pipe2(emulator);
                return true;
        }

        return super.handleUnknownSyscall(emulator, NR);
    }

    private void wait4(Emulator emulator) {
        EditableArm32RegisterContext context = (EditableArm32RegisterContext) emulator.getContext();
        int pid = context.getR0Int();
        Pointer wstatus = context.getR1Pointer();
        int options = context.getR2Int();
        Pointer rusage = context.getR3Pointer();
        System.out.println("wait4 pid=" + pid + ", wstatus=" + wstatus + ", options=0x" + Integer.toHexString(options) + ", rusage=" + rusage);
    }

    private void vfork(Emulator<?> emulator) {
        EditableArm32RegisterContext context = (EditableArm32RegisterContext) emulator.getContext();
        int r0 = emulator.getPid() + ThreadLocalRandom.current().nextInt(256);
        System.out.println("vfork pid=" + r0);
        context.setR0(r0);
    }

    protected int pipe2(Emulator<?> emulator) {
        EditableArm32RegisterContext context = (EditableArm32RegisterContext) emulator.getContext();
        Pointer pipefd = context.getPointerArg(0);
        int flags = context.getIntArg(1);
        int write = getMinFd();
        this.fdMap.put(write, new DumpFileIO(write));
        int read = getMinFd();
        String arg = emulator.get("popenarg");
        System.out.println("pipe get arg:" + arg);
        // 这个是根据 arg 执行得到的输出结果
        String stdout = "\n";
        this.fdMap.put(read, new ByteArrayFileIO(0, "pipe2_read_side", stdout.getBytes()));
        pipefd.setInt(0, read);
        pipefd.setInt(4, write);
        System.out.println("pipe2 pipefd=" + pipefd + ", flags=0x" + flags + ", read=" + read + ", write=" + write + ", stdout=" + stdout);
        context.setR0(0);
        return 0;
    }
}

补完之后之后,再运行一下发现已经运行出结果了

5. 补充说明

但是 pdd 其实还有很多检测的地方,文件也没有补全,所以生成的结果是否能用也未可知,如果不可用可能还要针对检测的地方逐一处理,篇幅有限,这边暂时不做介绍了

下面为全部的代码

package com.pdd;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidARMEmulator;
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 com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.util.Objects;

public class info3 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final String dirPath = "unidbg-android/src/test/resources";
    private final Module module;

    info3() {
        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);

        AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
            public AndroidEmulator build() {
                return new AndroidARMEmulator(processName, rootDir,
                        backendFactories) {
                    @Override
                    protected UnixSyscallHandler<AndroidFileIO>
                    createSyscallHandler(SvcMemory svcMemory) {
                        return new pddARM32SyscallHandler(svcMemory);
                    }
                };
            }
        };


        // 模拟器,设置进程名为com.xunmeng.pinduoduo
        emulator = builder.setProcessName("com.xunmeng.pinduoduo").build();
        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 23版本的sdk
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(dirPath + "/pdd5.72.apk"));
        // 打印日志
        vm.setVerbose(true);
        // 设置JNI,类必须extends AbstractJni -> 用于补环境
        vm.setJni(this);
        // 加载 so 文件, 因为传入 app, 所以可以直接写 so 文件的名字, 去掉 开头的 lib 和 结尾的 .so
        DalvikModule dm = vm.loadLibrary("pdd_secure", true);
        module = dm.getModule(); // 获取so模块的句柄
        dm.callJNI_OnLoad(emulator); // 调用JNI_OnLoad

    }

    public static void main(String[] args) {
//        Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.ALL);
//        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);
        info3 info3 = new info3();
        info3.callInfo3WithFnSign();
    }

    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if (Objects.equals(signature, "com/tencent/mars/xlog/PLog->i(Ljava/lang/String;Ljava/lang/String;)V")) {
            return;
        }
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        if (Objects.equals(signature, "android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;")) {
            return new StringObject(vm, "android_id");
        }

        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {


        if (Objects.equals(signature, "android/content/Context->getContentResolver()Landroid/content/ContentResolver;")) {
            return vm.resolveClass("android/content/ContentResolver").newObject(signature);
        }

        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if (Objects.equals(signature, "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;")) {
            DvmObject<?> key = varArg.getObjectArg(1);
            if (key.getValue().equals("android_id")) {
                return new StringObject(vm, "");
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (Objects.equals(signature, "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
            String mystring = (String) dvmObject.getValue();
            String arg1 = (String) vaList.getObjectArg(0).getValue();
            String arg2 = (String) vaList.getObjectArg(1).getValue();
            String ret = mystring.replaceAll(arg1, arg2);
            return new StringObject(vm, ret);
        }

        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (Objects.equals(signature, "java/lang/String->hashCode()I")) {
            String str = (String) dvmObject.getValue();
            return str.hashCode();

        }
        return super.callIntMethodV(vm, dvmObject, signature, vaList);
    }

    public void callInfo3WithFnSign() {
        DvmClass DeviceNative = vm.resolveClass("com.xunmeng.pinduoduo.secure.DeviceNative");
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        long timestamp = System.currentTimeMillis();
        DvmObject<?> ret = DeviceNative.callStaticJniMethodObject(
                emulator,
                "info3(Landroid/content/Context;JLjava/lang/String;)Ljava/lang/String;",
                vm.addLocalObject(context),
                timestamp,
                "E11C5thK"
        );

        System.out.println("================================================");
        System.out.println(ret.getValue());
        System.out.println("================================================");


    }


}

Tips

开启所有日志

import org.apache.log4j.Level;
import org.apache.log4j.Logger;


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

补文件步骤

需要自己处理文件,那么我们的类就需要再实现一下 IOResolver, 如:

public class demo1 extends AbstractJni implements IOResolver {}

然后在实例化的时候添加一下

emulator.getSyscallHandler().addIOResolver(this);

然后再实现一下 resolve 接口

最后发现他读取的文件有:/dev/__properties__/proc/stat,当然你打开了全日志你也可以看到

Hook Popen

    int popenAddress = (int) moduleLibc.findSymbolByName("popen").getAddress();
    emulator.attach().addBreakPoint(popenAddress, new BreakPointCallback() {
        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext registerContext = emulator.getContext();
            String cmdline = registerContext.getPointerArg(0).getString(0);
            System.out.println("fuck popen cmdline:"+cmdline);
            return false;
        }
    });

最后更新于

这有帮助吗?