猿人学 App 第二题「简单的 so 层加密」离机复现:一个改版 RC4 的完整还原
本文最后更新于:2026年9月1日 下午
[猿人学 App 逆向系列]第二篇。第二题「简单的 so 层加密」:请求参数
data由 native 加密。
这题的 so 不加壳、不混淆,可以直接反汇编读算法——是练”读一个改版 RC4”的好素材。本文记录怎么读encrypt、认出它是带 header 的改版 RC4,并用 Frida 主动调用当预言机对拍,定位两个只看反汇编容易漏的细节,最后还原成纯 Python。⚠️ 猿人学闯关 App 是面向逆向练习的 CTF 式靶场;本文仅用于授权范围内的学习研究,请遵守相关服务条款与法律。
本文目录
- 请求模型(静态反编译)
- libtwo.so 侦察:不加壳不混淆
- 读 encrypt:header + 改版 RC4
- Frida 预言机对拍,定位两个坑
- 纯 Python 还原
一、请求模型(静态反编译)
ChallengeTwoFragment 里翻页:
1 | |
NativeLib(com.yuanrenxue.challenge.two):static { System.loadLibrary("two"); },public static native byte[] encrypt(byte[], long)。核心就是 libtwo.so 的 encrypt。
二、libtwo.so 侦察:不加壳不混淆
拿 lief/capstone 一看就轻松了:libtwo.so 只有 10KB,没有 UPX!、.text 是正常指令(不是第十一题那种 udf),encrypt 是静态导出 Java_com_yuanrenxue_challenge_two_NativeLib_encrypt,函数序言干净(sub sp,sp,#0x50; stp x29,x30...)。也没有第四题那种 goron 间接跳转。直接反汇编读即可。
三、读 encrypt:header + 改版 RC4
外壳(Java_..._encrypt)逻辑很直白:
GetByteArrayElements取明文指针、GetArrayLength取长度len;- 输出长度 =
len + 32,malloc; - 调核心加密(
x0=明文, x1=输出, x2=len+32, x3=ts); NewByteArray(len+32)+SetByteArrayRegion返回。
核心(sub_c70)读下来是一个带 32 字节 header 的改版 RC4:
- **header(输出前 32 字节)= key1(16) ++ key2(16)**:
key1:由 ts 经一个 LCG 派生——x = x*1103515245 + 12345,取(x>>16)&0x7fff,正是 glibcrand()的内核;连续取 8 个、每个拆成 2 字节,凑 16 字节。key2:std::random_device生成的 16 随机字节。
- KSA:
S=0..255,先用 key1 跑 768 次(= 3 遍),再用 key2 跑 768 次; - PRGA:对明文逐字节异或 keystream。
- 两把 key 都写进 header 发给服务器 —— 服务器据此重建同一个 S 盒、还原 keystream 解密(类似”把密钥material 摊在包头”的思路)。
KSA / PRGA 的 j 更新,都比标准 RC4 多一层 S 盒查表:
1 | |
四、Frida 预言机对拍,定位两个坑
只看反汇编,有两处极易翻车。这题 so 就在真机上跑着,直接 Frida 主动调用 encrypt 当预言机最省事:拿到 env(JavaVM → AttachCurrentThread),用 NewByteArray/SetByteArrayRegion 造入参,调 Java_..._encrypt(env, 0, jarr, ts),再 GetByteArrayElements 读返回——喂任意 (明文, ts) 拿真机输出,和 Python 逐字节对拍。
对拍逼出两个细节:
- KSA 的
j也带 S[] 包裹:反汇编里and w9,#0xff后还有一条ldrb w9,[x8,w9](x8是 S 盒基址)。漏掉它,header 对、但密文全错。 - key1 不能乱填:一开始以为两把 key 都能随机(都在 header 里嘛),结果服务器回
status=2。原因是服务器用明文里的 ts 重算 key1、和 header 里的 key1 比对——所以key1必须由 ts 经那套 LCG 派生;只有key2能随机。
对齐这两点后,Python 输出与真机 encrypt 逐字节一致,服务器也返回 status=1。
五、纯 Python 还原
1 | |
到这一步就完全离机:不碰手机、纯 Python 复刻 native 的改版 RC4 即可逐页取数。key2 随机、ts 也不卡新鲜度,只要 key1 与 ts 自洽即可。
小结
“so 层加密”听着唬人,实际这题 so 既不加壳也不混淆,encrypt 是个可直读的改版 RC4。真正的经验是:别只信眼睛——so 就在真机上,Frida 主动调用当预言机对拍,几秒就能揪出”KSA 少一层 S[] 包裹””key1 必须由 ts 派生”这种纯看反汇编容易漏的细节。往后第十一题会遇到加壳(读不了就 dump 内存),第四题会遇到 goron(读不动就 Unicorn 模拟),本题则是”读得动,但要对拍验证”的典型。