本文章内容仅用于逆向学习,请勿用于黑产行为,如有侵权,请联系本人删除,未经本人允许,不可转载。
这是笔者第一篇关于APP逆向的文章,文笔过于青涩QwQ,还望海涵,属于入门系列文章(大师傅们请绕道QvQ)
QUIC降级:
QUIC是基于 UDP 协议 的
主流抓包工具,本质上是建立一个 HTTP/HTTPS 代理服务器。它们主要工作在 TCP 协议 之上
因此需要让它放弃使用UDP而使用TCP,因此要先降级处理
正常抓包只有okhttpd走代理,要先降级抓包,jadx搜索cronetConfig找到对应设置配置的地方,打开libageon.so并搜索字符串enable_quic可以找到处理配置的地方
然后就可以正常抓到带sig3的包了
使用reqable进行抓包
字符串搜索sig3很容易找到传入逻辑,就不多说了(在其他例子中可能会出现没找到的情况,可以通过hook hash.put或者其他可能的java方法,很喜欢通过这些方法将header放进去,例如下面这段代码)
Java层入口传入/rest/n/feed/selectionfb4e77xxxxx2541
其中/rest/n/feed/selection是API接口,fb4e77xxxxxxx2541是sig的值
sig是通过传入的request得到的
sig是通过ce6.d$d.d获取的,拿一个栗子
扔给gemini分析一下
第一部分:设备唯一标识 (Device Identifiers)
这些参数用于唯一标识一台设备,是风控最关注的部分。
第二部分:系统与环境信息 (System & Env)
这些参数描述 App 运行的软件环境。
第三部分:业务与请求参数 (Business Logic)
这些参数随用户的具体操作(如刷新首页)而变化。
然后就是一个标准MD5就能获取到649xxxxx879d(之所以跟第一次的不同是因为这不是同一个例子????)

第一个是监控计算签名耗时(可能会拿来做检测?)
第二个是关键计算sig3函数
最后走的是com.kuaishou.android.security.internal.crypto.e.c方法,但是从jadx看该java层代码被严重混淆,jadx无法正确反汇编,因此直接阅读smali
通过gemini可以初步恢复成:
经过分析调用的是com.kuaishou.android.security.internal.crypto.j.f方法,

最后走到了这个函数
是一个so层实现的函数
快手采用了非常经典的“通用分发”模式。它没有为每个功能写一个 JNI 函数
而是写了一个通用的 doCommandNative,通过整数 ID (i4) 来区分要执行什么功能
Ljava.lang.String;@8d18d16这里存储了处理过的字符串MD5(sig)
d7b7d042-d4f2-4012-be60-d97ff2429c17是快手AppKey
com.yxcorp.gifshow.App@9da18de为Native层留一个调用Java方法的对象
Hook registerNative函数获取
在libkwsgmain.so

其实就是一个goto指令,直接patch BR X9-> B 0x4631C,IDA就会自己生成一个sub_4631C函数
该软件的花指令基本都是这个,非常简单,只需要patch一个指令即可,可以写个脚本批量匹配特征值进行patch,但是我懒,所以都是手动patch
字符串没找到但是可以通过Hook的结果直接得知对应函数是sub_41680
先unidbg搭架子,把函数运行起来先
unidbg部分在网上就很多了,基本拿来都能直接用,这里就不贴了
64位想正常运行read需要将
unidbg-android/src/main/java/com/github/unidbg/virtualmodule/android/AndroidModule.java
里的throw new BackendException();改为return read(emulator, vm);才可以正常运行
发现多次运行相同的参数结果却不相同,猜测在运行过程中调用了获取时间戳的函数,增加了随机性,在分析之前需要先固定随机
修改gettimeofday64
这样每次运行的结果都是c5d4a4xxxxxxxxx77e3909c9284
目标函数存在大量ollvm混淆的代码,D-810貌似有点bug,所以我写了一个配合unidbg模拟执行然后nop掉一些无用逻辑的IDA插件
e53K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6x3h3&6W2j5#2)9J5k6o6q4G2i4K6u0r3g2s2u0S2j5$3g2o6L8r3g2S2L8R3`.`.
如果有用,球球star⭐
sub_11BDC函数
调用前:
调用后:
可以发现甚至在调用这个函数之前就已经存在结果了,那么肯定在之前就有赋值操作了!

会发现其实就是v100,但是v100没有任何直接赋值的操作!

第二个显然就不可能,第一个就是之前的原语!
我开始怀疑是不是我插件的BUG了?但是这么简单的插件怎么可能有问题呢,unidbg检测一下这个地址附近的值
根据这个trace可以找到调用逻辑
其中有几个函数的部分函数调用是不会调用的,直接可以nop
大致知道调用顺序了,那么开始分析吧,从尾巴开始往上分析
sub_3D5F4
这里的v31十分可疑

是通过23578的a2来赋值

这里会发现调用的是下面的两个函数
通过插件能首先发现具有SHA256特征,因此尝试SHA256,但与结果存在较大差异性,然后发现这个函数多传入了两个可疑参数,经过比对分析,发现是HMAC的性能优化的实现,为了避免每次计算签名都重复进行Key ^ 0x36和Key ^ 0x5C的运算,程序预先计算好了这两个状态保存在内存中,经过unidbg打印出对应的值就能看出来,密钥:vWqd4fRXxXxxxxxxxxxeRitxT7VwbK
然后就能写出下面的脚本:
一般来说如果看到是SHA256但输出却不是标准SHA256,有以下几种情况:
HMAC-SHA256(概率最高)
输入被加Salt或者对输入进行了预处理(前后加Salt、转hex、大小端、特殊字符)
魔改初始向量(最好判断)
魔改轮常量(修改K表,找0x428A2F98找不到就是K表被改了)
魔改逻辑/位移量(修改Sigma或Ch/Maj的位移数,很难判断,需要一行一行比对汇编逻辑)
输出后处理(trace一下,也好判断)
.......
然后看到03D5F4函数

这个函数感觉就是C++的某个库函数,涉及到流的操作,但是其实就是一个toHex函数
将类似0xC5->0x63 0x35
通过跟踪trace发现这是较早出现结果的地方
执行过三次这个toHex函数,第一次是HMAC-SHA256的写入,第二次也是某个算法,第三次就是最后的结果了,从最后一次看


但是发现结果是24位的显然是不对的,结果应该是48位才对,应该是后面还有拼接吧
不过这24位确实是结果
这个函数是对全局this指针的初始化,并返回this指针,经过trace发现这里返回结果[3]+8的值都是固定的22
因此0是固定的0x51412200
貌似也是固定的0x1db5ae7f
先dump参数
这个x0是第二次toHex的入参
x1是0x30就是len
x2是CRC32b_poly_Constant_57C78
那就先看x0是怎么获取的

通过sub_1E2C4函数获取
v20依赖全局变量,然后调用26A14

所以v24存储的就是我们在寻找的字符串,然后看到v24是malloc出来的堆内存0x404d3240trace看看
位于2636C函数中,初步判断为白盒AES
根据调试可以知道大致流程如下:
首先在进入这个函数之前会将HMAC-SHA256的结果进行填充\x10到0x30个字节
然后每0x10个字节进去这个函数一次,输出0x10个字节的密文
简单补个环境
$$
\begin{array}{l}\text{state} \leftarrow \text{plaintext} \\\text{AddRoundKey}(\text{state}, k_0) \\\text{for } r = 1 \dots 9 \\\quad \text{SubBytes}(\text{state}) \\\quad \text{ShiftRows}(\text{state}) \\\quad \text{MixColumns}(\text{state}) \\\quad \text{AddRoundKey}(\text{state}, k_r) \\\text{SubBytes}(\text{state}) \\\text{ShiftRows}(\text{state}) \\\text{AddRoundKey}(\text{state}, k_{10}) \\\text{ciphertext} \leftarrow \text{state}\end{array}
$$
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-12-17 16:31
被s1nec-1o编辑
,原因: