Android逆向之指令集和CPU架构
Android逆向之必备前置知识
经过前2篇逆向前置知识的铺垫之后,我们终于要开始逆向实操了。以下操作主要是体现一下逆向的流程以及实操,为了安全考虑,并不会直接给出APP名称。
从目标出发,一步步去完成我们的目标,回头再看,大家就会发现,不过如此罢了。
获取APP中的某个功能,例如点击加载模型,模型会自带一些事件触发逻辑。博主的目的是拿到这部分事件逻辑的代码,参考一下。
例如下图的加载模型。
sudo pacman -Sy binutils python jadx radare2
sudo pip install mitmproxy
wget http://cdn3.cooolar.com/model/b/e/5/5c6a65be57f782510b26bdb4/ios_192/5c6a65be57f782510b26bdb4.zip
unzip 5c6a65be57f782510b26bdb4.zip
提示需要输入解压密码,如下所示,这似乎进一步证实该zip包就是我们需要的模型资源文件。
接下来我们需要找到该zip包解压需要的密码。
jadx -d magicxx magicxx.apk
cd magicxx
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操作。
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调用应该是和模型下载之后解压相关的。
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()**返回值。
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()函数的实现。
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函数的定义。
r2 resources/lib/armeabi-v7a/libnative-lib.so
然后我们跳转到Java_com_qjtc_magicalar_common_Constant_getZipSecret函数起始地址处, 如下所示:
💻 根据得到的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