smali 是Android 应用程序(*.apk)反编译生成的文件格式, 是一种类似于汇编语言的底层计算机语言。阅读和书写smali语法都需要极大的耐心和勇气, 本文将介绍如何反编译和运行第三方的程序(smali)代码。
什么是smali 文件
Android程序文件, 即apk文件, 其实是一个zip压缩包, 其文件结构如下:
其中, classes.dex是程序的核心文件,是java语言的代码编译后的二进制字节码程序。这种字节码程序是编译专供机器阅读的, 类似于汇编语言的机器码。然而如果想直接阅读这部分程序, 最好的方法就是将该文件转化为smali文件。
apktool
著名的反编译工具apktool就可以完成这部分工作。 它将classes.dex文件反编译成一堆的smali文件, 这些文件按源码的包结构保存在各自的文件夹中。如下所示:
另外, apktool 工具强大之处在于它不仅可以反编译apk文件, 而且可以根据现有的smali 文件生成新的apk文件。
这就给汉化apk或者去除apk内嵌广告提供了可能。
smali语法类似于汇编语言的语法, 涉及寄存器的直接操作, 可以直接阅读, 但羞涩难懂, 尤其是在代码混淆之后。
dex2jar
有人做了一个专门的工具dex2jar 将classes.dex 转化为jar 文件, 通过jd-gui 阅读。
但dex2jar 并不健全, 反编译出的java文件大部分都有编译错误。可以简单的阅读和分析, 但若是涉及到很细致的内容, 则还是需要依赖smali。 smali 文件的编辑器推荐使用 sublime + sublime-smali, 具体请参考文章 为Sublime Text安装smali代码语法高亮插件。
smali语法
另外,本文将涉及到smali的一些基础语法, 请参考Smali--Dalvik虚拟机指令语言-->【android_smali语法学习一】。
运行smali文件
思路
- 反编译目标apk程序(target.apk)。
- 确定需要运行的smali文件(target.smali)及依赖文件。
- 分析smali文件的类结构, 编写对应的java类文件(proxy.java)。
- 编写proxy-project, 生成新的apk程序文件(proxy.apk)。
- 反编译proxy-apk文件, 利用target.smali替换相应的smali文件(proxy.java)。
- 利用apktool 重新生成apk程序(new-proxy.apk), 安装到手机。 运行,调试。
思路说明
我们都知道, apktool工具既可以将apk文件反编译成smali文件, 也可以将修改后的smali文件build成新的可以运行的apk。 这里, 我们将构造一个代理工程proxy-project, 这个工程将用来调用target.smali文件获取结果。 调用的方式则是构建一个proxy.java 类, 它与target.smali有着相同的函数签名。 将proxy.apk反编译之后, 用target.smali替换proxy.java, 重新生成apk, 进行运行和调试。
举例
1. 构建target.apk
(这里是为了举例的需要, 通常情况下不需要这个步骤。)
首先, 模拟构建一个简单的android应用程序, 该应用程序包含一个特殊的算法函数(本次目标就是运行这个特殊算法函数)。
TargetProject 工程的结构如下:
TargetProject工程是利用Android Studio的Gradle生成, 与较早的工程结构有所不同, 但应该不会影响理解。
主界面里面的Test按钮点击时, 将运行Target.encode()函数, 并利用Toast显示计算结果。
Target类如下:
Target.encode(String str) 函数将输入的str对象进行编码,输出结果。
源码下载地址
配置好gradle 和 Android Studio后, 在工程目录下运行gradle clean build 将会自动生成./TargetProject/build/apk/TargetProject-release.apk。 下载
运行 apktool d TargetProject-release.apk release
可在release目录得到反编译后的smali文件。结构如下:
因为该apk已经过混淆和签名, 所以smali文件是混淆为a, b ...类型的类。简单的分析就知道, b.smali 就是对应源码里面的Target类。
打开b.smali 文件, 函数
.method public static a(Ljava/lang/String;)Ljava/lang/String;
即对应 Target.encode(String str)
将b.smali文件拷贝出来, 作为待运行的smali文件。下载
需要注意的是, b.smali文件的包路径是:com.example.targetproject, 这个包路径要跟后续对应的java文件的包路径一致。
2. 编写Proxy-Project
新建工程ProxyProject, 添加类com.example.targetproject.b, 如下:
package com.example.targetproject; public class b { public static String a(String str) { return ""; } }
在MainActivity添加按钮和点击事件。
package com.example.proxyproject; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.widget.Toast; import com.example.targetproject.b; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btnRun).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, b.a("反编译"), Toast.LENGTH_LONG).show(); } }); } }
编译运行, 这时点击按钮是没有内容输出的。得到ProxyProject-debug-unaligned.apk
3. 反编译ProxyProject-debug-unaligned.apk,替换, 重新编译运行。
运行
apktool d ProxyProject-debug-unaligned.apk proxy
替换/proxy/smali/com/example/targetproject/b.smali 为target.apk反编译出的b.smali
运行
apktool b proxy new.apk
这里生成的new.apk文件还没有签名。
网上这样的工具挺多的, 这里用dodo APK Sign
签名后的文件是new_signed.apk, 直接安装运行。
点击"运行...", 将弹出输出结果Toast。 大功告成!