为啥非得去啃这个硬骨头?
话说回来,我这人有个毛病,越是说搞不定的事儿,我越想去试试。最近有朋友去面试,回来跟我抱怨,说有个面试官特损,给了他一个国外的安卓App,要求当场汉化几个核心界面。结果朋友没搞定,面试黄了。朋友就来找我诉苦,说这玩意儿肯定有鬼,常规的反编译压根儿找不到字符串。
我一听,这不就是搞技术的人的血性被激发了吗?我直接把那个App要了过来,寻思着看看到底有多硬。我当时心想,能难住谁?不就是反编译、改资源、再打包吗?我以前也没少干这事儿。
第一脚就踢到了铁板
我先把这App拖进我的工具箱里,准备大干一场。我先是用Apktool走了个常规流程,反编译出来一看,我直接傻眼了。倒是存在,但一看里面的资源配置,全部指向了几个空的或者乱七八糟的文件。我赶紧跑到res目录,平时放字符串的values文件夹,要么是空的,要么只有几个系统默认的占位符。
我明白了,这玩意儿不是个善茬,字符串肯定没走标准资源系统,多半是硬编码或者动态加载的。我立马掏出了JADX,开始查看它的Java代码逻辑。
- 第一个线索:我发现很多核心UI相关的类,在初始化的时候,会去调用一个很奇怪的静态方法,传入一个数字ID,然后返回一串文本。
-
深挖下去:我追着这个静态方法,发现它最终指向了一个自定义的类,我把它命名为“字符串处理器”。这个处理器,它竟然是去读取App的
assets文件夹里的一个二进制文件,文件名就叫。 - 核心问题:它读取字节流之后,不是直接用,而是先进行了一次解密,好像是用了一个非常简单的异或加密(XOR)。
硬啃二进制和Smali代码
既然找到了数据源,那就好办了。我先把那个文件抠了出来。然后回到反编译出来的smali代码里,定位到那个解密函数。我得把那个异或的密钥给找出来。这玩意儿藏得特别深,被混淆得不成样子,各种跳转和无用的指令一大堆,我花了三个小时才确定了那个密钥常量。
密钥拿到手,我在本地写了个简单的Python脚本,模拟它的解密流程,把文件一跑,好家伙,一堆英文文本齐刷刷地跳了出来。那感觉,就跟挖矿挖到金子一样爽!
接下来就是体力活了。我把所有的英文文本逐条翻译成了简体中文。翻译完,我再用Python脚本,用同样的密钥,把它们加密回去,覆盖了原来的文件。数据替换这一步就完成了。
重打包:的临门一脚
所有文件都处理完毕,我重新用Apktool打包,然后用我自己的私钥重新签名。我心想这回肯定没问题了,逻辑没变,只改了数据。
结果,我安装到手机上一试,App倒是能跑,但是一打开需要汉化的那个界面,立马崩溃闪退!我当时差点砸了电脑,这都一步了,怎么还出幺蛾子?
我冷静下来,重新检查Smali。我发现虽然我没改代码逻辑,但我替换了的大小,导致它加载和内存分配的时候出了问题。后来我才意识到,我必须保证新的加密文件大小跟旧的文件一模一样,哪怕多余的地方用空格填充。
我又调整了数据文件,填充了字节,确保新旧文件尺寸吻合。重新打包、签名、安装。这回成功了!界面上的英文文字全部变成了我翻译的简体中文。
实践比理论重要一万倍
这一个“硬面试题”,我前前后后折腾了差不多两天。虽然不是真的在面试现场完成的,但这回实打实的逆向实践,比我之前看任何逆向教程都管用。我深刻体会到,很多公司为了保护自己的App,那是真下功夫,各种非标操作层出不穷。下次再遇到这种“硬”的挑战,我心里就有底了。
这个过程也提醒我,技术活,千万不能只靠书本上的标准流程,必须要有打破砂锅问到底的劲头,才能把藏起来的那些“硬”知识给挖出来。