pxx app 算法逆向与设备指纹信息提取分析

这里只是做个记录

pdd anti-token参数网上unidbg补环境有很多,这里不再过多赘述

package com.pdd;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
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 com.github.unidbg.utils.Inspector;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneEncoded;
import keystone.KeystoneMode;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPOutputStream;

public class demo extends AbstractJni implements IOResolver {
    public static AndroidEmulator emulator;
    public static Memory memory;
    public static VM vm;
    public static Module module;

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("[IO] open => " + pathname + " flags=" + oflags);     //监视文件访问
        return null;
    }

    public demo(){
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .setProcessName("com.xunmeng.pinduoduo")
                .addBackendFactory(new Unicorn2Factory(false))
                .build();
        emulator.getSyscallHandler().addIOResolver(this);
        memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("D:\\NewWorld\\android_tool\\unidbg\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\apk\\pinduoduo-7-80-0.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary(new File("D:\\NewWorld\\android_tool\\unidbg\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\pdd\\libpdd_secure64.so"), true);
        module = dm.getModule();
        System.out.printf("so文件基地址==>>>>>>>> %s base = 0x%x%n", module.name, module.base);
        dm.callJNI_OnLoad(emulator);
        //emulator.traceWrite(0x4009ee74,0x4009ee74+4);
        hook_memcpy();
        hook_system_property_get();
        hook_breakpoint1();
        emulator.attach().addBreakPoint(module.base + 0x1A1D0);
        //emulator.attach().addBreakPoint(module.base+0x11664);

    }

    public static void main(String[] args) {
        demo pdd_demo = new demo();
        pdd_demo.call_info2();
    }

    @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":
                System.out.println("[+]PLog.i arg0-->>" + vaList.getObjectArg(0));
                System.out.println("[+]PLog.i arg1-->>" + vaList.getObjectArg(1));
                return;
        }
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/xunmeng/pinduoduo/secure/EU->gad()Ljava/lang/String;":
                return new StringObject(vm,"98c3863151319dc6");
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public boolean callStaticBooleanMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            case "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) {
        switch (signature){
            case "java/lang/Throwable-><init>()V":
                return vm.resolveClass("java/lang/Throwable").newObject(new Throwable());
            case "java/io/ByteArrayOutputStream-><init>()V":
                ByteArrayOutputStream OutputMemoryBuffer = new ByteArrayOutputStream();
                return vm.resolveClass("java/io/ByteArrayOutputStream").newObject(OutputMemoryBuffer);
            case "java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V":
                DvmObject obj = varArg.getObjectArg(0);
                OutputStream outputStream = (OutputStream) obj.getValue();
                try {
                    return dvmClass.newObject(new GZIPOutputStream(outputStream));
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }

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

    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/content/Context->checkSelfPermission(Ljava/lang/String;)I":
                System.out.println("[+]Context.checkSelfPermission arg0-->>" + varArg.getObjectArg(0));
                return 1;
            case "android/telephony/TelephonyManager->getSimState()I":
                return 5;
            case "android/telephony/TelephonyManager->getNetworkType()I":
                return 2;
            case "android/telephony/TelephonyManager->getDataState()I":
                return 0;
            case "android/telephony/TelephonyManager->getDataActivity()I":
                return 0;

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

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
                String serviceName = varArg.getObjectArg(0).getValue().toString();
                System.out.println("getSystemService->" + serviceName);
                switch (serviceName){
                    case "phone":{
                        return vm.resolveClass("android/telephony/TelephonyManager").newObject(null);
                    }

                }
            case "android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;":
                return new StringObject(vm, "中国移动");
            case "android/telephony/TelephonyManager->getSimCountryIso()Ljava/lang/String;":
                return new StringObject(vm, "cn");
            case "android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;":
                return new StringObject(vm, "46000");
            case "android/telephony/TelephonyManager->getNetworkOperatorName()Ljava/lang/String;":
                return new StringObject(vm, "中国移动");
            case "android/telephony/TelephonyManager->getNetworkCountryIso()Ljava/lang/String;":
                return new StringObject(vm, "cn");
            case "java/lang/Throwable->getStackTrace()[Ljava/lang/StackTraceElement;":
                return new ArrayObject(
                        vm.resolveClass("java/lang/StackTraceElement").newObject(null),
                        vm.resolveClass("java/lang/StackTraceElement").newObject(null),
                        vm.resolveClass("java/lang/StackTraceElement").newObject(null)
                );
            case "java/lang/StackTraceElement->getClassName()Ljava/lang/String;":
                return new StringObject(vm, "java.lang.Object");
            case "java/io/ByteArrayOutputStream->toByteArray()[B":
                ByteArrayOutputStream ByteArrayArg = (ByteArrayOutputStream) dvmObject.getValue();
                byte[] data = ByteArrayArg.toByteArray();
                // Inspector.inspect(data, "java/io/ByteArrayOutputStream->toByteArray()");
                return new ByteArray(vm, data);
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
                // 获取原始字符串
                String original = (String) dvmObject.getValue();
                // 获取参数
                DvmObject<?> regexObj = vaList.getObjectArg(0);
                DvmObject<?> replacementObj = vaList.getObjectArg(1);
                if (regexObj == null || replacementObj == null) {
                    return new StringObject(vm, original);
                }
                String regex = (String) regexObj.getValue();
                String replacement = (String) replacementObj.getValue();
                // 执行替换
                String result = original.replaceAll(regex, replacement);
                // 返回结果
                return new StringObject(vm, result);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "java/util/zip/GZIPOutputStream->write([B)V":
                OutputStream outputStream = (OutputStream) dvmObject.getValue();
                ByteArray array = varArg.getObjectArg(0);
                Inspector.inspect(array.getValue(), "java/util/zip/GZIPOutputStream->write outputStream"); //unidbg提供的十六进制输出
                try {
                    outputStream.write(array.getValue());
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }
                return;
            case "java/util/zip/GZIPOutputStream->finish()V":
                GZIPOutputStream gzipOutputStream = (GZIPOutputStream) dvmObject.getValue();
                try {
                    gzipOutputStream.finish();
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }
                return;
            case "java/util/zip/GZIPOutputStream->close()V":
                GZIPOutputStream gzipOutputStream2 = (GZIPOutputStream) dvmObject.getValue();
                try {
                    gzipOutputStream2.finish();
                } catch (IOException e) {
                    throw new IllegalStateException(e);
                }
                return;
        }
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }

    public void hook_memcpy(){

        emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext context = emulator.getContext();
                int len = context.getIntArg(2);
                UnidbgPointer pointer1 = context.getPointerArg(0);
                UnidbgPointer pointer2 = context.getPointerArg(1);
                Inspector.inspect(pointer2.getByteArray(0, len), "src " + Long.toHexString(pointer1.peer) + " memcpy " + Long.toHexString(pointer2.peer));
                return true;
            }
        });
    }

    public void hook_system_property_get() {


        emulator.attach().addBreakPoint(module.findSymbolByName("__system_property_get").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {

                RegisterContext ctx = emulator.getContext();

                UnidbgPointer namePtr = ctx.getPointerArg(0);
                UnidbgPointer valuePtr = ctx.getPointerArg(1);

                String key = namePtr.getString(0);
                System.out.println("[__system_property_get] key = " + key);

                return true;
            }
        });
    }


    protected void printJavaObject(long jobjectOffset, String label) {
        DvmObject<?> javaObject = vm.getObject((int) jobjectOffset);
        if (javaObject != null) {
            System.out.println("[" + label + "] " + javaObject.getValue());
        } else {
            System.out.println("[" + label + "] null");
        }
    }
    public void hook_breakpoint1(){
        emulator.attach().addBreakPoint(
                module.base + 0x11378,
                new BreakPointCallback() {
                    @Override
                    public boolean onHit(Emulator<?> emulator, long address) {

                        RegisterContext context = emulator.getContext();
                        UnidbgPointer pointer1 = context.getPointerArg(1);
                        int pointer2 = context.getIntArg(2);
                        UnidbgPointer pointer3 = context.getPointerArg(3);
                        Inspector.inspect(pointer1.getByteArray(0,300), "0x11378_arg1");
                        System.out.println("0x11378_arg2===>>>"+pointer2);
                        Inspector.inspect(pointer3.getByteArray(0,100),"0x11378_arg3");
                        emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                            @Override
                            public boolean onHit(Emulator<?> emulator, long address) {
                                UnidbgPointer pointer0 = context.getPointerArg(0);  //x0此时是返回值
                                printJavaObject(pointer0.peer, "0x11378返回值");
                                return true;
                            }
                        });
                        return true; // 继续执行
                    }
                }
        );
    }



    private void call_info2(){
        DvmClass deviceNativeClass = vm.resolveClass("com/xunmeng/pinduoduo/secure/DeviceNative");
        // 2. 构造一个假的 Context(很多 native 只检查非 null / 调少量方法)
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        DvmObject<?> result = deviceNativeClass.callStaticJniMethodObject(
                emulator,
                "info2(Landroid/content/Context;J)Ljava/lang/String;",
                context,
                1764743489547L
        );
        System.out.println("主动调用结果result====>>>>>>"+result.toString());
    }
}

算法加密逻辑就是Gzip加密数据+AES(cbc)+base64

通过这个JNI函数的偏移可以确定一下函数位置

函数是

unidbg下断点调试看看,可以发现第二个参数s是设备信息

交叉引用看一下位置,发现只有一处调用在device_info2函数内

有ollvm混淆但不妨碍分析,结合ai问一下即可定位到关键代码段

这里有几个关键方法可以点进去看一下

如sub_1A1D0

看到有相关的设备指纹获取api,qword_9A120是运行时动态解密的数据,下断点看看

可以发现此时就是该字段就是即将要获取的指纹信息

其实在unidbg中hook system_property_get也会获取到数据,但这里是想追一下代码的执行过程

   public void hook_system_property_get() {


        emulator.attach().addBreakPoint(module.findSymbolByName("__system_property_get").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {

                RegisterContext ctx = emulator.getContext();

                UnidbgPointer namePtr = ctx.getPointerArg(0);
                UnidbgPointer valuePtr = ctx.getPointerArg(1);

                String key = namePtr.getString(0);
                System.out.println("[__system_property_get] key = " + key);

                return true;
            }
        });
    }
>"ANDROID.PERMISSION.READ-PHONE-STATE" [__SYSTEM_PROPERTY_GET] KEY - RO.SERIALNO JNIENV-SNEWSTRINAUTE("015763636321672505') WAS CALLED FROM RXGOX6003A80OFLIBNDD-SECUNE-SOLOX3A809 JNIENV--NENSLOBALREF("015753636321C72505') WAS CALLED FROM RXODX4004E668(LIBDDDD-SECURE,SOLOX4E6689 JHIENV--GETSTRINQUTFLENATH("0157636321C72505") WAS CALLED FROM RXBAXBOX60O1LIBDDDDDA-SECURS,SOLEX14ED JNIENV->GATSTRINAUTFCHARS("01503573321672505年) WAS CALLED FROM RXAOXHOX4001503OLLIBDD-SECUNS,SOLOX15 JNIENY-RELEASESTRINGUTFCHANS('015763210DDDD FROS") WAS CALLED FRON RXOXAOO15864LLIOPDDDDDDDDDX15X150B [__SYSTEM_PROPERTY_GET] KEY - RO.PRODUCT.BRAND [__SYSTEM_PROPERTY_GET] KEY 三 R RO.PRODUCT.DEVICE [__SYSTEM_PROPERTY_GET] KEY 三 RO.PRODUCT.MODEL [__SYSTEM_PROPERTY_GET] KEY RO.P O.PRODUCT.MANUFACTURER [_SYSTEM_PROPERTY_GET] KEY - RO.PRODUCT.BOARD [__SYSTEM_PROPERTY_GET] RO.BUILD.DISPLAY.ID KEY R [__SYSTEM_PROPERTY_GET] KEY - R RO.BUILD.ID [__SYSTEM_PROPERTY_GET] KEY 三 R RO.BUILD.VERSION.INCREMENTAL [__SYSTEM_PROPERTY_GET] KEY - RO.BUILD.TYPE [__SYSTEM_PROPERTY_GET] KEY RO.BUILD.TAGS [__SYSTEM_PROPERTY_GET] KEY 三 RO.BUILD.VERSION.RELEASE [__SYSTEM_PROPERTY_GET] KEY 三 R RO.BUILD.VERSION.SDK [__SYSTEM_PROPERTY_GET] KEY - RO.BUILD.DATE.UTC [TOL ONEN :> /SVSTEM/BUILD.DRON FLAGS:O -->


接着看sub_1B6DC

看一下这个file路径是什么

** /system/build.prop 是系统在编译ROM时生成的,可用于判断是否为模拟器**

这里没有使用open等函数进行获取数据,而是使用了stat函数,是为了避免被hook

stat()用来将参数file_name 所指的文件状态, 复制到参数buf 所指的结构中
struct stat {
    dev_t st_dev; //device 文件的设备编号
    ino_t st_ino; //inode 文件的i-node
    mode_t st_mode; //protection 文件的类型和存取的权限
    nlink_t st_nlink; //number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
    uid_t st_uid; //user ID of owner 文件所有者的用户识别码 
    gid_t st_gid; //group ID of owner 文件所有者的组识别码 
    dev_t st_rdev; //device type 若此文件为装置设备文件, 则为其设备编号 
    off_t st_size; //total size, in bytes 文件大小, 以字节计算 
    unsigned long st_blksize; //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小. 
    u nsigned long st_blocks; //number of blocks allocated 占用文件区块的个数, 每一区块大小为512 个字节. 
    time_t st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、 utime、read、write 与tructate 时改变.
    time_t st_mtime; //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、 utime 和write 时才会改变
    time_t st_ctime; //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、 权限被更改时更新 
};

所以此刻v3 = buf[11]; 获取的数据就是time_t st_mtime

sub_1B960函数ollvm混淆比较严重,这里结合unidbg进行分析

在该函数下断点如何blr,运行到函数结尾,可发现运行大量JNI函数,大部分都是调用java层的api获取设备sim卡信息。

"ANDROID.DERMISSION.READ_PHONE_STATEW JNIENV->CALLINTHOD[ANDROID-CONTENTENTENTEXTPASFBAO77, CHRCONTSOLIPENISSION["ANAROID,PEMISSIONE STATE ) -> OX1) WAS CALLED FROM RXCOX40018AB4[LIBDD_SECURE.SO]OX18AB4 JUTENV- FINDCLASS(ONAROLD/TELEPHONY/TOLEPHONYTANAGER) WAS CALLED FRON FXDPXAO3ELTIBPA. 30JBX3AB35 [20:A7:38 697]SRC 40417008 MEMCPY 68417013, MD5-8BB6C17838643F9691CCBAADE6C51789, HEX-05 SIZE:1 0000:05 -->


sub_1D450函数

依旧是字符串解密后调用

继续下断点看一下传入了什么数据

这是即将要调用的方法签名

看一下java层的实现

通过JNI回调java层获取设备ID


sub_20814函数

根据三个函数的返回值不同进行调用

进去sub_1E8C0函数看看

还是一样

回调java层android/os/Debug.isDebuggerConnected()的方法,判断是否被调试

sub_1EF50函数

其实在unidbg中这个函数干了什么也看的七七八八了

首先获取进程pid,获取status文件TracerPid值检查是否被反调试

sub_1F3E0函数

这个函数同时也是调用了大量JNI函数,回调Java调用栈,判断是否被unidbg等模拟器模拟

正常 Android Java 调用 native 的栈

com.xunmeng.pinduoduo.secure.EU.gad
com.xunmeng.pinduoduo.secure.EU.xxx
com.xunmeng.pinduoduo.xxx.xxx
android.app.ActivityThread

而我unidbg调用栈却是

java.lang.Object
java.lang.Object
java.lang.Object


posted @ 2025-12-27 22:03  GGBomb  阅读(5)  评论(0)    收藏  举报