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 构造SortedMapSortedMap<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内部的,包名类名前面加个LString 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内部@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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.getObjectArgMap 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);}@Overridepublic 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 补环境代码

代码如下

@Overridepublic 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);}@Overridepublic 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_STATEreturn -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@Overridepublic 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;")){// 拿运营商:getSimOperatorNamereturn 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/118685804return 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);}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {if (signature.equals("java/lang/Throwable-><init>()V")) {Throwable t=new Throwable(); // 可以new出来,可以传nullreturn 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个小时,感谢点赞关注加收藏,发现补环境是不是很恼火,没办法,纯算更难,加油补吧!

补充

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

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/390479.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多 NodeJS 环境管理

前言 对于某个项目依赖特定版本的 NodeJS&#xff0c;或几个项目的 NodeJS 版本冲突时&#xff0c;需要在系统中安装多个版本的 NodeJS&#xff0c;这时可以使用一些工具来进行多个 NodeJS 的管理。 有很多类似的 NodeJS 管理工具&#xff0c;如 nvm, nvs, n 等&#xff0c;接…

深入理解单元测试与JUnit:从基础概念到实践操作

文章目录 前言一、单元测试是什么&#xff1f;单元测试的特点单元测试的好处 二、junit是什么&#xff1f;三、操作步骤1.junit安装2.maven新建项目3. 新建java文件4. 生成测试类5. 编写测试方法6. 测试结果 总结 前言 随着软件开发行业的不断发展&#xff0c;测试的重要性日益…

C++自定义接口类设计器之函数解析二

关键代码 // 解析为函数 bool FunctionCreator::parse(const QString& lineFunc) {auto trimFunc lineFunc.trimmed();auto list trimFunc.split(" ");bool bHasReturn false;// 返回值和函数名解析for (const auto& key : list) {auto trimKey key.trim…

串口应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

串口应用编程 串口应用编程介绍 介绍 串口定义:串行接口,数据按顺序传输 串口特点:通信线路简单,距离远,速度较低 应用领域:常用工业接口 Linux系统中的作用 作为标准输入输出设备 系统打印信息输出 用户与系统交互 串口与终端:在Linux系统中,串口被视为一种终端&#…

2024年软件测试岗必问的100+个面试题【含答案】

一、基础理论 1、开场介绍 介绍要领&#xff1a;个人基本信息、工作经历、之前所做过的工作及个人专长或者技能优势。扬长避短&#xff0c;一定要口语化&#xff0c;语速适中。沟通好的就多说几句&#xff0c;沟通不好的话就尽量少说两句。举例如下&#xff1a; 面试官你好&…

前端Web-JavaScript(下)

主要是补全一下JavaScript 基本对象: String对象 语法格式 方式1&#xff1a; var 变量名 new String("…") ; //方式一 例如&#xff1a; var str new String("Hello String"); 方式2&#xff1a; var 变量名 … ; //方式二 例如&#xff1a; var …

【外排序】--- 文件归并排序的实现

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构 我们之前学习的八大排序&#xff1a;冒泡&#xff0c;快排&#xff0c;插入&#xff0c;堆排等都是内排序&#xff0c;这些排序算法处理的都是…

一键生成视频并批量上传视频抖音、bilibili、腾讯(已打包)

GenerateAndAutoupload Github地址&#xff1a;https://github.com/cmdch2017/GenerateAndAutoupload 如何下载&#xff08;找到最新的release&#xff09; https://github.com/cmdch2017/GenerateAndAutoupload/releases/download/v1.0.1/v1.0.1.zip 启动必知道 conf.py …

数论第四节:二元一次不定方程、勾股数

不定方程定义 解不确定的方程称为不定方程。一般化的定义为&#xff1a;不定方程是指未知数的个数多余方程的个数&#xff0c;或未知数受到某种限制&#xff08;如整数、正整数等&#xff09;的方程和方程组。 二元一次不定方程定义 形如axbyc的形式的方程。其中a,b不等于0&…

Qt对象树的介绍

目录 创建项目&#xff08;此处我就不多介绍了&#xff09; 按钮 对象树 创建项目&#xff08;此处我就不多介绍了&#xff09; QMainWidow带菜单栏的 QWidget空白的 QDialog对话框 创建功能时注意&#xff1a; 项目工程名称一般不要有标点&#xff0c;不要带中文 按钮 /…

变量作用域、作用域链、return

全局变量 全局变量因为在全局操作会每次留存上次操作的结果 局部变量因为执行完成就会被销毁并不会保留本次操作的结果 可以通过传参和返回&#xff0c;将结果不断地专递处理 局部变量 参数也是局部变量 函数内的预解析预赋值 函数内的局部变量 如果同名全局变量遇到局部变量…

linux进程控制——进程替换——exec函数接口

前言&#xff1a; 本节内容进入linux进程控制板块的最后一个知识点——进程替换。 通过本板块的学习&#xff0c; 我们了解了进程的基本控制方法——进程创建&#xff0c; 进程退出&#xff0c; 进程终止&#xff0c; 进程替换。 进程控制章节和上一节进程概念板块都是在谈进程…

【IEEE出版】第五届大数据、人工智能与软件工程国际研讨会(ICBASE 2024,9月20-22)

第五届大数据、人工智能与软件工程国际研讨会&#xff08;ICBASE 2024&#xff09;将于2024年09月20-22日在中国温州隆重举行。 会议主要围绕大数据、人工智能与软件工程等研究领域展开讨论。会议旨在为从事大数据、人工智能与软件工程研究的专家学者、工程技术人员、技术研发人…

C#加班统计次数

C#加班统计次数 运行环境&#xff1a;vs2022 .net 8.0 社区版 1、用C#语言&#xff1b;2、有界面上传Excel文件; 3、对Excel列&#xff08;部门、人员姓名、人员编号、考勤时间 &#xff09;处理&#xff1a;&#xff08;1&#xff09;按人员编号、考勤日期分组且保留原来字段&…

大厂linux面试题攻略五之数据库管理

一、数据库管理-MySQL语句 0.MySQL基本语句&#xff1a; 1.SQL语句-增 创建xxx用户&#xff1a; mysql>create user xxx % indentified by 123456; xxx表示用户名 %b表示该用户用来连接数据库的方式&#xff08;远程或本地连接&#xff09; indentified by 123456设置密码…

C语言基础知识之函数指针和指针函数

函数指针和指针函数 函数指针和指针函数指向函数的指针返回指针值的函数指针函数和函数指针的区别 问题1_1代码1_1结果1_1 函数指针和指针函数 指向函数的指针 用函数指针变量调用函数 可以用指针变量指向整型变量、字符串、数组&#xff0c;也可以指向一个函数。一个…

Xinstall超级渠道功能,轻松解决App推广中的层级统计难题

随着互联网的不断发展和流量玩法的多样化&#xff0c;App推广和运营面临着前所未有的挑战。传统的营销方式在互联网流量红利衰退的背景下逐渐失效&#xff0c;企业急需提高获客转化的效率和用户留存。在这个过程中&#xff0c;App渠道数据分析显得尤为重要。然而&#xff0c;许…

Spring中是如何实现IoC和DI的?

前言&#xff1a;在前一篇文章中对于IoC的核心思想进行了讲解&#xff0c;而本篇文章则从Spring的角度入手&#xff0c;体会Spring对于IoC是如何实现的。 如果对IoC还有不太了解的可以阅读上一篇文章&#xff0c;相信一定会带来全新的收获&#xff1a;什么是IoC&#xff08;控制…

J029_UDP通信

一、需求描述 实现UDP的通信 1.1 一发一收 1.1.1 ClientTest1 package com.itheima.udp;import java.net.*;import static java.net.InetAddress.*;//完成udp通信快速入门&#xff0c;实现一收一发 public class ClientTest1 {public static void main(String[] args) thro…