前言
Android逆向之指令集和CPU架构
Android逆向之必备前置知识
经过前2篇逆向前置知识的铺垫之后,我们终于要开始逆向实操了。以下操作主要是体现一下逆向的流程以及实操,为了安全考虑,并不会直接给出APP名称。
从目标出发,一步步去完成我们的目标,回头再看,大家就会发现,不过如此罢了。
逆向目标
获取APP中的某个功能,例如点击加载模型,模型会自带一些事件触发逻辑。博主的目的是拿到这部分事件逻辑的代码,参考一下。
例如下图的加载模型。
环境准备
- 安装有linux系统的电脑,此处以manjaro linux 23.1.0-1为例。
- 安装有某APP的手机,此处以iPhone 13为例。
- linux系统上安装以下程序。
- 大家可以看到,安装的程序都是我们前置知识里面提过的,是必不可少的工具。
sudo pacman -Sy binutils python jadx radare2
sudo pip install mitmproxy
- 确保电脑和手机处于同一个网络,方便抓包操作。
开始逆向之旅
抓包
- 💻 在终端执行mitmweb命令, 此时会在默认浏览器中打开一个新的标签页,如下所示:
- 📱 在设置->无线局域网,点击已经连接的Wifi名称右边的编辑按钮,如下所示:
- 📱 在进入Wifi编译页之后,滚动到页面最下方,点击配置代理,选择手动,之后在出现的服务器输入框中输入电脑的IP地址,** 端口输入框中填写8080**,点击存储,如下所示:
- 📱 在默认浏览器中,此处为Safari, 打开 http://mitm.it, 滚动页面到对应平台处,此处为iOS, 按照页面提示的步骤,安装mitmproxy需要的CA证书,如下所示:
- 📱 打开 某 App,点击一个没有下载过的模型,触发下载,如下所示:
- 💻 在mitmweb打开的浏览器标签页,分析抓到的包列表,注意到有一个下载zip的请求,猜测是该模型对应的资源文件,如下所示:
- 💻 在终端执行以下命令,下载zip文件:
wget http://cdn3.cooolar.com/model/b/e/5/5c6a65be57f782510b26bdb4/ios_192/5c6a65be57f782510b26bdb4.zip
- 💻 在终端执行以下命令,解压zip文件:
unzip 5c6a65be57f782510b26bdb4.zip
提示需要输入解压密码,如下所示,这似乎进一步证实该zip包就是我们需要的模型资源文件。
接下来我们需要找到该zip包解压需要的密码。
寻找解压密码
- 💻 下载网址可以自己在网上找,当星网-绿色软件_最新绿色下载软件_免费软件下载网站 - 当星网这个网址可以下载到部分的APK文件。下载某APP的APK, 保存为 magicxx.apk。
- 💻** **终端执行以下命令,解压出APK相应的资源文件和Java源码:
- 还记得吗,就是我们前置知识中说的jadx反编译工具
jadx -d magicxx magicxx.apk
cd magicxx
- 💻 终端执行以下命令,查找zip相关的Java文件:
find sources -name "*zip*.java"
得到以下结果:
sources/net/lingala/zip4j/unzip/Unzip.java
sources/net/lingala/zip4j/unzip/UnzipEngine.java
sources/net/lingala/zip4j/unzip/UnzipUtil.java
sources/net/lingala/zip4j/model/UnzipParameters.java
sources/net/lingala/zip4j/model/UnzipEngineParameters.java
sources/com/xx/magicalar/mvp/view/model/IModelUnzipView.java
sources/okio/GzipSink.java
sources/okio/GzipSource.java
应该是使用了net.lingala.zip4j库进行unzip操作。
- 💻 查看zip4j的API文档,发现有一个setPassword函数:
- 💻 终端执行以下命令,搜索Java源码中使用setPassword函数的源码:
grep -r setPassword sources
得到以下结果:
sources/net/lingala/zip4j/core/HeaderReader.java: localFileHeader.setPassword(fileHeader.getPassword());
sources/net/lingala/zip4j/core/ZipFile.java: public void setPassword(String str) throws ZipException {
sources/net/lingala/zip4j/core/ZipFile.java: setPassword(str.toCharArray());
sources/net/lingala/zip4j/core/ZipFile.java: public void setPassword(char[] cArr) throws ZipException {
sources/net/lingala/zip4j/core/ZipFile.java: ((FileHeader) this.zipModel.getCentralDirectory().getFileHeaders().get(i)).setPassword(cArr);
sources/net/lingala/zip4j/model/ZipParameters.java: public void setPassword(String str) {
sources/net/lingala/zip4j/model/ZipParameters.java: setPassword(str.toCharArray());
sources/net/lingala/zip4j/model/ZipParameters.java: public void setPassword(char[] cArr) {
sources/net/lingala/zip4j/model/FileHeader.java: public void setPassword(char[] cArr) {
sources/net/lingala/zip4j/model/LocalFileHeader.java: public void setPassword(char[] cArr) {
sources/com/xx/magicalar/retrofitUtils/downmodel/DownLoadUtil.java: zipFile.setPassword(MD5_1.substring(MD5_1.length() - 10).toCharArray());
sources/com/xx/magicalar/retrofitUtils/scandown/ScanDownUtils.java: zipFile.setPassword(MD5_1.substring(MD5_1.length() - 10).toCharArray());
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleDrawable(int i) {
sources/android/support/design/widget/TextInputLayout.java: setPasswordVisibilityToggleDrawable(i != 0 ? AppCompatResources.getDrawable(getContext(), i) : null);
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleDrawable(Drawable drawable) {
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleContentDescription(int i) {
sources/android/support/design/widget/TextInputLayout.java: setPasswordVisibilityToggleContentDescription(i != 0 ? getResources().getText(i) : null);
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleContentDescription(CharSequence charSequence) {
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleEnabled(boolean z) {
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleTintList(ColorStateList colorStateList) {
sources/android/support/design/widget/TextInputLayout.java: public void setPasswordVisibilityToggleTintMode(PorterDuff.Mode mode) {
sources/android/support/v4/widget/ExploreByTouchHelper.java: obtain.setPassword(obtainAccessibilityNodeInfo.isPassword());
sources/android/support/v4/view/accessibility/AccessibilityRecordCompat.java: public void setPassword(boolean z) {
sources/android/support/v4/view/accessibility/AccessibilityRecordCompat.java: this.mRecord.setPassword(z);
sources/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java: public void setPassword(boolean z) {
sources/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java: this.mInfo.setPassword(z);
其中sources/com/xx/magicalar/retrofitUtils/downmodel/DownLoadUtil.java中的setPassword调用应该是和模型下载之后解压相关的。
- 💻 打开sources/com/xx/magicalar/retrofitUtils/downmodel/DownLoadUtil.java,找到setPassword调用的地方,只有436行一处调用,如下所示:
if (zipFile.isEncrypted()) {
String substring = str.substring(0, str.indexOf(".zip"));
String MD5_1 = Signature.MD5_1(new Object[]{substring.substring(substring.length() - 10), Constant.getZipSecret()});
zipFile.setPassword(MD5_1.substring(MD5_1.length() - 10).toCharArray());
}
zipFile.extractAll(str2);
看到密码是通过zip文件名以及Constant.getZipSecret()函数的返回值计算MD5得到的。其中zip文件名是5c6a65be57f782510b26bdb4.zip,还需要得到**Constant.getZipSecret()**返回值。
- 💻 终端执行以下命令,搜索Java源码中getZipSecret函数的定义:
- 这里涉及到了Android的native层,前置知识中提到过
grep -r getZipSecret sources
得到以下结果:
sources/com/xx/magicalar/retrofitUtils/downmodel/DownLoadUtil.java: String MD5_1 = Signature.MD5_1(new Object[]{substring.substring(substring.length() - 10), Constant.getZipSecret()});
sources/com/xx/magicalar/retrofitUtils/scandown/ScanDownUtils.java: String MD5_1 = Signature.MD5_1(new Object[]{substring.substring(substring.length() - 10), Constant.getZipSecret()});
sources/com/xx/magicalar/common/Constant.java: public static native String getZipSecret();
看到该函数是以native方式定义在Constant.java中的。需要去APK中的so库中寻找getZipSecret()函数的实现。
so库逆向
- 💻 在终端执行以下命令,搜索包含getZipSecret相关信息的so库:
ls resources/lib/armeabi-v7a/*.so | xargs nm -A 2>&1 | grep -v "no symbols" | c++filt | grep getZipSecret
得到以下结果:
resources/lib/armeabi-v7a/libnative-lib.so:000116c9 T Java_com_qjtc_magicalar_common_Constant_getZipSecret
在libnative-lib.so中有Java_com_xx_magicalar_common_Constant_getZipSecret函数的定义。
- 💻 在终端执行以下命令,使用radare2分析libnative-lib.so:
- 重头戏来了,二进制文件分析
r2 resources/lib/armeabi-v7a/libnative-lib.so
- 💻 在radare2程序中,依次执行以下命令
- aaaa:分析这个二进制文件
- s sym.Java_com_qjtc_magicalar_common_Constant_getZipSecret指令 :跳转到指定函数地址
然后我们跳转到Java_com_qjtc_magicalar_common_Constant_getZipSecret函数起始地址处, 如下所示:
- 💻 在radare2程序中,执行pdf指令,查看Java_com_xx_magicalar_common_Constant_getZipSecret函数的实现, 如下所示:
- 没错,开始需要汇编知识了
- 💻 分析函数代码,看到在libnative-lib.so文件的0xf8d5处有一串字符串d(EcaK#9cBQL,就是需要的secret:
解压密码生成函数
💻 根据得到的secret数据d(EcaK#9cBQL, 以及之前的相关的Java源码:
if (zipFile.isEncrypted()) {
String substring = str.substring(0, str.indexOf(".zip"));
String MD5_1 = Signature.MD5_1(new Object[]{substring.substring(substring.length() - 10), Constant.getZipSecret()});
zipFile.setPassword(MD5_1.substring(MD5_1.length() - 10).toCharArray());
}
zipFile.extractAll(str2);
重新使用Python实现如下代码,保存成unzip.py:
import hashlib
import sys
ZipSecret = 'd(EcaK#9cBQL'
def genPassword(zipFile: str) -> str:
substring = zipFile[0:zipFile.index('.zip')]
md5 = hashlib.md5((substring[-10:] + ZipSecret).encode()).hexdigest()
return md5[-10:]
if __name__ == '__main__':
print(genPassword(sys.argv[1]))
在终端执行以下代码,
python unzip.py 5c6a65be57f782510b26bdb4.zip
得到解压密码为 1cfa866766, 执行 unzip 5c6a65be57f782510b26bdb4.zip, 输入加压密码,正常解压。最终得到如下内容:
5c6a65be57f782510b26bdb4
├── 5c6a65be57f782510b26bdb4.bytes
├── 5c6a65be57f782510b26bdb4.png
└── LuaProject
└── main.lua
查看5c6a65be57f782510b26bdb4.png文件,的确是我们下载的模型的缩略图。而这个main.lua就是我们想要参考的事件逻辑代码了。
总结
是的,整体流程也许没有大家想象中的那么神秘,但我们确实是实现了最初的逆向目标。在整个流程中也用到了前面提到的必备前置知识。大家可以举一反三,比如逆向目标变成下载所有的模型?比如逆向目标变成更改源代码重新打包?比如逆向目标变成检测自己公司内APP的安全性? 等等。
逆向不是银弹,有逆向自然也有防御。逆向的目标并不是只有破坏,很多逆向操作反而是为了完善自己的安全策略。所以呢,技术没有好坏,有好坏的是使用技术的人。
再会了各位!
end