实战APP逆向解密

1. 玩转("com.xunlei.playfarm")

VPN检测

首先是VPN检测,我们进入页面的时候会提示升级版本,我们通过抓包来查看申请抓包版本的请求,发现会导致程序崩溃,优先考虑是做了保护VPN的加固。

寻找关键代码

再一次寻找突破点:我们发现会出现提示网络错误等的Toast,那我们是不是可以查看一下对应的堆栈信息,去实现我们的关键代码的定位

  //这里记得通过自启动的方式进行注入
  	var Toast =Java.use("android.widget.Toast");
    Toast.show.implementation = function(){
        showstack();
        console.log("Toast.show.implementation");
        return this.show();
    }
/*
java.lang.Throwable
        at android.widget.Toast.show(Native Method)
        at com.jtxc.common.utils.ToastUtils$SystemToast.a(SourceFile:3)
        at com.jtxc.common.utils.ToastUtils$1.run(SourceFile:20)
        at com.jtxc.common.utils.ThreadUtils.a(SourceFile:2)
        at com.jtxc.common.utils.ToastUtils.a(SourceFile:2)
        at com.jtxc.common.utils.ToastUtils.e(SourceFile:1)
        以上是Toast对应代码
        
        at com.jtxc.common.viewmodel.BaseViewModel.b(SourceFile:20)
        at com.jtxc.common.viewmodel.AnnouncementInfoViewModel$2.accept(SourceFile:2)
        
        以下是io设备的系统的调用
        at io.reactivex.internal.subscribers.LambdaSubscriber.onError(SourceFile:3)
        at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(SourceFile:12)     
        at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(SourceFile:7)
        at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(SourceFile:5)
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(SourceFile:1)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Toast.show.implementation
*/  这里我们直接去利用Jadx看看对应的类信息

shell

结果我们并不能直接去找到对应的app对应的类和类信息,疑似是加了shell,我们通过PKID.exe去查shell

   《腾讯》加固

腾讯的壳,这里不会脱壳省略到脱壳之后

字符串定位

HOOK StringFactory()方法的newStringFromString来获取信息

    var Stringfactory = Java.use("java.lang.StringFactory");
    Stringfactory.newStringFromString.overload('java.lang.String').implementation = function(a){
        var result = this.newStringFromString(a);
        console.log("Stringfactory.newStringFromString.overload('java.lang.String'):"+result);
        return result;
    }
    Stringfactory.newStringFromChars.overload('int', 'int', '[C').implementation= function(a,b,c){
        // showstack();
        var result = this.newStringFromChars(a,b,c);
        console.log("Stringfactory.newStringFromChars.overload('int', 'int', '[C'):"+result);
        return result;
    }
//结果很乱,找不到具体很重要的参数和信息

控件利用

我们再去看看控件上面是否能够进行有用的获取,利用SDK中的uiautomatorviewer,路径:android\Sdk\tools\bin

发现我们利用抓包的过程中弹出的"网络异常"以及"检测到网络环境异常"都是使用的TextView,但是字符串生成的过程中并没有这样的字符串

所以我们只能去看看是否有利用findViewById来使用控件了

这里我们要来看看findViewById方法是存在在哪个包里面的,这里查了资料发现,利用getActivity().findViewById()和AppCompatActivity()可以找到findViewById,但是不能通过这样的路径直接获取,因为SDK以及java的解释器的版本不同对于包名可能处理不同,我们去遍历一下加载的类方法

    var classArr = Java.enumerateLoadedClassesSync();
    for(var i=0; i<classArr.length; i++) 
    {
        console.log("classname",classArr[i]);
    }
找到了"androidx.appcompat.app.AppCompatActivity"

我们利用"androidx.appcompat.app.AppCompatActivity"看看得到的ID有哪些

    var AppCompatActivity = Java.use("androidx.appcompat.app.AppCompatActivity");
    AppCompatActivity.findViewById.implementation= function(id){
        console.log("findViewById",id);
        var result= this.findViewById(id);
        console.log("result:",result);
        // showstack();
        return result;
    }
/*
result: android.widget.ViewFlipper{5bd0975 V.E...... ......I. 0,0-0,0 #7f0a023e app:id/arg}
findViewById 2131362751
result: com.jtxc.common.view.TitleBarView{fdcdd0a V.E...... ......I. 0,0-0,0 #7f0a03bf app:id/arg}
findViewById 2131362366
result: android.widget.ViewFlipper{1ab3d69 V.E...... ......I. 0,0-0,0 #7f0a023e app:id/arg}
findViewById 2131362751
result: com.jtxc.common.view.TitleBarView{ad553ee V.E...... ......I. 0,0-0,0 #7f0a03bf app:id/arg}
findViewById 2131363099
result: com.jtxc.common.view.NoScrollViewPager{79743c VFED..... ......I. 0,0-0,0 #7f0a051b app:id/arg}
findViewById 2131363099
*/ 查看一下前面几个堆栈,去找可能存在的代码
堆栈:
        at androidx.appcompat.app.AppCompatActivity.findViewById(Native Method)
        at com.jtxc.common.base.BaseActivity.onCreate(SourceFile:10)
        at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1306)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

堆栈分析

        at com.jtxc.common.base.BaseActivity.onCreate(SourceFile:10)

我们发现在这里的下端的代码中会看到我们弹出的

            if (z) {
                KillProcessDialog killProcessDialog = new KillProcessDialog();
                if (killProcessDialog.a == null) {
                    ConfirmCancelCenterPopupView confirmCancelCenterPopupView = new ConfirmCancelCenterPopupView(this);
                    confirmCancelCenterPopupView.B = "网络异常";
                    confirmCancelCenterPopupView.C = "检测到网络环境异常\n请关闭网络代理后尝试";
                    confirmCancelCenterPopupView.E = "我知道了";

z为Ture会导致这些的字符串的赋值,所以去看看怎么让z为False

if (!SmartSpUtil.isWillShowSplashAdvert()) {
            Set<Utils.Consumer<NetworkUtils.WifiScanResults>> set = NetworkUtils.a;
            boolean z = false;
            try {
                Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
                while (true) {
                    if (networkInterfaces.hasMoreElements()) {
                        NetworkInterface nextElement = networkInterfaces.nextElement();
                        /*这里是关键代码
                        if (!(nextElement == null || !nextElement.getName().equalsIgnoreCase("tun0") || (interfaceAddresses = nextElement.getInterfaceAddresses()) == null || interfaceAddresses.size() <= 0 || interfaceAddresses.get(0) == null)) {
                            z = true;
  */
                            break;
                        }
                    } else {
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

!nextElement.getName().equalsIgnoreCase("tun0") 让这里的结果为False可以试试

绕过网络错误提醒

    var String = Java.use("java.lang.String");
    var NetworkInterface = Java.use("java.net.NetworkInterface")
    NetworkInterface.getName.implementation= function()
    {
        var result= this.getName();
        if(result.indexOf("tun0")!=-1)
        {
            result= "tun1";
        }
        console.log("result:",result);
        return result
    }

这样的frida注入之后就过掉了最初的由于抓包引起的代理出现的网络错误

登录过程中的由于手机是root权限导致的提醒

我们在过掉了网络错误的提醒之后,在登录界面由于我们的手机是root权限的手机会导致被安全提醒,所以我们要绕过这个提醒。

通过Toast寻找关键代码

    var Toast =Java.use("android.widget.Toast")
    Toast.show.implementation=function(){
        var result = this.show;
        console.log(result);
        showstack();
    }

能够找到部分的相关的信息,但是很难去定位到关键代码,所以我们选择的另外寻找定位的方法

通过字符串寻找弹出的信息

   var StringBuild = Java.use("java.lang.StringBuilder");
    StringBuild.toString.implementation =function()
    {
        var result = this.toString();
        if (result.indexOf("the phone is root")!=-1)
        {
            console.log("stack");
            showstack();
        }
        console.log("StringBuild",result);
        return result
    }
    var StringBuffer = Java.use("java.lang.StringBuffer");
    StringBuffer.toString.implementation =function()
    {
        var result = this.toString();
        if (result.indexOf("the phone is root")!=-1)
        {
            console.log("stack");
            showstack();
        }
        console.log("StringBuffer",result);
        return result
    }
/*
java.lang.Throwable
	at java.lang.StringBuilder.toString(Native Method)
	at com.mobile.auth.gatewayauth.manager.SystemManager.checkEnvSafe(Native Method)
	at com.mobile.auth.gatewayauth.manager.SystemManager.a(Unknown Source:0)
	at com.mobile.auth.gatewayauth.PhoneNumberAuthHelperProxy$30.a(Unknown Source:192)
	at com.mobile.auth.gatewayauth.utils.c$b.run(Unknown Source:9)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:919)

StringBuild 手机终端不安全:the phone is root, Device has su!
*/

我们知道那个字段是"the phone is root......"所以我们按照这样的形势先去找找是否有这样的字段出发,结果发现有,于是我们就直接去获取他当时的堆栈信息了。

这里我们直接去分析一下可疑的class类

at com.mobile.auth.gatewayauth.manager.SystemManager.checkEnvSafe(Native Method)

发现这个类的方法函数是没有进行公开的,是为什么?最终都只有一行的代码

public native TokenRet checkEnvSafe(ResultCodeProcessor resultCodeProcessor);

SO层的逆向

为什么没有出现函数的具体实现过程?就是因为函数的实际代码出现在SO层上面,也就是程序是通过调用文件夹中的对应的so文件进行获取数据从而实现的程序代码的执行过程,由于并没有学习SO层代码的逆向,所以其次逆向过程告一段落,但是我们可以去看看传入的参数以及最终获取的返回值是什么。

    var SystemManager = Java.use("com.mobile.auth.gatewayauth.manager.SystemManager")
    SystemManager.checkEnvSafe.implementation =function(a)
    {
        console.log("SystemManager.checkEnvSafe's arg:",a);
        var result = this.checkEnvSafe(a);
        console.log("SystemManager.checkEnvSafe's result:",result);
        return result
    }
/*
[Pixel XL::玩转]-> SystemManager.checkEnvSafe's arg: [object Object]
SystemManager.checkEnvSafe's result: TokenRet{vendorName='unknown', code='600005', msg='手机终端不安全:the phone is root, Device has su!', carrierFailedResultData=', requestId=null', requestCode=0, token='null'}
*/

发现我们可以看到输出的字段,可以得知由于通过了这个so文件加载的函数方法使得我们最终获取了提醒的字符串

2.1905电影网

在这里先了解java自吐的注入代码自吐算法

Java.perform(function () {

    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }

    function logOutPut(msg) {
        Java.use("android.util.Log").d("xiaojianbang", "frida inject: " + msg);
    }

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag, data) {
        //logOutPut(tag + " Base64: " + ByteString.of(data).base64());
        console.log(tag + " Base64: " + ByteString.of(data).base64());
    }
    function toHex(tag, data) {
        //logOutPut(tag + " Hex: " + ByteString.of(data).hex());
        console.log(tag + " Hex: " + ByteString.of(data).hex());
    }
    function toUtf8(tag, data) {
        //logOutPut(tag + " Utf8: " + ByteString.of(data).utf8());
        console.log(tag + " Utf8: " + ByteString.of(data).utf8());
    }
    // toBase64([48,49,50,51,52]);
    // toHex([48,49,50,51,52]);
    // toUtf8([48,49,50,51,52]);
    //console.log(Java.enumerateLoadedClassesSync().join("\n"));

    var messageDigest = Java.use("java.security.MessageDigest");
    messageDigest.update.overload('byte').implementation = function (data) {
        console.log("MessageDigest.update('byte') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('[B').implementation = function (data) {
        console.log("MessageDigest.update('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.update('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    messageDigest.digest.overload().implementation = function () {
        console.log("MessageDigest.digest() is called!");
        showStacks();
        var result = this.digest();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B').implementation = function (data) {
        console.log("MessageDigest.digest('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data);
        var tags = algorithm + " digest result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data, start, length);
        var tags = algorithm + " digest result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", start, length);
        return result;
    }

    var mac = Java.use("javax.crypto.Mac");
    mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
        console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        return this.init(key, AlgorithmParameterSpec);
    }
    mac.init.overload('java.security.Key').implementation = function (key) {
        console.log("Mac.init('java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = key.getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        console.log("=======================================================");
        return this.init(key);
    }
    mac.update.overload('byte').implementation = function (data) {
        console.log("Mac.update('byte') is called!");
        return this.update(data);
    }
    mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Mac.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    mac.update.overload('[B').implementation = function (data) {
        console.log("Mac.update('[B') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Mac.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    mac.doFinal.overload().implementation = function () {
        console.log("Mac.doFinal() is called!");
        var result = this.doFinal();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }

    var cipher = Java.use("javax.crypto.Cipher");
    cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () {
        console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");
        return this.init.apply(this, arguments);
    }

    cipher.init.overload('int', 'java.security.Key').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var className = JSON.stringify(arguments[1]);
        if(className.indexOf("OpenSSLRSAPrivateKey") === -1){
            var keyBytes = arguments[1].getEncoded();
            toUtf8(tag, keyBytes);
            toHex(tag, keyBytes);
            toBase64(tag, keyBytes);
        }
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = arguments[1].getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        var tags = algorithm + " init iv";
        var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));
        var ivBytes = iv.getIV();
        toUtf8(tags, ivBytes);
        toHex(tags, ivBytes);
        toBase64(tags, ivBytes);
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }

    cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () {
        console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload().implementation = function () {
        console.log("Cipher.doFinal() is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }

    cipher.doFinal.overload('[B').implementation = function () {
        console.log("Cipher.doFinal('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        var data = arguments[0];
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.doFinal.apply(this, arguments);
        var tags = algorithm + " doFinal result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    cipher.doFinal.overload('[B', 'int', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        var data = arguments[0];
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.doFinal.apply(this, arguments);
        var tags = algorithm + " doFinal result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", arguments[1], arguments[2]);
        return result;
    }

    var signature = Java.use("java.security.Signature");
    signature.update.overload('byte').implementation = function (data) {
        console.log("Signature.update('byte') is called!");
        return this.update(data);
    }
    signature.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Signature.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Signature.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    signature.sign.overload('[B', 'int', 'int').implementation = function () {
        console.log("Signature.sign('[B', 'int', 'int') is called!");
        return this.sign.apply(this, arguments);
    }
    signature.sign.overload().implementation = function () {
        console.log("Signature.sign() is called!");
        var result = this.sign();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " sign result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
});

这里Hook的是调用标准加密算法库中的函数,并且会返回相应的返回值以及参数

抓包以及登录查看请求

//请求的raw:
GET /user/login?request=xWonulEWGml9KQbg0AsPT%2B3aehLetoBXV7d9pWqCI6yyhC7wsCVyvHeS6QpyhFkEAPqX5LWsP8c%3D HTTP/1.1
pid: 236
ver: 100/152/2016020901
Did: e46a22b94686f0019003b0443d94f1f6
osv: 29
devicemodel: Google/Pixel XL
osvr: 10
key: f25d9bd677d2230a5f5a4bad860b7cdf
Screen: 1440_2392
User-Agent: Android 10; Language/zh-cn; Pixel XL Build/QP1A.191005.007.A3 NetType/wifi
App-Conf-Code: 11
Host: mapps.m1905.cn
Connection: Keep-Alive
Accept-Encoding: gzip

//响应的Text:
tY7l4iV8+idaPCxFCfK6PPX+zM+4+Zfm/d/P0TwO+xy6vE5GsavTU27R6ZbcGIYj73VdU5FavDqe0H47XC9BO1FfeulbpgRU8F+LckEjuLbVUUktwM1FCqcqky0lRRIR4RXYGOVpdExt9X1z0qsRBLJ+UK7q8zyv8nAZYsJnPRHikdyh4dvu2uUpDm45tLujRpmf/0dLZ1HO4S/xwguNDjvwcchh8RPV9ulQG8LSLHt+baLPe1eRB8Z68eK8kQ3+Bvtemw0gMYNA5i77fvNfRVx2Q7184Lokczuaw3WVdDmgxd0avIS/mcuJsoHsrxdrALNqZgrdBD95x62Z1u5un48M7gkXkqW9omJkO/oNfiglpY9/uohu8PGSXu4ISp97GN0iz5rpfMKG9Kt419KXcMWvE9ibYAvkBmUEJdfFfSC/K9YvJp7HEbJILukF9Up5

我们通过在登录途中去注入java的自吐Hook查看一些相关的参数结果:

//Did:
MD5 update data Utf8: c08e0b3f65f48db3    //可能是手机的相关参数的结果
MD5 digest result Hex: e46a22b94686f0019003b0443d94f1f6

//key: 
MD5 update data Utf8: e46a22b94686f0019003b0443d94f1f6m1905_2014     //Did+'m1905_2014'
MD5 digest result Hex: f25d9bd677d2230a5f5a4bad860b7cdf

//request=
DESede/CBC/PKCS5Padding init Key Utf8: 32495987e8b5d1f713611481   
DESede/CBC/PKCS5Padding init iv Utf8: 83e2497b
DESede/CBC/PKCS5Padding doFinal data Utf8: username=15828712516&password=password777&bcode=
DESede/CBC/PKCS5Padding doFinal result Base64: xWonulEWGml9KQbg0AsPT+3aehLetoBXV7d9pWqCI6yyhC7wsCVyvHeS6QpyhFkEAPqX5LWsP8c=

最后是响应的Text

这是是初始解密函数的date
/*
desede/CBC/PKCS5Padding doFinal data Utf8: ����%|�'Z<,E	�<���ϸ�������<���NF���Sn����#�u]S�Z�:��~;\/A;Q_z�[�T�_�rA#���QI-��E
�*�-%E���itLm�}sҫ�~P���<��pb�g=�ܡ�����)n9���F���GKgQ��/���;�q�a����P��,{~m��{W��z�⼑
��^�
 1�@�.�~�_E\vC�|�$s;��u�t9�������ˉ���k
 
desede/CBC/PKCS5Padding doFinal data Hex: b58ee5e2257cfa275a3c2c4509f2ba3cf5fecccfb8f997e6fddfcfd13c0efb1cbabc4e46b1abd3536ed1e996dc188623ef755d53915abc3a9ed07e3b5c2f413b515f7ae95ba60454f05f8b724123b8b6d551492dc0cd450aa72a932d25451211e115d818e569744c6df57d73d2ab1104b27e50aeeaf33caff2701962c2673d11e291dca1e1dbeedae5290e6e39b4bba346999fff474b6751cee12ff1c20b8d0e3bf071c861f113d5f6e9501bc2d22c7b7e6da2cf7b579107c67af1e2bc910dfe06fb5e9b0d20318340e62efb7ef35f455c7643bd7ce0ba24733b9ac375957439a0c5dd1abc84bf99cb89b281ecaf176b00b36a660add043f79c7ad99d6ee6e9f8f0cee091792a5bda262643bfa0d7e2825a58f7fba886ef0f1925eee084a9f7b18dd22cf9ae97cc286f4ab78d7d29770c5af13d89b600be406650425d7c57d20bf2bd62f269ec711b2482ee905f54a79

desede/CBC/PKCS5Padding doFinal data Base64: 
我们发现Text的数据在这里
tY7l4iV8+idaPCxFCfK6PPX+zM+4+Zfm/d/P0TwO+xy6vE5GsavTU27R6ZbcGIYj73VdU5FavDqe0H47XC9BO1FfeulbpgRU8F+LckEjuLbVUUktwM1FCqcqky0lRRIR4RXYGOVpdExt9X1z0qsRBLJ+UK7q8zyv8nAZYsJnPRHikdyh4dvu2uUpDm45tLujRpmf/0dLZ1HO4S/xwguNDjvwcchh8RPV9ulQG8LSLHt+baLPe1eRB8Z68eK8kQ3+Bvtemw0gMYNA5i77fvNfRVx2Q7184Lokczuaw3WVdDmgxd0avIS/mcuJsoHsrxdrALNqZgrdBD95x62Z1u5un48M7gkXkqW9omJkO/oNfiglpY9/uohu8PGSXu4ISp97GN0iz5rpfMKG9Kt419KXcMWvE9ibYAvkBmUEJdfFfSC/K9YvJp7HEbJILukF9Up5
*/
这里是解密之后的结果
desede/CBC/PKCS5Padding doFinal result Hex: 
7b22726573223a7b22726573756c74223a3630302c22646964223a226534366132326239343638366630303139303033623034343364393466316636222c22736964223a22222c22756964223a22222c227365725f74696d65223a313732333632313633302c22617070636f6e666967223a7b22737461747573223a3430317d2c226d657373616765223a225c75373532385c75363233375c75353430645c75363231365c75356263365c75373830315c75393531395c75386265665c75666630635c75386266375c75393163645c75363562305c75373637625c7535663535227d2c226d657373616765223a225c75373532385c75363233375c75353430645c75363231365c75356263365c75373830315c75393531395c75386265665c75666630635c75386266375c75393163645c75363562305c75373637625c7535663535222c2264617461223a7b7d7d
desede/CBC/PKCS5Padding doFinal result Base64: eyJyZXMiOnsicmVzdWx0Ijo2MDAsImRpZCI6ImU0NmEyMmI5NDY4NmYwMDE5MDAzYjA0NDNkOTRmMWY2Iiwic2lkIjoiIiwidWlkIjoiIiwic2VyX3RpbWUiOjE3MjM2MjE2MzAsImFwcGNvbmZpZyI6eyJzdGF0dXMiOjQwMX0sIm1lc3NhZ2UiOiJcdTc1MjhcdTYyMzdcdTU0MGRcdTYyMTZcdTViYzZcdTc4MDFcdTk1MTlcdThiZWZcdWZmMGNcdThiZjdcdTkxY2RcdTY1YjBcdTc2N2JcdTVmNTUifSwibWVzc2FnZSI6Ilx1NzUyOFx1NjIzN1x1NTQwZFx1NjIxNlx1NWJjNlx1NzgwMVx1OTUxOVx1OGJlZlx1ZmYwY1x1OGJmN1x1OTFjZFx1NjViMFx1NzY3Ylx1NWY1NSIsImRhdGEiOnt9fQ==

最后对应这个HEX进行解密

{"res":{"result":600,"did":"e46a22b94686f0019003b0443d94f1f6","sid":"","uid":"","ser_time":1723621630,"appconfig":{"status":401},"message":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef\uff0c\u8bf7\u91cd\u65b0\u767b\u5f55"},"message":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef\uff0c\u8bf7\u91cd\u65b0\u767b\u5f55","data":{}}

是这样的结果可以明显的看出是Unicode编码,以u开头,然后按照两个字节为一组进行编码,最后在cyber中使用unescape string解码

小结一下

通过两次的案例,我们可以发现的是,我们需要伪造的协议头和数据头其实很多是由原本的已有的数据构成的

比如像(这样通过自吐算法得到的相关的申请的text)

POST /v5_0/user/login HTTP/1.1
codeSign: A3F4471D0E5F22479E95DC0F56DD138F
channel: qianfan
nonce: 3855a2a02bc44f3a9c9f093405ab53a5
channel: qianfan
User-Agent: QianFan;quan0715;PixelXLgoogle29;
timestamp: 1723702969458
version: 2.9
product-version: 530
platform: google PixelXL
network: 1
device: 6c0a2b3354ebf82b
screen-height: 2392
system: 2
system-version: 29


third-app-token:  
screen-width: 1440
Content-Type: application/json; charset=UTF-8
Content-Length: 66
Host: app.0715.cn
Connection: Keep-Alive
Accept-Encoding: gzip
{"password":"password777","black_box":"","username":"15828712516"}


MD5 update data Utf8: {"password":"password777","black_box":"","username":"15828712516"}3855a2a02bc44f3a9c9f093405ab53a5844e9653bd05f0687db78e96bfc5ca491723702969458

最后的数据

MD5 update data Utf8: {"password":"password777","black_box":"","username":"15828712516"}3855a2a02bc44f3a9c9f093405ab53a5844e9653bd05f0687db78e96bfc5ca491723702969458

其实可以被分为

{"password":"password777","black_box":"","username":"15828712516"} ——>输入

3855a2a02bc44f3a9c9f093405ab53a5————>nonce

844e9653bd05f0687db78e96bfc5ca49————>经过多次尝试发现是固定值

1723702969458————>timestamp

3. 指宝玩

版本更新检测

首先是去点开程序看看,能不能进行抓包之类的操作,结果发现有版本检测,会提示我们进行版本的更新,而且更新键点不了,我们尝试用jadx进行反汇编读取伪代码,定位到了Update的位置,如下:

    public void checkUpdate() {
        NetWork.getInstance().getUpdateUrl(APPUtil.getAgentId(this), MyApplication.phoneType, new OkHttpClientManager.ResultCallback<UpdateResult>() { // from class: com.hfzs.zhibaowan.ui.MainActivity.7
            @Override // com.hfzs.zhibaowan.network.OkHttpClientManager.ResultCallback
            public void onError(Request request, Exception exc) {
                MainActivity.this.showMainAdv();
            }

            public void onResponse(UpdateResult updateResult) {
                if (updateResult == null || !"1".equals(updateResult.getA())) {
                    LogUtils.m976e(updateResult.getB());
                    MainActivity.this.showMainAdv();
                    return;
                }
                try {
                    if (APPUtil.getVersionCode(MainActivity.this) < Integer.valueOf(updateResult.getC().getVersion()).intValue()) {
                        MainActivity.this.initUpdateDialog(updateResult.getC().getDownload_url(), updateResult.getC().getText());
                    } else {
                        LogUtils.m976e("你的版本已经是最新的了");
                        MainActivity.this.showMainAdv();
                    }
                } catch (Exception unused) {
                    Toast.makeText(MainActivity.this.context, "更新失败,版本号异常", 1).show();
                    MainActivity.this.showMainAdv();
                }
            }
        });
    }

可以看到这里有对于版本进行检测的一系列的操作,起初我是直接将 public void checkUpdate()直接进行全覆盖,做nothing的操作,结果发现并不能实现版本绕过,于是就往细了去看

                try {
                    if (APPUtil.getVersionCode(MainActivity.this) < Integer.valueOf(updateResult.getC().getVersion()).intValue()) {
                        MainActivity.this.initUpdateDialog(updateResult.getC().getDownload_url(), updateResult.getC().getText());
                    } else {
                        LogUtils.m976e("你的版本已经是最新的了");
                        MainActivity.this.showMainAdv();
                    }

在这里可以看到的是将自己的API中的getVersionCode中的返回值与‘updateResult.getC().getVersion()’进行比对,结果else的输出结果我们可以看到的是,假如APPUtil.getVersionCode(MainActivity.this)大于了后面的数据,就可以去跳转到版本为最新的字段,于是尝试去Hook一下APPUtil.getVersionCode()

    var checkUpdate=Java.use("com.hfzs.zhibaowan.util.APPUtil");
    checkUpdate.getVersionCode.implementation=function(){
        return 9999999;//直接返回一个大数据
    };//这里成功绕过了更新检测

登录过程

登录中的字符串输入

为了检测我们字符串输入过程中的数据,我们利用了objection的自动化Hook去查看了一下hook了getBytes()方法

objection -g com.hfzs.zhibaowan explore

发现是使用了getBytes方法去实现的字符串输入的过程,于是我们自己进行HOOK查看一下有哪些信息

    var Str = Java.use("java.lang.String");
    Str.getBytes.overload('java.lang.String').implementation=function(a){
        var result = this.getBytes();
        console.log("Str.getBytes",Str.$new(result));//这里注意一下,因为这里的值是byte[]类似于字符数组,要转为String输出好一点
        showstack();
        return result;
    }
    Str.getBytes.overload('java.nio.charset.Charset').implementation=function(a){
        var result = this.getBytes();
        console.log("Str.getBytes",Str.$new(result));//同理以上
        showstack();
        return result;
    }

代码定位

看看我们showstack()的结果

java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$4.done(AsyncTask.java:399)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
Caused by: java.lang.NullPointerException: Attempt to get length of null array
        /*at java.lang.StringFactory.newStringFromBytes(StringFactory.java:46)
        at com.hfzs.zhibaowan.util.Encrypt.encodeStr(Encrypt.java:93)
        at com.hfzs.zhibaowan.util.Encrypt.encode(Encrypt.java:23)
        at com.hfzs.zhibaowan.util.Encrypt.encode(Native Method)*/
        at com.hfzs.zhibaowan.network.GetDataImpl.doRequest(GetDataImpl.java:237)
        at com.hfzs.zhibaowan.network.GetDataImpl.requestLoginUrl(GetDataImpl.java:1017)
        at com.hfzs.zhibaowan.ui.LoginActivity$1.doInBackground(LoginActivity.java:100)
        at com.hfzs.zhibaowan.ui.LoginActivity$1.doInBackground(LoginActivity.java:96)
        at android.os.AsyncTask$3.call(AsyncTask.java:378)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        ... 4 more

去找一下关键代码,这个app是没有被加固的,发现了加密的过程

加密过程

    public static String encode(String str) {
        if (str == null) {
            return "";
        }
        String encodeStr = encodeStr(str);//这里的encodeStr是base64
        StringBuffer stringBuffer = new StringBuffer();
        char[] charArray = encodeStr.toCharArray();//获取每一个字符的数组
        stringBuffer.append("x");//首位添加x
        for (int i = 0; i < charArray.length; i++) {
            char c = charArray[i];
            char[] cArr = f2537k;
            stringBuffer.append(c + cArr[i % cArr.length]);
            stringBuffer.append("_");
        }
        stringBuffer.deleteCharAt(stringBuffer.length() - 1);
        stringBuffer.append("y");//末尾添加y
        return stringBuffer.toString();
    }
    public static String encodeStr(String str) {
        return new String(Base64.encodeBase64Chunked(str.getBytes()));
    }

这里可以看到是很简单的结果过程,base64之后的一个自的一个加密数据,我们来进行一个加密过程的编写

import java.util.Base64;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
    private static final char[] key = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '*', '!'};

    public static void main(String[] args) {
        String date="{\"appid\":\"2\",\"username\":\"15828712516\",\"password\":\"passworf777\",\"cpsId\":\"tg001lxx\",\"imei\":\"2ffe0d3b-9a69-4513-9e7f-87d4334432bb\"}";
        String base64_date =Base64.getEncoder().encodeToString(date.getBytes());
        System.out.println(base64_date);//这里是初始的base64
        StringBuilder result = new StringBuilder();
        for(int i=0;i<base64_date.length();i+=76){
            String part = base64_date.substring(i,Math.min(i+76,base64_date.length()));
            result.append(part).append("\r\n");//这里可以注意一下,由于我们的base64之后的数据其实是76为一组之后进行添加了/r/n的,所以我们也要这样
        }
        System.out.println(result.toString());
        String encode_date = encode(result.toString());
        System.out.println(encode_date);
    }
    public static String encode(String str) {
        if(str == null || str.isEmpty())
        {
            return "";
        }
        StringBuilder stringBuffer = new StringBuilder();
        char[] charArray = str.toCharArray();
        stringBuffer.append("x");
        for (int i = 0; i < charArray.length; i++) {
            char c = charArray[i];
            char[] cArr = key;
            stringBuffer.append(c+cArr[i%cArr.length]);
            stringBuffer.append("_");
        }
        stringBuffer.deleteCharAt(stringBuffer.length()-1);
        stringBuffer.append("y");
        return stringBuffer.toString();

    }
    public static String encodeStr(String str) {
        Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(str.getBytes());
    }
}

这里我们就可以得到加密之后的数据了

base64之后的结果

eyJhcHBpZCI6IjIiLCJ1c2VybmFtZSI6IjE1ODI4NzEyNTE2IiwicGFzc3dvcmQiOiJwYXNzd29y
Zjc3NyIsImNwc0lkIjoidGcwMDFseHgiLCJpbWVpIjoiMmZmZTBkM2ItOWE2OS00NTEzLTllN2Yt
ODdkNDMzNDQzMmJiIn0=

encode之后的结果

x149_170_124_155_151_125_120_167_146_124_170_152_172_206_174_207_179_171_179_155_206_158_195_231_209_221_183_230_205_199_190_172_192_226_190_171_144_134_140_120_147_192_140_193_151_158_144_126_150_183_198_185_180_153_153_206_184_137_187_206_188_199_123_138_127_154_124_170_141_141_132_177_156_107_154_219_112_110_191_208_202_155_183_227_180_223_182_219_189_231_212_162_223_223_190_224_230_225_221_193_164_185_144_136_139_185_172_144_176_179_151_143_151_190_177_167_167_194_156_190_196_191_164_197_179_199_132_117_114_156_127_101_125_169_133_142_125_107_176_181_147_148_179_186_172_226_181_190_215_216_187_160_200_228_126_124_194_184_217_225_197_188_198_244_143_134_148_190_146_179_145_177_146_184_123_137_90_88y

4.口袋48

httpcanary抓包

POST /user/api/v1/login/app/mobile HTTP/1.1
pa: MTcyNTM0Mzc5OTUxMSxlNmMxNDliZmZmZDg0MWUwYWJhYmQzMTcyODM2ZTI2OSxmZWM3OTdjOTQxNjFiNTUzMWRkYzgxOTdkNmZkNTc2MSw=
appInfo: {"IMEI":"564920aafcea5bad","appBuild":"21070602","appVersion":"6.2.0","deviceId":"564920aafcea5bad","deviceName":"Pixel XL","osType":"android","osVersion":"10","phoneName":"Pixel XL","phoneSystemVersion":"10","vendor":"Google"}
User-Agent: PocketFans201807/6.2.0_21070602 (Pixel XL:Android 10;Google QP1A.191005.007.A3)
Content-Type: application/json; charset=UTF-8
Content-Length: 44
Host: pocketapi.48.cn
Connection: Keep-Alive
Accept-Encoding: gzip
{"mobile":"15828712516","pwd":"password777"}

注意MT ey开头的数据大概是经过base64之后的结果,这里我们直接去base64解密pa的值

1725343799511,e6c149bfffd841e0ababd3172836e269,fec797c94161b5531ddc8197d6fd5761,

base64HOOK

base64HOOK的包名:

java.net.URLEncoder java.util.Base64 okio.Base64 okio.ByteString android.util.Base64.Base64

这里直接开始HOOK,发现是在android.util.Base64.Base64的库函数

    var Base64 = Java.use('android.util.Base64');
    Base64.encodeToString.overload('[B', 'int').implementation = function(input, flag){
        var tag="input data: "
        toBase64(tag,input);
        toBase64(tag,input);
        toUtf8(tag,input);
        console.log("input data: " + input);
        console.log("flag data:"+flag);
        console.log("Base64.encodeToString.'[B', 'int'");
        console.log("JSON"+JSON.stringify(input));
        var result = this.encodeToString(input,flag);
        console.log("result: " + result);
        showstack();
        return result;
    }
    Base64.encodeToString.overload('[B', 'int', 'int', 'int').implementation = function(){
        console.log("Base64.encodeToString.'[B', 'int', 'int', 'int'");
        return this.encodeToString.apply(this,arguments);
    }

结果:

[Pixel XL::口袋48]-> input data:  Base64: MTcyNTM0NzU4ODI0MywwNTA2NjJjMzNiM2M0ZWQ4YmI1ZTVjOWRhZTE1N2M4MSxjMjE1NWZkNWQ4YzBmN2ZlZjE1ZWUwNDU3YzJiNzg5Nyw=
input data:  Base64: MTcyNTM0NzU4ODI0MywwNTA2NjJjMzNiM2M0ZWQ4YmI1ZTVjOWRhZTE1N2M4MSxjMjE1NWZkNWQ4YzBmN2ZlZjE1ZWUwNDU3YzJiNzg5Nyw=
input data:  Utf8: 1725347588243,050662c33b3c4ed8bb5e5c9dae157c81,c2155fd5d8c0f7fef15ee0457c2b7897,
input data: [object Object]
flag data:2
Base64.encodeToString.'[B', 'int'
JSON[49,55,50,53,51,52,55,53,56,56,50,52,51,44,48,53,48,54,54,50,99,51,51,98,51,99,52,101,100,56,98,98,53,101,53,99,57,100,97,101,49,53,55,99,56,49,44,99,50,49,53,53,102,100,53,100,56,99,48,102,55,102,101,102,49,53,101,101,48,52,53,55,99,50,98,55,56,57,55,44]
result: MTcyNTM0NzU4ODI0MywwNTA2NjJjMzNiM2M0ZWQ4YmI1ZTVjOWRhZTE1N2M4MSxjMjE1NWZkNWQ4YzBmN2ZlZjE1ZWUwNDU3YzJiNzg5Nyw=
java.lang.Throwable
        at android.util.Base64.encodeToString(Native Method)
        at com.pocket.snh48.base.core.kotlin.net.KTokenHeaderInterceptor.intercept(TbsSdkJava:18)
        at com.pocket.snh48.activity.鱅鬚蠶矡齇鱅竈龘龘.簾籲蠶爩(TbsSdkJava:12)
        at okhttp3.logging.HttpLoggingInterceptor.intercept(TbsSdkJava:4)
        at com.pocket.snh48.activity.鱅鬚蠶矡齇鱅竈龘龘.簾籲蠶爩(TbsSdkJava:12)
        at okhttp3.internal.connection.鼕鼕鷙貜簾颱鷙龘鼕.竈貜籲糴(TbsSdkJava:16)
        at okhttp3.internal.connection.鼕鼕鷙貜簾颱鷙龘鼕$鷙籲矡.run(TbsSdkJava:6)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

函数定位

直接定位到 at com.pocket.snh48.base.core.kotlin.net.KTokenHeaderInterceptor.intercept(TbsSdkJava:18)

找到pa的生成的出处:

        String Time = String.valueOf(System.currentTimeMillis());
        String randomUUID = StringsKt__StringsJVMKt.replace$default(UUID.randomUUID().toString(), "-", "", false, 4, (Object) null);
        try {
            str = Time + ',' + randomUUID + ',' + ((Object) EncryptlibUtils.MD5(AbstractC13570.m34234(), Time, randomUUID, postkey)) + ',' + ((Object) str2);
            charset = Charsets.UTF_8;
        } catch (Exception unused) {
        }
        if (str != null) {
            r32.m28234("pa", Base64.encodeToString(str.getBytes(charset), 2));
            r32.m28234("appInfo", this.f36636);
            return r12.mo32373(r32.m28229());
数据:1725343799511,e6c149bfffd841e0ababd3172836e269,fec797c94161b5531ddc8197d6fd5761,

发现第一个数据是时间戳,第二个数据是随机数,第三个则是前面的两个参数加上一个postkey传入的md5值

查看一下能不能看到md5是不是规范的加密算法

package com.pocket.snh48.base.net.utils;
import android.content.Context;
/* compiled from: TbsSdkJava */
/* loaded from: classes2.dex */
public class EncryptlibUtils {
    static {
        System.loadLibrary("encryptlib");
    }

    public static native String MD5(Context context, String str, String str2, String str3);
}

结果是so层的逆向,libc是"encryptlib" ,现在还不会

posted @   fisherman-ovo  阅读(211)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示