首页
论坛
课程
招聘
[原创]知乎分析
2022-4-5 18:28 17052

[原创]知乎分析

2022-4-5 18:28
17052

# 摘要 

分析某乎 是因为有一老乡,刚学unidbg遇到某乎一直没办法正常运行、报错。问了我一句,我出于好奇于是想着帮他看看是哪里出的问题。


# 1.抓包看看某乎在接口上有啥需要特别关注的地方

多次抓包发现每次这个x-zse-96一直是变化的 于是去代码里找找看看具体在哪里。

很好,找了个寂寞,果然没那么简单。看了下网络框架用的okhttp,那么请求头正常来说是放在拦截器中添加的,于是搜索了一下。

找到个感觉有点像的某乎包名下的net包,正常没有混淆的网络请求包基本上也采用这种命名方式,而且也有使用拦截器,点进去看看。最终在这个net包下找到这个类名为j

这个方法为添加请求头方法,怎么定位到的呢?这里的把请求头key做了一个加密。通过frida 去hook 这个方法拿到了x-zse-96 对应的加密串为G51CEEF09BA7DF27F  frida hook结果如下:

所以可以定位到了这个位置就是添加请求头的位置 从这里开始找该key 对应的value 是如何生成的。


value的值为 1.0_S85l858ic01VjwtX/xa0+BWZPtg5JRUKVGz3uz0XgD+JXTufPwrDK4sClacJhLk3 由两部分组成,

```
 H.d("G38CD8525") + new String(this.f61745c.encode(encrypt)))
```

G38CD8525  代表的值为1.0_  

后面这串字符串是上面这个encrypt方法返回,具体分析这个方法。

具体加密为这个c类 的a 方法  


参数1:待加密的数组


参数2:一串固定的字符串 通过H.d()方法解密后得到  Aes 的key值


541a3a5896fbefd351917c8251328a236a7efbf27d0fad8283ef59ef07aa386dbb2b1fcbba167135d575877ba0205a02f0aac2d31957bc7f028ed5888d4bbe69ed6768efc15ab703dc0f406b301845a0a64cf3c427c82870053bd7ba6721649c3a9aca8c3c31710a6be5ce71e4686842732d9314d6898cc3fdca075db46d1ccf3a7f9b20615f4a303c5235bd02c5cdc791eb123b9d9f7e72e954de3bcbf7d314064a1eced78d13679d040dd4080640d18c37bbde


参数3:固定的byte数组  16位 看着像Aes 的IV值

```
new byte[]{102, 48, 53, 53, 49, 56, 53, 54, 97, 97, 53, 55, 53, 102, 97, 97}
```

接下来开始分析 b.b() 的入参1  也就是CryptoTool.laesEncryptByteArr() 方法的返回值

这里可以到b.b()方法 嵌套了几层


1.首先先b.a()方法计算CryptoTool.laesEncryptByteArr() 入参1


2.CryptoTool.laesEncryptByteArr()返回值 又作为b.b()方法的入参1


这里其实看看b.a() 、b.b() 方法都可以看到完整的java 实现代码 这里就不分析了,主要看CryptoTool.laesEncryptByteArr()这个方法 因为是 native方法 所以这里直接看下frida hook的结果


把上面这三个方法都hook 一遍看看结果吧 frida  hook 代码也送上

~~~
function hookB(){
    Java.perform(function(){
        var B = Java.use("com.bangcle.b")
        B.a.overload('[B', 'java.lang.String', '[B').implementation = function(arg1,arg2,arg3){
            // b.a()方法的返回值 是 native方法CryptoTool.laesEncryptByteArr()入参 1
            console.log('B.a 参数1 ',bytes2hexstr_1(arg1));
            var result = this.a(arg1,arg2,arg3)
            console.log("B.a 返回值为 : ",bytes2hexstr_1(result))
            return result
        }
        var CryptoTool = Java.use('com.bangcle.CryptoTool');
        CryptoTool.laesEncryptByteArr.overload('[B','java.lang.String','[B').implementation = function (arg1,arg2,arg3) {
            console.log('---hook CryptoTool.laesEncryptByteArr---');
            //printstack();
            // arg:传入的是加密前数据
            console.log('CryptoTool 参数1 ',bytes2hexstr_1(arg1));
            var ret = this.laesEncryptByteArr(arg1,arg2,arg3);
            console.log('CryptoTool 返回值 ',bytes2hexstr_1(ret));
            return ret;
        } 
        B.b.overload('[B', 'java.lang.String', '[B').implementation  = function(arg1,arg2,arg3){
            // b.b()方法的arg1 是 native方法CryptoTool.laesEncryptByteArr()的返回值
            console.log("B.b 入参1为 : ",bytes2hexstr_1(arg1))
            var result = this.b(arg1,arg2,arg3)
            console.log("B.b 结果为 : ",bytes2hexstr_1(result))
            return result
        } 

    })
}
function bytes2hexstr_1(arrBytes) {
    var str = "";
    for (var i = 0; i < arrBytes.length; i++) {
        var tmp;
        var num = arrBytes[i];
        if (num < 0) {
            tmp = (255 + num + 1).toString(16);
        } else {
            tmp = num.toString(16);
        }
        if (tmp.length == 1) {
            tmp = "0" + tmp;
        }
        str += tmp;
    }
    return str;
 }
~~~

因为项目需要使用到unidbg 黑盒调用,所以这里我们需要使用unidbg来调用 so 的 CryptoTool.laesEncryptByteArr()这个方法返回值给上面截图的b.b()的入参 1进行比对 也就是返回值 必须为下面的值才是正确的

~~~
4d4e2f66e5e7d7cfb4602ddfdf8f82531e8f31be8534921f2df2a281685b8e68ab29cdc87c9a8ac8d4883dd6e865d73d
~~~

# 2.unidbg 调用

ida 打开libbangcle_crypto_tool.so 找到 laesEncryptByteArr() 很明显不是动态注册的,双击然后到混编区域 ,全是这种%1 加密了 。如下图:

上大杀器,用yang神的frida-dump工具重新导入修复 后 的 so包

可以正常看了,F5一波看伪代码


接下来unidbg 开始调用so包 补环境过程就不说了 很简单就是补一个包名就好 具体包名在清单文件中可以找到

```
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
    switch (signature){
        case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":{
            return new StringObject(vm,"com.xxx.android");
        }
    }
    return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
```

开始调用这个laesEncryptByteArr()这个方法

提示报错,把日志设置为Debug以后在运行一遍

也是运行到这里开始报错了 ,那么分析就从0xa7d4开始。看ida 跳转到这个地址 F5查看伪代码。


我这边是先 Y键修改下参数类型,这样代码好看些。主要看看这个加密方法调用的时候哪个位置出现的问题。

这里主要看sub_8E74 函数 里的init()方法 因为刚才unidbg调用执行到 这里时候出现的问题怎么看出来的呢?上面截图有执行 unidbg调用的执行过程。如下图:

init方法里面检测包名和检测md5签名

因为我们之前补环境的时候已经补了包名了,但是这里调用记录提示是在获取包名的时候导致程序出错的,这里先断点看看check_package_name方法执行过程

通过不断的s 单步调试找到了出问题的点 如图

这有点懵逼了这个位置报错,找了很久也没发现是出错的原因、所以这里采用了patch 的方式直接nop掉这个init方法,代码如下:

~~~
private void patchInit(){
        try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)){
            KeystoneEncoded assemble = keystone.assemble("nop;nop");
            byte[] machineCode = assemble.getMachineCode();            	emulator.getMemory().pointer(module.base+0x8EBC).write(0,machineCode,0,machineCode.length);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
~~~

重新运行一次在看看情况、如下图:

成功断下来后 s 继续单步执行  发现程序走的loc_91FC,接下来分析程序走的哪个case代码块。

当执行到R3=0x4的时候这时候遇到b跳转指令那么可知道是执行case 4  的判断,那么继续分析


查看伪代码找到case4 调用的方法如下,前面程序返回错误码也是这个方法返回的,那么具体分析下这个方法。


根据分析ida 汇编执行只有当0x4E88  R3不等于0的情况下程序才会继续往下走,否则直接返回错误码6 具体


如下图:

那么这里最快的解决办法就是把这个R3的值改为大于0 比如说1,程序是不是就可以继续执行了呢?尝试下,因为这if有两个判断条件那么需要 patch两个位置将R3的值都统一改为1,patch 代码如下:

~~~
KeystoneEncoded encoded = keystone.assemble("mov r3, 1");
            byte[] patchCode = encoded.getMachineCode();
            androidEmulator.getMemory().pointer(moduleModule.base + 0x4e70).write(0, patchCode, 0, patchCode.length);
            KeystoneEncoded encoded1 = keystone.assemble("mov r3, 1");
            byte[] patchCode1 = encoded1.getMachineCode();
            androidEmulator.getMemory().pointer(moduleModule.base + 0x4e84).write(0, patchCode1, 0, patchCode1.length);
~~~

patch后重新运行能正确拿到结果了截图如下:

至于后面的具体计算也就简单了有现成的java代码copy一下就算出来,这里就不详细说明了.这里验证有个问题就是最好是hook拿到b.a()方法的入参1后用copy出来的b.a() java代码进行计算拿到返回值后在调用CryptoTool.laesEncryptByteArr() 这样后方便些。我也是踩坑了几次才知道用这种方式验证结果最快。


至于算法还原,就留到以后在出吧。



[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~

最后于 2022-4-5 18:29 被那年没下雪编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (11)
雪    币: 324
活跃值: 活跃值 (1737)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Zard_ 活跃值 2022-4-5 18:54
2
0
unidbg是个好东东 值得深入学习
雪    币: 220
活跃值: 活跃值 (126)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
james_mvb 活跃值 2022-4-6 10:11
3
0
牛掰啊!unidbg大法真不错
雪    币: 4360
活跃值: 活跃值 (2607)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
王cb 活跃值 6 2022-4-6 10:53
4
0
写的不错
雪    币: 1920
活跃值: 活跃值 (534)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Tajang 活跃值 2022-4-6 16:15
5
0
牛逼,大佬太强了,unidbg那里的调用用的着实细节!
雪    币: 527
活跃值: 活跃值 (2348)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小黄鸭爱学习 活跃值 2022-4-6 16:18
6
0
好文
雪    币: 527
活跃值: 活跃值 (2348)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小黄鸭爱学习 活跃值 2022-4-6 16:24
7
0

apk哪个版本

最后于 2022-4-6 16:24 被小黄鸭爱学习编辑 ,原因:
雪    币: 1050
活跃值: 活跃值 (2678)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
那年没下雪 活跃值 1 2022-4-6 16:37
8
0
小黄鸭爱学习 apk哪个版本
8.10版本的上个月的
雪    币: 514
活跃值: 活跃值 (130)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhenzhentx 活跃值 2022-4-6 23:50
9
0
厉害,要多看几遍消化一下
雪    币: 318
活跃值: 活跃值 (534)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
浮夸进进 活跃值 2022-4-7 14:04
10
0
大佬您好,请问您用unidbg调用so,是APK原so,还是dump修复后的so啊?
雪    币: 220
活跃值: 活跃值 (126)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
james_mvb 活跃值 2022-4-9 08:31
11
0
浮夸进进 大佬您好,请问您用unidbg调用so,是APK原so,还是dump修复后的so啊?
一看 就是原so呀
雪    币: 200
活跃值: 活跃值 (18)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_czlpsxqq 活跃值 2022-6-30 12:48
12
0
大佬,牛逼................................................
游客
登录 | 注册 方可回帖
返回