移动端加密协议逆向实战:从Frida Hook到算法还原全解析
在移动应用安全分析与数据抓取领域,协议逆向始终是技术攻坚的核心环节。当面对一个经过多重加密的登录请求时,如何快速定位关键算法、识别加密特征并完整还原协议逻辑,成为每位逆向工程师的必修课。本文将聚焦Android平台,以某典型App登录协议为例,系统性地演示从抓包分析到SO层算法还原的全套实战方法。
1. 逆向工程环境搭建与工具链配置
工欲善其事,必先利其器。在开始逆向分析前,需要搭建稳定的调试环境并配置高效的工具组合:
基础环境要求:
- 已root的Android测试设备或模拟器(推荐Android 7.1-9.0版本)
- Python 3.8+环境用于脚本管理
- Frida 15.1.17+服务端与客户端
- IDA Pro 7.5+或Ghidra用于静态分析
- Jadx-gui或JEB用于Java层逆向
关键工具链配置技巧:
# 安装frida-tools pip install frida-tools==12.1.1 # 推送frida-server到设备 adb push frida-server-15.1.17-android-arm64 /data/local/tmp/fs adb shell "chmod 755 /data/local/tmp/fs" adb shell "/data/local/tmp/fs -l 0.0.0.0:27042"提示:为避免检测,建议修改frida-server文件名并使用非默认端口。部分厂商ROM会对/data/local/tmp目录进行监控,可考虑将服务端部署到应用私有目录。
常见检测与绕过方案对比:
| 检测类型 | 特征表现 | 绕过方案 | 成功率 |
|---|---|---|---|
| 进程名检测 | 检查frida-server | 重命名二进制文件 | 95% |
| 端口扫描 | 检测27042端口 | 修改监听端口 | 90% |
| D-Bus协议探测 | 发送认证消息验证 | Hook libc的strstr函数 | 80% |
| 内存映射检查 | 扫描maps中的frida特征 | 过滤maps文件内容 | 85% |
| 线程名检测 | 检查gum-js-loop等线程 | 修改线程名或延迟注入 | 70% |
2. 协议抓包与关键字段定位
使用Burp Suite或Charles抓取登录请求样本,典型请求示例如下:
{ "username": "testuser", "password": "FfQn1pwmgRY=", "sign": "a3d5e7f9b2c4d6a8f0e1b3c5d7e9f2a4", "captcha": "1234", "deviceId": "abcdef123456" }通过字段分析可初步判断:
password字段的Base64编码特征表明存在二次编码sign字段的32位十六进制字符串符合MD5特征- 其他字段多为明文传输
定位关键算法的四步法:
- 动态追踪:Hook系统加密函数(如OpenSSL的EVP系列)
- 调用栈分析:通过JNI接口回溯到Native层
- 字符串监控:拦截NewStringUTF等字符串转换函数
- 内存扫描:搜索特征常量(如MD5初始化向量)
3. Frida动态Hook实战技巧
3.1 JNI关键函数拦截
通过Hook JNIEnv的NewStringUTF函数,可以捕获所有从Native层返回Java层的字符串:
function hookNewStringUTF() { const libart = Process.findModuleByName("libart.so"); const symbols = libart.enumerateSymbols(); symbols.forEach(sym => { if (sym.name.includes("NewStringUTF") && !sym.name.includes("Check")) { Interceptor.attach(sym.address, { onEnter: function(args) { const str = args[1].readCString(); console.log(`[NewStringUTF] ${str}`); console.log('Call stack:\n' + Thread.backtrace(this.context, Backtracer.FUZZY) .map(DebugSymbol.fromAddress).join('\n')); } }); } }); }3.2 加密算法特征识别
当定位到可疑函数后,需要快速识别算法类型:
DES算法特征:
- 初始置换表(IP)和逆初始置换表(IP-1)
- 16轮Feistel网络结构
- 子密钥生成过程中的左循环移位
- 典型的8字节块加密
MD5算法特征:
- 四个初始化常量(A=0x67452301等)
- 64元素正弦函数表
- 四轮主循环操作(F,G,H,I函数)
- 结果拼接为128位哈希值
Hook示例(检测MD5):
function detectMD5(module) { const patterns = [ "67452301", "EFCDAB89", "98BADCFE", "10325476" ]; module.enumerateRanges('r-x').forEach(range => { Memory.scan(range.base, range.size, patterns.join(" "), { onMatch: function(address, size) { console.log(`Potential MD5 found at ${address}`); } }); }); }4. SO层算法逆向深度解析
4.1 DES加密还原实战
通过动态分析定位到加密函数后,静态逆向的关键步骤:
密钥处理分析:
- 定位密钥扩展函数(通常包含28位左移操作)
- 提取轮密钥生成逻辑
- 验证PC-1和PC-2置换表
加密模式识别:
- CBC模式会存在IV向量
- ECB模式无IV且块独立
- 观察初始xor操作判断模式
数据流追踪:
// 典型DES加密伪代码 void des_encrypt(uint8_t *plain, uint8_t *cipher, des_key *key) { uint64_t block = bytes_to_long(plain); block = initial_permutation(block); uint32_t left = block >> 32; uint32_t right = block & 0xFFFFFFFF; for (int i = 0; i < 16; i++) { uint32_t next_left = right; right = left ^ feistel(right, key->subkeys[i]); left = next_left; } block = ((uint64_t)right << 32) | left; block = final_permutation(block); long_to_bytes(block, cipher); }4.2 复合算法处理流程
现代App常采用多层加密策略,典型处理流程可能包含:
- 原始密码经过DES/CBC加密
- 输出进行Base64编码
- 与其他字段拼接后做MD5哈希
- 最终结果作为sign参数
参数提取技巧:
- 使用Frida的Memory.scan搜索硬编码密钥
- Hook libc的malloc/free分析内存分配
- 监控系统时间相关函数获取时间戳参数
5. 完整协议逆向案例演示
以某App登录协议为例,完整还原流程:
密码加密环节:
- DES密钥:"7A24432646294A"
- IV向量:0xEFCDAB9078563412
- 模式:CBC with PKCS5Padding
- 输出编码:Base64
签名生成环节:
import hashlib def generate_sign(params): components = [ params['captcha'], params['captchaId'], params['dateline'], params['deviceId'], params['info'], params['password'], params['username'], "ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po" # salt ] plaintext = ''.join(components) return hashlib.md5(plaintext.encode()).hexdigest()- 完整请求构造:
import pyDes import base64 def encrypt_password(pwd): des = pyDes.des( key="7A24432646294A", iv="\xEF\xCD\xAB\x90\x78\x56\x34\x12", mode=pyDes.CBC, padmode=pyDes.PAD_PKCS5 ) encrypted = des.encrypt(pwd) return base64.b64encode(encrypted).decode() def build_request(username, pwd): params = { "username": username, "password": encrypt_password(pwd), "deviceId": "abcdef123456", # 其他字段... } params['sign'] = generate_sign(params) return params在实际项目中,我们发现大多数加密漏洞源于以下设计缺陷:
- 使用固定IV导致CBC模式可预测
- 密钥硬编码在so文件中
- 签名salt值过于简单
- 缺乏时效性验证机制