女神想喝茅台?我给某东APP做了个“CT检查”
江城大学,女生宿舍楼下。
“林辰!这里!”
苏清语穿着一件宽松的卫衣,手里提着两杯奶茶,在寒风中冻得瑟瑟发抖。看到林辰走过来,她像只欢快的小兔子一样蹦了过去。
“这么冷的天,叫我出来干嘛?”林辰接过热奶茶,暖了暖手。
苏清语眨巴着大眼睛,一脸期待:“大神,还有一周就是我爸生日了。他老人家就好一口茅台,可是现在京东上的茅台太难抢了!我每天定闹钟,手都戳破了也没抢到。”
“所以……”她可怜兮兮地看着林辰,“你能不能帮帮我?我知道你最厉害了!”
林辰喝了一口奶茶,甜度刚好。
“抢茅台啊……”林辰沉吟片刻,“现在的电商 APP,尤其是涉及这种硬通货,风控等级都是最高的。不仅有滑块验证码,还有设备指纹检测,最核心的是那个叫 JDGS 的参数。”
“JDGS?”苏清语一脸茫然。
“对,那是京东 APP 的核心防御系统。所有的关键请求,比如登录、下单、抢购,都会带上一个加密参数。服务器只有校验通过了,才会处理你的请求。否则,你就算手速再快,发过去的也是废包。”
林辰看着她那副“虽不明但觉厉”的样子,笑了笑。
“走吧,去自习室。既然你想孝敬岳……咳,孝敬叔叔,那我就帮你这一次。”
……
图书馆自习室。
林辰将苏清语的安卓手机连接到电脑上,熟练地打开了 Frida 和 JADX。
“抢购的核心接口是 submitOrder。我们先抓个包看看。”
林辰操作手机尝试下单,Charles 里瞬间捕获到了请求。
POST /client.action?functionId=submitOrder...&body=...&sign=...&st=...&sv=...
“看到了吗?”林辰指着请求体里的 body 字段,“这里面有一大堆加密数据,其中最关键的就是这个 jdgs 参数。”
jdgs: 806911e114d055771f14c8bbc3f89f33...
“这个参数是动态生成的,包含设备信息、时间戳、随机数,而且经过了多重加密。如果不逆向出它的生成逻辑,就没法构造合法的抢购请求。”
【第一步:定位与 Hook】
林辰将 APK 拖入 JADX,搜索 “jdgs”。
代码被混淆得一塌糊涂,到处都是 a.b.c 这种无意义的类名。
“静态分析太累了,直接上动态插桩。”
林辰在 Frida 脚本里写了一段追踪代码:
JavaScript
functionhook_jdgs() { Java.perform(function () { // 定位到核心桥接类 Bridge var Bridge = Java.use("com.jd.security.jdguard.core.Bridge"); // Hook 它的 main 方法,这是通用的入口 Bridge["main"].implementation = function (i11, objArr) { console.log(`=== JDGS Hook 触发 ===`); console.log(`功能码 i11=${i11}`); // 打印入参for (var i = 0; i < objArr.length; i++) {if(objArr[i] != null){ console.log(`参数[${i}]: ${objArr[i].toString()}`); } } // 执行原方法let result = this["main"](i11, objArr); console.log(`返回值 result=${result}`);return result; }; });}setTimeout(hook_jdgs, 1000);
“运行。”
随着林辰按下回车,手机上再次点击抢购。
终端里瞬间刷出了绿色的日志:
=== JDGS Hook 触发 ===功能码 i11=101参数[0]: [B@8d8b455 (字节数组,疑似 url 或 body) 参数[1]: -|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§-|-§§-|-|-… (这一串乱码是什么?) 参数[2]: eidAcb388… (设备指纹 eid) 参数[3]: 1.0参数[4]: 83
返回值: {“b1″:”6bbc89…”,”b2″:”3.2.8.4…”,”b5″:”85a711be…”…}
“抓到了。”林辰眼神一亮,“核心入口就是这个 Bridge.main。入参里那个长长的乱码字符串,还有最后的 eid,都是生成签名的关键。”
【第二步:算法还原——抽丝剥茧】
“但是,光知道入口没用。我们得知道它内部是怎么算的。”林辰指着返回值里的 b5 字段,“这个 b5 看起来像是个哈希值。”
b5: 268ad327ff5fd45b8c26821f2c2b19e04aebcf68
“40 位字符,大概率是 SHA-1。”
林辰没有去硬啃 SO 库里的汇编代码,而是祭出了神器 Unidbg。
“Unidbg 可以模拟执行安卓的 SO 库,让我们像调试 Java 代码一样调试 C++ 代码。”
他快速搭建好 Unidbg 环境,加载了 libjdg.so。
“我们在内存里搜索这个 b5 的值,看看它是怎么生成的。”
通过 Trace(指令追踪),林辰很快定位到了生成 b5 的内存地址。
“看这段汇编代码。”林辰指着屏幕,“它把一段数据进行了 SHA-1 运算,然后又做了一个奇怪的 XOR(异或)操作。”
EOR W9, W10, W9 (异或指令)
“这就是它的魔改之处。它不是标准的 SHA-1,而是在结果上加了一层‘私货’。”
林辰写了一个 Python 脚本来验证这个魔改算法:
Python
def add_mix(x):# 这是从 SO 库里提取出来的固定 Key add_key = bytes.fromhex("788a0409a538ff39...") eor_key = bytes.fromhex("4c907a161bc9b669...")# 模拟它的混淆逻辑:先加后异或 out = bytearray(len(x))for i in range(len(x)): tmp = (add_key[i] + x[i]) & 0xFF out[i] = tmp ^ eor_key[i]return bytes(out)
“把这个混淆函数加上,再跑一遍标准 SHA-1……”
林辰按下回车。
屏幕上输出:268ad327ff5fd45b8c26821f2c2b19e04aebcf68。
“Bingo!b5 算法破解成功。”
【第三步:终极奥义——Zlib 压缩与 Base64 魔改】
“接下来是 b4 字段。”林辰指着那串最长的乱码,“这个看起来像是 Base64,但解出来是乱码。”
b4: vvmMfjDu/js7zqgV7iM6o5...
林辰再次利用 Unidbg 追踪。
“代码跳到了 0x152f4。这里有一个循环 XOR 操作,看起来是 RC4 加密。”
他继续追踪数据流向,发现解密后的数据被送入了一个名为 gzputc 的函数。
“gzputc?这是 Zlib 压缩库的函数!”
林辰恍然大悟:“原来它是先用 Zlib 压缩,再用 RC4 加密,最后用 Base64 编码。这套娃玩得挺溜啊。”
他迅速写出逆向脚本:
Python
# 1. Base64 解码data = base64.b64decode(b4_str)# 2. RC4 解密 (密钥是从 SO 里提取的 S-Box)data = rc4_decrypt(data, key)# 3. Zlib 解压plain_text = zlib.decompress(data)print(plain_text)
运行脚本。
屏幕上终于跳出了清晰的明文 JSON:
{"e1":"eidAcb388...","e2":"1.0§§0§§com.jingdong.app.mall...","e3":1760494305496}
“看这个 e2 字段。”林辰指着其中的 §§ 符号,“这其实是 c2a7,也就是段落符号。它是拼接出来的。”
至此,整个 JDGS 的生成逻辑,像被剥光了衣服一样展现在林辰面前。
【尾声:抢购成功】
林辰将逆向出来的算法封装成一个 Python 脚本。
“好了,苏清语。现在你的电脑已经拥有了和京东 APP 一样的‘签名权’。而且……”
林辰坏笑一下,“我可以让脚本在一秒钟内并发发送 50 次抢购请求,每次都带着合法的、不一样的 JDGS 签名。”
“这叫——影分身之术。”
……
一周后,苏清语家。
苏爸爸看着桌上两瓶崭新的飞天茅台,乐得合不拢嘴。
“哎呀!清语啊,你这运气也太好了!这现在可是硬通货啊,有钱都买不到!”
苏清语心虚地笑了笑,脑海里浮现出林辰那晚在自习室敲代码的专注侧脸。
“爸,这……这是一个朋友帮我抢的。”
“朋友?男的女的?”苏爸爸警觉地问。
“男……男的。”苏清语脸红了。
苏爸爸意味深长地看了一眼女儿,又看了一眼那两瓶茅台。
“嗯……这小伙子,技术不错。改天带回来吃顿饭。”
(第十章 完)
夜雨聆风
