我几乎没做逆向:一次把抓包、help_tool、AI 提示词和 Codex 串起来的实战
这篇文章想记录的,不只是“某河南阅卷平台”的登录加密是怎么被还原出来的,更想记录另一件事:
我自己几乎没有手工做逆向分析,只是把几个线索交给 AI,然后让 Codex 直接接管电脑,把后面的事一路做完。
先说明两点:
- 平台名称、域名、手机号、密码、密钥等敏感信息均已脱敏。
- 文中的脚本是教学版脱敏脚本,域名统一替换为
https://app.test.com,不针对任何真实线上服务。
一、我自己实际只做了四件事
如果把整个过程压缩一下,我自己真正动手做的事其实非常少:
- 打开模拟器,把 App 启动起来。
- 用抓包软件拿到登录请求包。
- 用开源项目 help_tool 先跑出“可能的加密方式”和“候选 key”。
- 让另一个通用 AI 根据这些线索,帮我生成一段适合发给 Codex 的执行型提示词。
到这里为止,我还没有自己去点 jadx、没有自己翻 smali、也没有自己连 adb 看内存。
后面的工作,基本都是 Codex 自己完成的。
二、help_tool 给我的不是答案,而是“第一推动力”
我一开始并不知道 userPwd 到底是怎么来的,只知道抓包里有一个看起来像 Base64 的字段。
这时候 help_tool 给我的价值,不是直接还原出最终算法,而是给了我一个很重要的起点:
- 它提示这大概率是 AES 家族
- 它给了一个候选 key
- 它让我觉得“这条线索值得继续往代码里验证”
换句话说,help_tool 没有帮我直接得出最终结论,但它帮我把一个本来模糊的问题,压缩成了一个适合交给 AI 执行的任务。
这个阶段最关键的收获不是“我已经知道答案了”,而是:
我已经拥有了一组足够具体、足够结构化的输入,可以让 AI 去接手。
三、我交给 Codex 的,不是一个问题,而是一份任务单
接下来我没有自己继续分析,而是让另一个 AI 帮我整理了一段执行提示词,然后直接发给 Codex。
这段提示词的核心思路很简单:
- 把我已知的抓包信息整理出来
- 把 help_tool 给出的候选方向整理出来
- 明确要求 Codex 不要靠猜,而要以
Cipher.getInstance(...)为最终依据 - 明确要求它最终给出 Python 可复现脚本
下面是我当时交给 Codex 的任务单结构。出于脱敏需要,我把候选 key 做了替换,但格式和思路保持一致:
任务目标:
对当前目录中的 APK 文件进行静态分析,定位登录接口中 userPwd 的加密生成逻辑。
已知信息:
1. 登录接口:
POST /api/v2/user/Login
2. 加密结果:
userPwd 为 Base64 字符串,长度约 44 字符(解码后 32 bytes)
3. 已知候选 key:
"REDACTED_CANDIDATE_KEY"
4. 高概率算法:
AES-128 / ECB / ZeroPadding
5. 已存在中间值(32字节),说明密码在 AES 前被处理过(可能是 SHA256 或自定义逻辑)
---
执行步骤:
1. 工具准备(如本地不存在):
- 从 GitHub 下载并使用 jadx
- 从 GitHub 下载并使用 apktool
2. 反编译 APK:
- 使用 jadx 打开 APK
- 同时使用 apktool 解包
3. 全局搜索关键字:
优先级1:
- "REDACTED_CANDIDATE_KEY"
- "userPwd"
- "/api/v2/user/Login"
优先级2:
- "Cipher.getInstance"
- "SecretKeySpec"
- "doFinal"
- "Base64.encode"
优先级3:
- "MessageDigest"
- "SHA"
- "MD5"
- "digest"
4. 重点定位:
找到如下结构代码:
Cipher cipher = Cipher.getInstance(...)
SecretKeySpec key = new SecretKeySpec(...)
cipher.doFinal(...)
5. 向上追踪调用链:
必须还原完整流程:
password -> ??? -> AES -> Base64
重点找出:
“??? 这一步的实现代码”
6. 输出结果要求:
① 加密算法完整描述
② key 来源
③ 中间层处理逻辑
④ 完整 Java 调用链
⑤ 对应的 Python 还原代码(完整可运行)
注意事项:
- 不要根据密文“猜算法”
- 必须以代码中的 Cipher.getInstance 为最终依据
- 多算法匹配(DES/SM4)属于误导结果,应忽略
最终目标:
生成可用于 Python 模拟登录请求的完整加密函数。
从现在回头看,这段提示词真正有价值的地方,不是写得多“高级”,而是写得足够像一个可以执行的工单。
四、从这一刻开始,基本就是 Codex 在自己操作电脑
把任务单发出去以后,后面的流程几乎都是 Codex 自己推进的。
我印象最深的是,它不是只做“文字分析”,而是真正在电脑上把一整条链跑通了。
大致流程是这样的:
1. 它先自己准备工具
Codex 先检查本地环境,然后自己下载并解压:
jadxapktool
接着直接在当前目录开始反编译 APK、解包资源和 smali。
2. 它先按提示词的优先级去搜
最开始它就老老实实按任务单去做:
- 搜
userPwd - 搜
/api/v2/user/Login - 搜
Cipher.getInstance - 搜
MessageDigest
很快,它确认了登录接口和 LoginParams.userPwd 的赋值点,但真正的算法逻辑并没有在普通反编译结果里直接出现。
3. 它自己判断出“静态分析被壳挡住了”
这是我觉得很像“会干活的 AI”的地方。
Codex 没有在静态 dex 上死磕,而是通过反编译结果判断出:
- 这个 APK 被加壳了
- 关键方法体在静态结果里被抽空了
- 继续只看解包结果,意义不大
也就是说,它自己从“继续搜关键词”切换成了“想办法拿运行时真实代码”。
4. 它开始要求一个新前提:root 环境
这时我做的唯一一次配合,就是告诉它:
- 我已经连上一个 root 模拟器
- 目标 App 就装在这个模拟器里
然后 Codex 就开始自己用 adb 干活了。
5. 它自己连 adb,自己看进程,自己 dump dex
后面这一段,基本已经不是“我和 AI 聊天”,而是“AI 在替我操作机器”了。
它依次做了这些事:
- 连接 adb
- 启动目标 App
- 找到进程 PID
- 查看
/proc/<pid>/maps - 确认真实 dex 被加载进内存
- 直接从进程内存里把 dex dump 出来
- 修复 dump 后 dex 的 header、checksum 和 signature
- 再用
jadx和androguard去分析恢复后的真实 dex
说实话,这一步如果让我自己手工做,也不是不能做,但我大概率会在中途频繁切换工具和思路。
而 Codex 的优势是,它能一直保持问题上下文不丢。
五、真正决定胜负的,不是“找到 AES”,而是继续追到了运行时 key
恢复出真实 dex 之后,Codex 很快就把登录调用链串起来了:
登录按钮
-> BaseLoginActivity.h0()
-> e3.d.h(userNo, password)
-> LoginParams.userPwd = x5.a.b(password, j.a)
-> POST /api/v2/user/Login
再往下追,它找到了真正的算法代码:
Cipher.getInstance("AES/CBC/PKCS5Padding")到这里,最开始 help_tool 给出的“可能是 AES”这一点,算是被代码证实了。
但更有意思的是,help_tool 提供的候选方向并不等于最终答案。
因为 Codex 继续往后挖,发现还有一层更关键的事实:
j.a虽然有静态初值- 但应用启动时会在
Application.onCreate()里把它改写成运行时真实值
这就是为什么有一条客户端样本密文,一开始怎么都解不开。
不是因为算法错了,而是因为如果你只看到静态 key,就会拿错钥匙。
这一步我自己完全没有手工定位,是 Codex 继续沿着调用链、初始化逻辑和运行时赋值一路追下去,最后把这个坑找出来的。
六、最终还原出来的真实流程
最后被还原出来的流程是:
password
-> UTF-8 bytes
-> AES/CBC/PKCS5Padding
key = SHA-256(runtime_key_utf8)[:16]
iv = random 16 bytes
-> IV || ciphertext
-> Base64(NO_WRAP)
这里有两个特别值得记住的点:
1. 真正做 SHA-256 的不是密码,而是 key
一开始根据抓包去猜,很容易怀疑是:
password -> SHA256 -> AES
但代码证据告诉我们不是。
真实逻辑是:
runtime_key -> SHA256 -> 前16字节 -> AES key
password -> 直接 UTF-8 -> AES/CBC/PKCS5Padding
2. 同一个密码每次加密结果都不一样
因为 IV 是随机 16 字节,所以:
- 明文相同
- key 相同
- 但每次
userPwd仍然可能不同
这也是为什么如果只看几条抓包,很容易误判。
七、这次最让我有感触的地方:我几乎没有“亲手逆向”
如果要非常直白地总结这次过程,那就是:
我不是靠自己一点点手工翻代码,把算法抠出来的。
我是先用 help_tool 给 AI 一个起点,再用提示词把问题打包,然后让 Codex 去接管操作。
我自己做的更像是:
- 提供环境
- 提供线索
- 提供一个执行目标
真正把这些变成结果的,是 Codex 后面这一连串自动化动作:
- 下载工具
- 反编译
- 搜关键字
- 识别壳
- 切换运行时分析
- 连 adb
- dump dex
- 修复 dex
- 追调用链
- 写脚本
- 本地闭环验证
如果说过去大家理解 AI 更像“会回答问题的聊天工具”,那这次给我的感觉更像是:
只要你的任务描述足够具体,AI 就可以开始扮演一个能持续执行、能自己换路线、还能自己落脚本的操作者。
八、博客版脱敏脚本
下面放的是脱敏版、教学版脚本:
- 软件名脱敏
- 域名替换为
https://app.test.com - 手机号和密码脱敏
- 运行时 key 用占位符替代
1. 加密 / 解密辅助脚本
import base64
import hashlib
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
BASE_URL = "https://app.test.com"
LOGIN_KEY = "REDACTED_RUNTIME_KEY"
def derive_aes_key(key_str: str = LOGIN_KEY) -> bytes:
return hashlib.sha256(key_str.encode("utf-8")).digest()[:16]
def encrypt_user_pwd(password: str, key_str: str = LOGIN_KEY) -> str:
aes_key = derive_aes_key(key_str)
iv = os.urandom(16)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(password.encode("utf-8"), AES.block_size))
return base64.b64encode(iv + ciphertext).decode("ascii")
def decrypt_user_pwd(user_pwd_b64: str, key_str: str = LOGIN_KEY) -> str:
raw = base64.b64decode(user_pwd_b64)
iv = raw[:16]
ciphertext = raw[16:]
aes_key = derive_aes_key(key_str)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext), AES.block_size).decode("utf-8")2. 脱敏版模拟登录脚本
import json
import requests
import urllib3
from crypto_helper import encrypt_user_pwd
BASE_URL = "https://app.test.com"
LOGIN_API = f"{BASE_URL}/api/v2/user/Login"
HEADERS = {
"User-Agent": "okhttp/3.12.13",
"Accept-Encoding": "gzip",
"content-type": "application/json; charset=UTF-8",
}
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def build_commondata():
return json.dumps(
{
"appSelectType": 2,
"clientConfig": "REDACTED_CLIENT_CONFIG",
"deviceModel": "REDACTED_MODEL",
"systemVersion": "REDACTED_SYSTEM_VERSION",
"version": "REDACTED_VERSION",
"wyClient": 1,
},
separators=(",", ":"),
)
def login(phone: str, password: str):
headers = dict(HEADERS)
headers["commondata"] = build_commondata()
payload = {
"appSelectType": 2,
"deviceModel": "REDACTED_MODEL",
"systemVersion": "REDACTED_SYSTEM_VERSION",
"userNo": phone,
"userPwd": encrypt_user_pwd(password),
"wyClient": 1,
}
with requests.Session() as session:
session.trust_env = False
response = session.post(
LOGIN_API,
headers=headers,
json=payload,
timeout=20,
verify=False,
)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
result = login("138****0000", "******")
datas = result.get("datas") or {}
print(
json.dumps(
{
"token": datas.get("token"),
"userName": datas.get("userName"),
"unitName": datas.get("unitName"),
},
ensure_ascii=False,
indent=2,
)
)九、如果只用一句话总结这次流程
如果要我用最简单的一句话概括整个过程,那就是:
我先用抓包和 help_tool 把问题缩小到一个 AI 可执行的范围,再用另一段提示词把任务交给 Codex,然后几乎不自己分析,直接看它把整条链跑通。
对我来说,这不是“AI 帮我查了点资料”,而是:
AI 真的开始像一个会操作电脑、会换思路、会写脚本、会自己验证结果的执行者。
这也是这次经历里最让我兴奋的地方。
十、结尾
以前很多人说 AI 适合做“辅助”。
但这次我的感受更像是:
只要你前面把任务描述清楚,把已知线索组织好,把目标定义明确,后面的很多事其实已经不必再由你亲手完成。
我自己只是:
- 打开模拟器
- 抓一个登录包
- 跑一次 help_tool
- 把线索喂给 AI
剩下的逆向、定位、验证、脚本化,基本都交给了 Codex。
如果以后再做类似的分析,我大概率还会沿用这套思路:
抓包 -> help_tool 给候选方向 -> 另一个 AI 生成执行型提示词 -> Codex 全程接管
从这个角度看,这篇文章其实不只是一次逆向复盘,更像是一次“AI 如何接管技术流程”的实战记录。