APP逆向 day26unidbg下-pdd(anti)案例

一.前言

今天我们讲unidbg的下篇,也就是unidbg基础的最后一个部分,我们上节课也有补环境,比如补java环境,补安卓环境,这节课我们讲的肯定比这些都要难,我会给出一个之前讲过的案例,然后会讲一个全新的案例,pdd,这个里面的环境就更加难了,让我们接着往下看吧

二.B站sign参数

2.1 回顾sign

不记得的可以去往期回顾一下,这里给出地址

APP逆向 day18某站逆向 part3-CSDN博客文章浏览阅读1.2k次,点赞23次,收藏19次。因为之前被封,所以很久才更新,今天这个主要是后面的两个hook脚本很重要,app逆向是真的难,真的难找,很大一部分比的就是大家的hook代码。https://blog.csdn.net/weixin_74178589/article/details/140632798?spm=1001.2014.3001.5502我这里给出几张重要的截图

 我们找到这个this.b就是sign值

 

this.b是signedQuery初始化传入的第二个参数

在这里传媒如一个map对象最后tostring 

 

最后执行s在c中进行加密

加密的so文件时libbili.so

2.2 分析

1 so文件和方法
    libbili.so
    static native SignedQuery s(SortedMap<String, String> sortedMap);
2 入参和返回值
    入参:sortedMap---》hook得到数据--》包裹--》传给unidbg
    返回值:SignedQuery类型---》app自己定义的类型
        
        
3 hook 参数和返回值
入参:
map= {actual_played_time=0, aid=884507902, appkey=1d8b6e7d45233436, auto_play=0, build=6240300, c_locale=zh-Hans_CN, channel=xxl_gdt_wm_253, cid=233035308, epid=0, epid_status=, from=2, from_spmid=main.ugc-video-detail.0.0, last_play_progress_time=0, list_play_time=0, max_play_progress_time=0, mid=0, miniplayer_play_time=0, mobi_app=android, network_type=1, paused_time=0, platform=android, play_status=0, play_type=1, played_time=0, quality=32, s_locale=zh-Hans_CN, session=907a218ff7237664b2910757062ce5a40d1f08fc, sid=0, spmid=main.ugc-video-detail.0.0, start_ts=0, statistics={"appId":1,"platform":3,"version":"6.24.0","abtest":""}, sub_type=0, total_time=0, type=3, user_status=0, video_duration=674}
返回值= actual_played_time=0&aid=884507902&appkey=1d8b6e7d45233436&auto_play=0&build=6240300&c_locale=zh-Hans_CN&channel=xxl_gdt_wm_253&cid=233035308&epid=0&epid_status=&from=2&from_spmid=main.ugc-video-detail.0.0&last_play_progress_time=0&list_play_time=0&max_play_progress_time=0&mid=0&miniplayer_play_time=0&mobi_app=android&network_type=1&paused_time=0&platform=android&play_status=0&play_type=1&played_time=0&quality=32&s_locale=zh-Hans_CN&session=907a218ff7237664b2910757062ce5a40d1f08fc&sid=0&spmid=main.ugc-video-detail.0.0&start_ts=0&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.24.0%22%2C%22abtest%22%3A%22%22%7D&sub_type=0&total_time=0&ts=1716387614&type=3&user_status=0&video_duration=674&sign=ed5a33857d99b8442a3fe182aa214eed


4 拼凑出SortedMap--》包裹后---》传给unidbg
    包裹方式一:ProxyDvmObject.createObject(vm,map)
    包裹方式一:vm.resolveClass("类").newObject(map)
    
5 返回值:SignedQuery类型
    app自定义类型---》可以DvmObject<?>类型泛指---》unidbg中所有类型,父类都是DvmObject---》StringObject也是DvmObject---》拿到具体的值DvmObject对象.getValue()
    

6 是否需要不环境?libbili.so中的s方法
    -so内部一定执行了  c调用java
        -c中是没有SignedQuery类的--》返回值是SignedQuery的对象
        -于是:c内部一定调用了java

7 所以在c内部调用java,完成SignQuery的初始化--》必须传入两个参数
而第二个参数,就是sign的值
          public SignedQuery(String str, String str2) {
                this.a = str;
                this.b = str2;
            }

8 分析:so内部--》传入sortedMap,通过某种加密方式加密---》通过c调用java,实例化得到了SignedQuery对象,第一个参数是:sortedMap转成了字符串,第二个参数就是  sign 的加密结果

9 使用unidbg运行时,一定需要补环境--》SignedQuery--》两种方案破解sign
    -1 在需要补SignedQuery环境时,打印出第二个参数,其实就是sign
    -2 完整补SignedQuery,自己把返回的SignedQuery对象,调用toString,返回整个字符串
 

这里我会讲第二种,讲第二种的时候也能讲到第一种,那我们先编写大致逻辑框架,除sign函数以外的直接c就好了

2.3 unidbg运行

 我们直接把unidbg初始化写好,然后运行发现不报错,那么我们现在就要开始编写sign部分的代码了

编写sign中的内容

这里主要是 SignedQuery是apk内部定义的,我们这里没有,所以用一个 DvmObject<?>来接受,这个是所有返回值的父类,然后最后返回 String res=singedQuery.getValue().toString();调用他内部的tostring方法,这样就能跑出最后结果

这里给出编写sign的代码

    public void sign(){
        // 0 构造SortedMap
        SortedMap<String, String> map=new TreeMap<String, String>();
        // 这不全--》我们目标是把sign跑出来,所以暂时传这一点点
        map.put("actual_played_time", "0");
        map.put("aid", "884507902");
        map.put("appkey", "1d8b6e7d45233436");
        map.put("auto_play", "0");
        map.put("ts", "1647952932");

        // 1 找到java类中jni的类  native方法,找的时候是固定写法
        DvmClass LibBili = vm.resolveClass("com/bilibili/nativelibrary/LibBili");

        // 这个返回值是apk内部的,包名类名前面加个L
        String method = "s(Ljava/util/SortedMap;)Lcom/bilibili/nativelibrary/SignedQuery;";

        //3 执行方法
        // 之前我们写的返回值都是StringObject 或者ByteArray
        // 但是这个前面分析他的返回值类型是signedQuery 而这个是apk内部定义的类,我们此时就可以使用DvmObject<?> ,这个是unidbg中所有返回值的父类
        DvmObject<?> singedQuery = LibBili.callStaticJniMethodObject(
                emulator,
                method,
                ProxyDvmObject.createObject(vm, map)
        );

        // 4 打印结果
        // singedQuery.getValue()--->SignedQuery 的对象-->有个toString方法--->返回字符串--》带sign的字符串,这样结果就是字符串类型了
        String res=singedQuery.getValue().toString();
        System.out.println(res);

    }

2.4 补环境

运行肯定会报错,这里我们就需要补环境

 这个就是传入一个字典,判断是否为空

我们有两种操作,一种直接返回True(因为我们知道传入的字典不为空),另一种就是主动调用

那我们又来补这个,这个不就是字典根据键来取值嘛,应该是字符串类型的,只是他用了object来泛指

 这里我们知道是string类型,所以可以用方式二来写,如果不知道的话,可以直接用第一种方便的来

我们运行看看

这个不就是调用了java中自己定义的方法吗

这个我就带着大家一步步来写

我们按照他的逻辑来,是不是补成这样,但是少了 SignedQuery类,还有里面的r方法,那我们去源代码里面扣一个来

把这个r方法扣进来,发现报错的有 TextUtils.isEmpty, b, ContainerUtils.FIELD_DELIMITER

很明显b是里面定义的方法,那TextUtils.isEmpty是什么呢,他是安卓中的判断是否为空,可以去chatgpt问一下,这里是不是可以直接调用string中的内置方法isEmpty()来替换呢ContainerUtils.FIELD_DELIMITER我们双击点进去发现是"&",也可以直接替换

b方法直接c就行了

我们把那些修改,再把b给扣进去,发现又需要c,我们再扣,这后面我就不说了,流程都一样,接着扣,修改就行了,补完之后,我们再运行

那我们就接着补,我们补之前先来分析一下这个代码,这个不就是SignedQuery类实例化的时候传入两个参数嘛,而这个第二个参数不就是我们要的sign值嘛,当时我们说了可以完整补完,也可以部分补,那我们来演示一下

 这样不就是了吗,这里异常报错是因为我们没有补完,因为我们最后返回的对象是个空嘛,然后上面最开始的又打印了最后的字符串,所以报错了

那我们现在完整的补

发现我们打印的结果是这个鬼样子,这个是为啥呢,这个不就是打印java中类的地址吗,只有调用的,这个不就是没有调用toString嘛,还记得我们当是不是通过toString来定位加密位置的,所以我们抠代码要把toString带上,那个相当于重写

这样就出结果啦

这里给出完整补环境代码

//放到sign内部
    @Override
    public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if(signature.equals("java/util/Map->isEmpty()Z")){
            //这个就是判断是不是这个字典是不是空嘛
            //方式一,直接返回True,因为我们已经知道这个字典不是空
            //return true;

            //方式二 主动调用,取出传入的对象
            Map m=(Map)dvmObject.getValue();
            // 打印这个map看一下,就是我们拼凑出来,传入的
            return m.isEmpty();
        }

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

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if(signature.equals("java/util/Map->get(Ljava/lang/Object;)Ljava/lang/Object;")){
            // 报错是:map根据key去取值--》c调用java--》map.get('actual_played_time')
            // 方式一:使用Object泛指:
            // Map m =(Map) dvmObject.getValue();
            // //取出参数:取第0个参数--》我们这个只有一个参数
            //Object key=varArg.getObjectArg(0).getValue();
            // Object value=m.get(key);
            // return ProxyDvmObject.createObject(vm,value);

            //方式二,知道了具体是string类型
            Map m =(Map) dvmObject.getValue();
            //取出参数:取第0个参数--》我们这个只有一个参数
            String key=(String)varArg.getObjectArg(0).getValue();
            String value=(String)m.get(key);
            return new StringObject(vm,value);
        }


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

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if(signature.equals("com/bilibili/nativelibrary/SignedQuery->r(Ljava/util/Map;)Ljava/lang/String;")){
            // SignedQuery对象--》调用r方法,传入map,返回字符串
            // 首先拿到参数 varArg.getObjectArg
            Map m =(Map) varArg.getObjectArg(0).getValue();

            // 然后我们取对象就不能用dvmObject,因为这个里面没有SignedQuery,apk定义的,得我们主动创建
            String res=SignedQuery.r(m);
            return new StringObject(vm,res);
        }


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

    @Override
    public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if(signature.equals("com/bilibili/nativelibrary/SignedQuery-><init>(Ljava/lang/String;Ljava/lang/String;)V")){
            // 方式一:打印sign 第二个参数就是
            //  String sign=(String) varArg.getObjectArg(1).getValue();
            // System.out.println(sign);
            // return vm.resolveClass("com/bilibili/nativelibrary/SignedQuery").newObject(null);

            // 方式二:补齐,实例化得到对象
            String v1=(String) varArg.getObjectArg(0).getValue();
            String sign=(String) varArg.getObjectArg(1).getValue();
            SignedQuery sq=new SignedQuery(v1,sign);
            return vm.resolveClass("com/bilibili/nativelibrary/SignedQuery").newObject(sq);


        }

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


//定义类,单独放
class SignedQuery{
    public final String a;
    public final String b;
    private static final char[] f15567c = "0123456789ABCDEF".toCharArray();


    public SignedQuery(String str, String str2) {
        this.a = str;
        this.b = str2;
    }




    static String r(Map<String, String> map) {
        if (!(map instanceof SortedMap)) {
            map = new TreeMap(map);
        }
        StringBuilder sb = new StringBuilder(256);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            if (!key.isEmpty()) {
                sb.append(b(key));
                sb.append("=");
                String value = entry.getValue();
                sb.append(value == null ? "" : b(value));
                sb.append("&");
            }
        }
        int length = sb.length();
        if (length > 0) {
            sb.deleteCharAt(length - 1);
        }
        if (length == 0) {
            return null;
        }
        return sb.toString();
    }

    static String b(String str) {
        return c(str, null);
    }

    static String c(String str, String str2) {
        StringBuilder sb = null;
        if (str == null) {
            return null;
        }
        int length = str.length();
        int i = 0;
        while (i < length) {
            int i2 = i;
            while (i2 < length && a(str.charAt(i2), str2)) {
                i2++;
            }
            if (i2 == length) {
                if (i == 0) {
                    return str;
                }
                sb.append((CharSequence) str, i, length);
                return sb.toString();
            }
            if (sb == null) {
                sb = new StringBuilder();
            }
            if (i2 > i) {
                sb.append((CharSequence) str, i, i2);
            }
            i = i2 + 1;
            while (i < length && !a(str.charAt(i), str2)) {
                i++;
            }
            try {
                byte[] bytes = str.substring(i2, i).getBytes("UTF-8");
                int length2 = bytes.length;
                for (int i3 = 0; i3 < length2; i3++) {
                    sb.append('%');
                    sb.append(f15567c[(bytes[i3] & 240) >> 4]);
                    sb.append(f15567c[bytes[i3] & 15]);
                }
            } catch (UnsupportedEncodingException e) {
                throw new AssertionError(e);
            }
        }
        return sb == null ? str : sb.toString();
    }

    private static boolean a(char c3, String str) {
        return (c3 >= 'A' && c3 <= 'Z') || (c3 >= 'a' && c3 <= 'z') || !((c3 < '0' || c3 > '9') && "-_.~".indexOf(c3) == -1 && (str == null || str.indexOf(c3) == -1));
    }

    public String toString() {
        String str = this.a;
        if (str == null) {
            return "";
        }
        if (this.b == null) {
            return str;
        }
        return this.a + "&sign=" + this.b;
    }

}

三.PDD anti-token破解

3.1 目标

1.目标
    破解搜索功能请求头中的  anti-token
    
2.版本选择:v6.32.0
1 通过hook查找方法在那个so文件中--》之前学过
    -动态注册脚本,静态注册脚本
    
3.补环境:app内部的参数值,系统参数值---》通过frida-hook得到--》搜索得到写死

4.使用SocksDroid转发再进行抓包

3.2 抓包和反编译搜索

 我们发现有时候配置了 SocksDroid也抓不到,那我们在无网络的时候进入,然后有网络了立马抓,这样就能抓到了

我们要破的就是这个吗,那我们现在来发编译搜索

我们直接搜索anti-token

 

发现很多地方,我这里直接告诉大家是第四个,我们点进去

发现f就是那个,那我们点进去看看

 

发现f是是一个接口,那我们是不是要找到这个接口的具体实现类,那我们查找这个接口的用例

这里我告诉大家是最后一个,我们点进去

我们找到这个f的实现类在这里,我们再点进去

 

我们再点进去

 

发现在这里,这不就是jni的方法吗,但是这里没有给出哪个so文件,我们之前给大家说过hook静态注册和动态注册,不知道大家记不记得,但是在hook之前我们先hook确定传入的参数,第一个参数是context,我们先不管,看看另一个参数是啥

 发现参数就是时间戳

那我们接下来就hook一下是静态注册和动态注册来判断so文件了,我直接告诉大家是动态注册,大家也可以去试试

hook完长这样 说明是在libpdd_secure.so里面,那我们把这两个都c到unidbg里面去

3.3 unidbg跑

我们编写好主要的初始化函数和sign方法

 

我们运行一下

 发现报错,那我们开始补环境

3.4 补环境

3.4.1 补腾讯日志库

我们在刚才报错截图里可以看到tencent,那我们百度搜索一下,这个是腾讯日志库,那我们就补

 他这个是日志,而且是void,我们直接return; 就好了

3.4.2 补手机权限

我们搜索发现 这个是获取手机权限的

1 报错是: android/content/Context->checkSelfPermission(Ljava/lang/String;)I
2 安卓开发者--》在Manifest文件中添加权限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
3 用户使用app,会弹窗提醒

4  ContextCompat.checkSelfPermission()方法检测授权状态,返回的结果为PackageManager中的两个常量:PERMISSION_GRANTED(已授权)和PERMISSION_DENIED(未授权)
    public static final int PERMISSION_DENIED = -1; # 未授权
    public static final int PERMISSION_GRANTED = 0; # 授权
    
    
5 获取这个返回值的方式:
    获取方式一:我们可以通过hook拿到
    获取方式二:我们直接搜索写死-1或0
    获取方式三:我们自己写安卓应该,看自己手机的状态

那我们直接返回0,其实要返回-1的,后面出问题了再解释

 

3.4.3 getSystemService

再次运行

1  android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
    -获得SystemService,后期为了拿到:sim卡状态,网络状态,运行商,电池。。。
    
2 安卓中对应
/*
* SIM的状态信息:
*  SIM_STATE_UNKNOWN             未知状态 0
*  SIM_STATE_ABSENT                 没插卡 1
*  SIM_STATE_PIN_REQUIRED     锁定状态,需要用户的PIN码解锁 2
*  SIM_STATE_PUK_REQUIRED    锁定状态,需要用户的PUK码解锁 3
*  SIM_STATE_NETWORK_LOCKED   锁定状态,需要网络的PIN码解锁 4
*  SIM_STATE_READY            就绪状态 5
*/ 

 我们再运行

发现这个错和补环境没关系,那是因为安卓11之后不允许获取手机权限, 所以得返回-1

改完之后再运行

 

 这里我就不和前面一样说为什么了,大家百度搜一下就知道啦,然后补系统方法,我就一次补好,我和大家说一下补app自己的方法

3.4.4 补app自己的方法

我们发现报这个错误,一是可以去app中找逻辑,二是可以直接hook,发现是个固定值,这里就不给出hook脚本了

 

3.4.5 补系统方法isDebuggerConnected

isDebuggerConnected是否有调试器挂载到程序上

运行

 

3.4.6 补异常

后面的报错我就先过,自己搜搜就知道了

3.4.7 补replace

补到发现报这个错,这个就是replace,要动脑子,我教大家补

 

3.4.8 补文件操作

发现报这个错,这个是文件的,我一次给大家补好

 

 

这些都补好之后,正常出值了

 

3.4.9 补环境代码

代码如下


    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if(signature.equals("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(signature.equals("android/content/Context->checkSelfPermission(Ljava/lang/String;)I")){
            String s=(String) varArg.getObjectArg(0).getValue();
            // System.out.println(s); // android.permission.READ_PHONE_STATE
            return -1;
        }
        if(signature.equals("android/telephony/TelephonyManager->getSimState()I")){
            return 5; // 表示没插卡
        }

        if (signature.equals("android/telephony/TelephonyManager->getNetworkType()I")) {
            // 网络类型:https://codeleading.com/article/33471321733/
            return 13;
        }
        if (signature.equals("android/telephony/TelephonyManager->getDataState()I")) {
            return 0;
        }
        if (signature.equals("android/telephony/TelephonyManager->getDataActivity()I")) {
            return 4;
        }
        return super.callIntMethod(vm, dvmObject, signature, varArg);
    }

// 补:callObjectMethod

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if(signature.equals("android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;")){
            // 补个null---》后续,在c中调用 SystemService 获取手机状态信息--》再缺什么就继续补:sim卡信息,运行上信息。。。
            return vm.resolveClass("android/telephony/TelephonyManager").newObject(null);
            //不能用 ProxyDvmObject.createObject(vm,null) 补

        }
        if(signature.equals("android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;")){
            // 拿运营商:getSimOperatorName
            return new StringObject(vm, "中国电信");
        }
        if(signature.equals("android/telephony/TelephonyManager->getSimCountryIso()Ljava/lang/String;")){
            // 拿地区
            return new StringObject(vm, "cn");
        }
        if (signature.equals("android/telephony/TelephonyManager->getNetworkOperator()Ljava/lang/String;")) {
            //https://blog.csdn.net/ztp800201/article/details/44198031/
            return new StringObject(vm, "46003");
        }
        if (signature.equals("android/telephony/TelephonyManager->getNetworkOperatorName()Ljava/lang/String;")) {
            //https://blog.csdn.net/Myfittinglife/article/details/118685804
            return new StringObject(vm, "中国电信");
        }
        if (signature.equals("android/telephony/TelephonyManager->getNetworkCountryIso()Ljava/lang/String;")) {
            // 获取国家代码
            return new StringObject(vm, "cn");
        }
        if (signature.equals("java/lang/Throwable->getStackTrace()[Ljava/lang/StackTraceElement;")) {
            return new ArrayObject(vm.resolveClass("java/lang/StackTraceElement").newObject(null));
        }
        if (signature.equals("java/lang/StackTraceElement->getClassName()Ljava/lang/String;")) {
            return new StringObject(vm, "");
        }

        if (signature.equals("java/io/ByteArrayOutputStream->toByteArray()[B")) {
            ByteArrayOutputStream obj = (ByteArrayOutputStream) dvmObject.getValue();
            return new ByteArray(vm, obj.toByteArray());
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

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

    @Override
    public boolean callStaticBooleanMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        if (signature.equals("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 (signature.equals("java/lang/Throwable-><init>()V")) {
            Throwable t=new Throwable(); // 可以new出来,可以传null
            return vm.resolveClass("java/lang/Throwable").newObject(t);
        }
        //补文件
        if (signature.equals("java/io/ByteArrayOutputStream-><init>()V")) {
            ByteArrayOutputStream obj = new ByteArrayOutputStream();
            return vm.resolveClass("java/io/ByteArrayOutputStream").newObject(obj);
        }
        if (signature.equals("java/util/zip/GZIPOutputStream-><init>(Ljava/io/OutputStream;)V")) {
            try {
                OutputStream chunk = (OutputStream) varArg.getObjectArg(0).getValue();
                GZIPOutputStream obj = new GZIPOutputStream(chunk);
                return vm.resolveClass("java/util/zip/GZIPOutputStream").newObject(obj);
            } catch (Exception e) {
                System.out.println("写入错误1" + e);
            }
        }


        return super.newObject(vm, dvmClass, signature, varArg);
    }
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) {
            String origin = (String) dvmObject.getValue();
            String a0 = (String) vaList.getObjectArg(0).getValue();
            String a1 = (String) vaList.getObjectArg(1).getValue();
            String result = origin.replaceAll(a0, a1);
            return new StringObject(vm, result);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if (signature.equals("java/util/zip/GZIPOutputStream->write([B)V")) {
            GZIPOutputStream obj = (GZIPOutputStream) dvmObject.getValue();
            byte[] chunk = (byte[]) varArg.getObjectArg(0).getValue();
            try {
                obj.write(chunk);
            } catch (Exception e) {
            }
            return;
        }
        if (signature.equals("java/util/zip/GZIPOutputStream->finish()V")) {
            GZIPOutputStream obj = (GZIPOutputStream) dvmObject.getValue();
            try {
                obj.finish();
            } catch (Exception e) {
            }
            return;
        }
        if (signature.equals("java/util/zip/GZIPOutputStream->close()V")) {
            GZIPOutputStream obj = (GZIPOutputStream) dvmObject.getValue();
            try {
                obj.close();
            } catch (Exception e) {
            }
            return;
        }
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }

四.总结 

今天的内容很多,主要pdd的环境是真的难,难所以值钱呀,我至少写了10个小时,感谢点赞关注加收藏,发现补环境是不是很恼火,没办法,纯算更难,加油补吧!

补充

有需要源码的看我主页签名名字私

 

 

 

 

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

往日情怀酿做酒 V1763929638

往日情怀酿作酒 感谢你的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值