Android Decompiler / 初探反编译
0x00 TOC
- 原理
- 无混淆无加密无加壳
- 仅混淆
- 仅加密
- 仅加壳
- 自动化实现
- 一些软件
- 参考链接
0x01 原理
首先在逆向领域,有一个是需要区别的。就是反汇编和反编译。
反汇编把程序的原始机器码,翻译成较便于阅读理解的汇编代码。比如IDA、OD等。
反编译,通常是将机器码(汇编语言)转换为高级编程语言。
由于Java、.net这样的基于虚拟机技术的语言都是采用了ByteCode的二进制结构,因此很容易将ByteCode转化为“抽象语法树”(简称AST,《编译原理》这门课中的概念),然后采用反编译器就可以将AST转换为代码了。
详细可以参考乌云上的文章反编译系列教程(上)
#####0x02 无混淆无加密无加壳
一个Android程序,如果没有进行混淆,加密,加壳等行为时,如果进行反编译的话,是可以逆向到Java源码的。
先通过Dex2Jar软件将classes.dex
转换为jar文件,然后再通过Java反编译工具JD-GUI将jar文件转换成JAVA源文件。
总之这是通过dex转jar,然后再转java源代码的思路。其中dex转jar也可以选择一些其他软件,例如谷歌官方的enjarify。
下载Dex2Jar和Jd-gui的地址:
Dex2Jar:https://github.com/pxb1988/dex2jar/releases
JD-GUI:http://jd.benow.ca/
使用Dex2Jar和Jd-gui的命令:
1 | dex2jar <file0> |
以下即为反汇编出来的结果。
2.APK改之理、APKDB、Android逆向助手、Android Killer之类软件
实际本质上还是通过Dex2jar或者apktool工具的封装。
还有一个Jadx的软件也比较好用。
3.在线反编译网站
地址:http://www.decompileandroid.com/
地址:http://www.ludaima.cn/android.html
只需要上传需要反编译的apk,稍等片刻,即可下载源码。
4.smali和Baksmali以及Smali Viewer
smali是将smali文件转换成dex。
Baksmali和smali相反,将dex转换成smali。
在Quora的一篇问答中,介绍了smali和baksmali的作用。
1 | Smali/Baksmali is an assembler/disassembler for the dex format used by dalvik, |
0x03 仅混淆
通常Android混淆方法,有ProGuard、DexGuard和APKfuscator等。
怎样使用ProGuard对Android项目源码进行混淆保护,在Android Studio中build.gradle,修改以下代码片段
1 | release { |
将minifyEnabled
改为true。去除无效资源的话需要添加shrinkResources true
。
然后在proguard-rules.pro
文件中,编写一些特定框架的混淆规则。
1 | # ProGuard configurations for Bugtags |
并且混淆时需要对使用的框架进行混淆,或者是避免混淆一些类。这时就对框架的文档进行阅读,分析需要增加的混淆规则。
混淆的效果如下图所示。
以及加速gradle编译的配置:
1 | org.gradle.daemon=true |
如何对抗这种混淆呢?这种混淆仅仅是增大了阅读难度,在反汇编之后,类名变成了a,b,c之类的。
dex2jar作者在Android混淆技巧与反混淆中谈到,ProGuard有Shrinking、Optimization、Name Obfuscation、Removal of logging code等功能。在面对名字替换时,有阅读源代码、JEB、ProGuard分析等方法,Flanker大牛也分享过利用JEB API编写插件分析。
默认的混淆规则在proguard-android.txt。
可以使用Proguard再混淆一次,利用自己写的规则Mapping文件。
这里以一个文件为例。进行Proguard的混淆以及反混淆。
在Proguard配置文件中,添加如下语句,可打印出默认混淆规则的Mapping文件。
1 | -printmapping mapping.txt |
在修改好对应的值以后,利用以下语句,应用Mapping文件,再建一个项目,分析对应的代码。
1 | -applymapping mapping.txt |
最后结果如下:
这样反混淆了以后,降低了代码阅读的难度,增加了应用被破解的风险。
0x04 仅加密
加密方式有很多种,本文不能面面俱到,只能找出一些典型的。比如有流程混排加密、代码内部字符串加密,对so源码、so函数名称以及接口调用进行加密隐藏,对classes.dex中的所有函数功能代码进行提取,然后加密单独存放等。
当然如果面面俱到,文章篇幅就比较长了,并且实践、研究花费的时间也比较多。这里就举比较简单的例子。
Android签名校验:
在android程序中,可以使用以下代码进行签名的获取。
1 | public int getSignature(String packageName) { |
可以在APP每次访问服务器的时候,携带上当前APP的签名,服务端做个签名验证,如果不对,直接不通过,返回客户端信息,然后客户端进行一定的处理。当然这样的话破解者也可以通过抓包的方式,抓取到签名验证的包,然后每次通信时修改对应的包即可。
0x05 仅加壳
so加壳,upx是最为so加壳的首选,以前在PC端也做过upx的脱壳。
这里做个示例,加壳so。
在前一篇文章里,涉及到了如何在so里打log文件。在写好jni代码了以后,将so文件复制出来。
然后添加到文件夹里,如下图所示。
准备UPX壳编译环境:
1 | zlib-1.2.8.tar.gz |
编译安装:
1 | cd ucl-1.03 |
设置环境变量。
1 | export UPX_UCLDIR=/Users/qingfeng/software/upx/libs/ucl-1.03 |
查看环境变量的命令env,应用环境变量的命令source .bash_profile
。
然后在编译make all
的过程中,遇到了以下的错误。
1 | ./bele_policy.h:156:9: error: unused typedef 'acc_cta_t__39' [-Werror,-Wunused-local-typedef] |
无论是upx是3.9.1,还是最新版,无论lzma是443版本还是最新版1610,编译的时候都会出现这个问题
然后试着升级了一下g++的版本,mac自带的是4.2版本,用brew升级到了4.8,然后用zsh的alias功能(vi .zshrc)将自带的替换掉,然后再编译还是不行,最后还是使用ubuntu进行编译吧。
然后就下了一个16.04的镜像,然后跑起了虚拟机,下载并编译那些依赖,然后在upx目录下make all
的过程中,出现了以下的问题:
1 | compress.cpp:32:18: fatal error: zlib.h: No such file or directory |
没找到zlib.h
,可是自己在zlib那也make
了啊,在查找资料的过程中,试着在zlib目录make install
了一下,然后就编译完成了,在src目录下也有./upx.out
了,版本是3.9.2。
这个时候就应该考虑so的init段的问题了。据这篇文章说,加壳的文件中需要有INIT段,添加init段的代码如下:
1 | void _init(void){} \\c++ |
然后加壳的命令如下:
1 | ./upx.out -f -o libdemo_upx.so libdemo.so |
然后脱壳自然-d参数即可,需要判断标志是否为UPX!,以及处理变形等问题,脱完壳就可以使用IDA进行android的so分析了。
0x06 自动化实现
如何实现一些自动化脚本,进行反汇编的还原工作。
当然网上已经有人实现过相关内容,比如Mac下的AndroidDecompiler,或者是easy-android-decompiler。
0x07 一些软件
- Baksmali 和 Dedexer
- apktool
- JEB
- jadx
- jdgui
DEX转jar:
- enjarify
- dex2jar
app调试器:
代码混淆软件:
- Proguard
- DashO
- Dexguard
- DexProtector
- ApkProtect
- Shield4j
- Stringer
- Allitori
对抗混淆:
0x08 参考链接
当然实际情况是混淆,加密,加壳综合起来的,实际情况要具体分析。
参考链接:
Android Decompiler / 初探反编译