我兄弟上周拿着他那台Pixel 3问我,教兄为什么他改了APK里的弟学一个字符串,装回去就闪退。教兄我说你改了什么,弟学他说把“登录失败”改成了“登录成功”。教兄我笑了,弟学说你这不叫逆向,教兄叫改字儿。弟学真正的教兄逆向是从二进制里把逻辑挖出来,不是弟学靠十六进制编辑器瞎蒙。
他之前用MT管理器直接搜字符串,教兄找到后原地替换,弟学长度变了,教兄DEX文件里字符串索引偏移全乱,弟学不闪退才怪。教兄我让他先把APK解包,用apktool反编译成smali。他问smali是什么,我说就是Dalvik字节码的文本表示,相当于汇编之于C。他哦了一声,显然没懂。我直接扔给他一个自己写的demo,里面有个checkPassword方法,返回布尔值。让他用jadx打开看Java源码,再用apktool看smali,对照着读。
他花了半小时,终于发现smali里const/4 v0, 0x0改成0x1就能让验证通过。我说对,但这是最蠢的改法。真正的逆向是理解控制流。比如那个checkPassword里有个if-eqz跳转,你改跳转条件比改返回值更隐蔽。他问为什么,我说因为有些应用会做校验,返回值可能被二次检查。他似懂非懂,但至少知道看smali里的if指令了。
接下来我让他装Frida。他电脑是Windows,手机没root,我说那你用模拟器。他下了个雷电,结果Frida-server跑不起来,报错说架构不对。我让他adb shell getprop ro.product.cpu.abi,一看是x86。他下的Frida是arm64的,能跑才怪。换了x86版本,终于hook上了。我让他写个脚本拦截那个checkPassword,打印参数和返回值。他第一次看到运行时数据从手机里吐出来,兴奋得跟发现新大陆似的。
但问题来了,他hook的是自己写的demo,没意思。我让他去分析一个真实应用,就选了个简单的天气App。他用jadx搜“vip”,找到个isVip方法,返回false。他直接改smali,结果App启动就崩。我让他看logcat,报错说VerifyError,smali里寄存器数量不对。他改的时候多用了v1,但方法头里.registers只写了1。这种细节不踩坑记不住。
后来他学乖了,改之前先看方法头的寄存器声明。又发现那个isVip被混淆了,类名是a.b.c,方法名是a。他问怎么找,我说你搜字符串“vip”或者“premium”,看哪里引用。他搜到个资源ID,顺藤摸瓜找到调用点。这次他没直接改返回值,而是在调用处把if-eqz改成if-nez。App没崩,VIP功能出来了。他截图发我,说原来逆向就是跟编译器斗智斗勇。
我说你才刚入门。真正的硬骨头是Native层,so文件里全是ARM指令,IDA打开一看几千个函数。还有反调试、混淆、壳。他问那怎么办,我说先把Java层玩熟,再碰so。别一上来就啃加固,跟没学会走就想跑一样。他点头,说那明天开始看ARM汇编。我说行,但先把今天那个天气App的smali全注释一遍,搞清楚每个寄存器在干嘛。他骂了句脏话,但还是打开了apktool。
教人逆向最怕他急着出结果。我兄弟现在至少知道,改字符串是最低级的玩法,改逻辑才是正路。至于能不能成,看他能不能熬过看汇编看到眼瞎的那几天。