免责声明:本文仅供技术交流与逆向安全学习,涉及目标已脱敏,不提供任何破解成品下载,请支持正版。
前情提要:零基础小白,纯Vibe Coding,此前仅用AI开发过几款小玩具,后来听说AI很适合逆向就跑来玩了,随便问朋友要了个看起来小众的APP也就是某解*专家就开始了
AI工具我用的dsh(ds+glm)+Antigravity,几个模型都可以说道德感极低了,只是Antigravity有外审,容易弹安全过滤,较好解决但是恶心,胜在便宜+有多模态,可以直接操作手机,不像两个国模是瞎子
然后逆向前我安装了github上高星逆向项目rea,成功但是实际上并没有投入使用,整个逆向流程全是AI自己裸跑,不过也可以用(血的教训,skill描述写得太隐晦,我后来才发现AI一次没调用,想用skill的记得一定要在AGENTS.md写清楚!!!尤其是glm很傻,它甚至不会自己请求我升级模拟器版本,不能纯许愿,必须提醒它需要什么可以配合!!!)
先打开dsh许愿,直接一句话向ai说明需求即可:
把APK安装包喂给AI,正式开始,它很快发现这软件在用户层面的特权逻辑完全取决于本地存的一份私有配置文件,路径在 /data/data/com.***unzip.zxw***/shared_prefs/app_share_data.xml
里面的有效数据结构长这样:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="FIRST_INSTALL_CHANNEL">vivo</string>
<boolean name="is_vip" value="true" />
<int name="vip_level" value="1" />
<string name="vip_end_time">2030-01-01 00:00:00</string>
<string name="VIP_DATA">{"code":20001,"msg":"ok","data":{"activation_time":0,"code":"UNLOCK","expire_time":1893456000,"isValid":true}}</string>
</map>
经过一些环境调整(模拟器版本不匹配),AI在开着Root权限的模拟器里用ADB推了一份写着 is_vip=true 的XML进去,权限改成660,再打开官方原版软件。开屏广告直接被干没了,会员资格也马上到手,各种解压收费特权全部解开
但是做完之后,我发现非Root真机是无法使用的,非root手机有系统沙箱隔离,外面根本不可能随意把文件写进/data/data/ 目录
既然外面写不进去,我就直接把问题丢给AI:非Root手机不能随意改文件怎么办?AI给出的方案是反编译APK,在应用启动入口加一段代码,让软件冷启动的时候自己给自己写这份XML。
AI随后直接手写了一个 Smali 注入类 VipInjector.smali,挂在应用主进程入口 MyApplication.onCreate:
.class public Lcom/***unzip/zxw***/VipInjector;
.super Ljava/lang/Object;
.method public static inject(Landroid/content/Context;)V
.registers 5
:try_start_0
const-string v0, "app_share_data"
const/4 v1, 0x0
invoke-virtual {p0, v0, v1}, Landroid/content/Context;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;
move-result-object p0
invoke-interface {p0}, Landroid/content/SharedPreferences;->edit()Landroid/content/SharedPreferences$Editor;
move-result-object p0
const-string v0, "is_vip"
const/4 v1, 0x1
invoke-interface {p0, v0, v1}, Landroid/content/SharedPreferences$Editor;->putBoolean(Ljava/lang/String;Z)Landroid/content/SharedPreferences$Editor;
const-string v0, "FIRST_INSTALL_CHANNEL"
const-string v1, "vivo"
invoke-interface {p0, v0, v1}, Landroid/content/SharedPreferences$Editor;->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;
const-string v0, "VIP_DATA"
const-string v1, "{\"code\":20001,\"msg\":\"ok\",\"data\":{\"activation_time\":0,\"code\":\"UNLOCK\",\"expire_time\":1893456000,\"isValid\":true}}"
invoke-interface {p0, v0, v1}, Landroid/content/SharedPreferences$Editor;->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;
invoke-interface {p0}, Landroid/content/SharedPreferences$Editor;->commit()Z
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catch_all
:catch_all
return-void
.end method
结果一回编重打包,第一个大坑就砸过来了:应用直接打不开报错。让AI拉出十六进制一查才发现被易盾阴了——官方包里的 classes.dex 表面看只有 65KB 大小,但易盾在这个 DEX 文件的末尾(偏移 65648 后面),硬生生追加了整整 56,854,924 字节(约 56.8MB)的加密资产载荷(Payload)!普通反编译工具重新汇编的时候,根本不认 DEX 标准头之外的多余数据,把这 56.8MB 的载荷直接切断丢弃了,壳运行解密的时候找不到这块粮草当然直接暴毙。
于是AI写了个外科手术式的缝合脚本 rebuild10.py,把汇编出来的新 DEX 头部和原本提取出来的 56.8MB 密文重新拼接在一起,并且严格按照 Dalvik 格式重新计算修正文件总大小、SHA1 和 Adler32 校验和:
import struct, hashlib, zlib, zipfile
with open('classes_patched11.dex', 'rb') as f:
new_dex = f.read()
with open('extracted_classes.dex', 'rb') as f:
orig_dex = f.read()
# 截取原始 65648 偏移后的 56.8MB 密文载荷并缝合
payload = orig_dex[65648:]
combined = bytearray(new_dex + payload)
# 修正 0x20 fileSize、0x0C SHA1 摘要、0x08 Adler32 校验和
combined[32:36] = struct.pack('<I', len(combined))
combined[12:32] = hashlib.sha1(combined[32:]).digest()
combined[8:12] = struct.pack('<I', zlib.adler32(combined[12:]) & 0xffffffff)
# 重新压回 APK 替换 classes.dex
zin = zipfile.ZipFile('src.apk', 'r')
zout = zipfile.ZipFile('patched.apk', 'w')
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == 'classes.dex':
data = bytes(combined)
zout.writestr(item, data)
zin.close()
zout.close()
用签名工具重新签名安装到真机上一跑,遇到易盾闪退机制:点开软件图标启动图一闪即退,存活约 1 秒,Logcat 日志记录为 Process com.***unzip.zxw*** has died: Process exited cleanly (28),或弹出官方渠道拦截提示。
排查确认这是易盾底层的 libnesec.so 在校验 APK。它在 Native 层直接读取安装包的 v2/v3 签名块并对 ZIP 文件重新计算摘要,检测到重打包修改后即主动退出。
尝试修改签名块绕过校验无果后,思路转为内存提取:在应用被杀死前的启动窗口期,真实业务 DEX 必然已解密在内存中,可以直接在进程内部完成 Dump。
为了让非 Root 真机能读出沙箱里的文件,AI 先写了个 patch_debuggable.py 脚本,在二进制层面直接把 AndroidManifest.xml 的 android:debuggable 属性改成 1,打通了 run-as 权限通道。
接着 AI 在 MyApplication.onCreate 里注入了一个自取证探针 MemDumper.smali。因为外部 adb 抓内存会被易盾反调试直接抛 EIO 错误,但应用进程自己在内部读自己(/proc/self/mem)是完全不受限制的!探针在自杀发生前把进程卡住,飞速扫描 /proc/self/maps,把内存里大于 3MB 的所有匿名映射区段全部吐到了本地的 files/dump/ 目录。
# MemDumper 核心逻辑:读取 /proc/self/maps,把匿名内存区段写入 dump 目录
# 外部工具会遭遇 EIO 反调试,进程内自己读取 /proc/self/mem 则是畅通无阻的
通过这种方式成功从内存中 Dump 出 7 个匿名内存区段,合计约 56.8MB。易盾在内存中将 DEX 开头的魔数 dex\n 摘除,导致数据整体前移了 4 个字节。在文件头部重新插入 dex\n 后,7 个业务 DEX 修复成功,Adler32 校验全部通过。
反编译这 7 个 DEX 确认,应用的 VIP 权限和去广告逻辑完全依赖本地 XML,无云端强校验。核心问题只剩解决 Native 层(libnesec.so)的主动退出逻辑。
AI 把内存里解密后的 libnesec.so 真实代码段(736KB)拉回电脑,喂给 Ghidra 逆向分析,顺藤摸瓜找出了它杀进程的根本链路:它根本不走 libc 的普通退出函数,而是把系统调用号 0x5e(exit_group)藏在了私有的内联汇编里。
顺着调用关系,AI 精准揪出了它的 6 处处决暗哨:
第一处是位于偏移 0xb00c4 的主决策分支:
0xb00c4: cbz w0, #+0x40 ; 校验签名失败时 w0 为 0,立马跳到处决 Stub
中和手法极其干脆:直接把这条指令覆写为 0x14000001(b 0xb00c8),强制短路,不管验签结果是啥都强行放行!
除此之外,代码里还藏了 5 个间接处决跳转点:site1(0x9BF24)、site2(0x9F970)、site3(0x77E94,这里还顺手纠正了之前会话多算了 0xE000 偏移的错误)、site4(0xB0A1C),以及一个被 6 处关键模块反复调用的公共处决子程序 site5(0xB0A5C)。对付这 5 个暗哨,直接把跳转点指令替换成双字自旋死循环 0x14000000 0x14000000(也就是 ARM64 的 b . ; b .),让处决指令直接发射失败,原地失效。
为了不破坏 APK 盘面文件引起二次校验崩溃,AI 把这套拦截逻辑做成了一个运行期的 Smali 常驻守护线程 guardLoop。在 APP 启动的早期,Java 层启动后台守护线程,循环检测到 libnesec.so 载入内存后,直接往 /proc/self/mem 写入这几处补丁:
# 1. 主决策块强制放行覆写 (0xb00c4)
const-wide/32 v5, 0xb00c4
add-long/2addr v3, v5
const v7, 0x14000001
invoke-static {v3, v4, v7, v8}, Lcom/netease/nis/wrapper/MemDumper;->writeMem2(JII)Z
# 2. site1 至 site5 处决分支覆写双字死循环 (0x14000000)
# 将 0x9bf24、0x9f970、0x77e94、0xb0a1c、0xb0a5c 逐一封堵中和
这套补丁打完后,将汇编、载荷缝合、补丁注入与重签名流程整合成自动化脚本 build_vip.py:
# 1. 自动汇编注入了 VipInjector 与看门狗的 Smali 源码
# 2. 自动把 56.8MB 原始密文载荷缝合回 classes.dex 尾部并修正校验
# 3. 自动注入 libsecdec.so 支持库并调用 uber-apk-signer 签名
python build_vip.py
# 4. 一键无线推送到真机(仅本地测试,不发成品)
adb -s 192.168.x.x:5555 install -r 某解压_VIP测试.apk
把包推送到 Vivo V2352A(Android 16)非 Root 真机上启动,1 秒闪退问题解决,主进程稳定常驻,内存补丁日志全部为 true。
进入应用,无任何警告弹窗,开屏与应用内广告清空,个人中心显示永久 VIP(2030-01-01 到期),分卷压缩、密码辅助、全格式解压等高级功能正常使用。
总结:AI很神但是有的时候也是真傻……我始终不理解为什么模拟器版本不匹配,glm会去选择想办法绕过而不是直接告诉我
关于token花费,ds+glm我是用的中转站,花费大概10r,gemini四舍五入免费,ds和glm我体感差不多,gemini有多模态加分但是安全过滤扣分,真机调试的时候切gemini,内容太敏感多次爆安全过滤再切国模
还有这个软件广告真的很多,我破了会员也不知道为什么还总是从别的应用跳转进应用(即使没广告),我让AI冻结了一下才解决……也可能是我这边有别的问题,懒得管了就这样吧