Github原文
smali/baksmali 是Android的Java VM实现dalvik使用的dex格式的汇编程序/反汇编程序。 语法松散地基于Jasmin/ dedexer的语法,并支持dex格式的全部功能(注释,调试信息,行信息等)
这可能是一份不完整的清单。如果您遇到遗漏的内容,请随时发表评论,我会将其添加
其他所有内容都应该通过gradle / gradlew下载
git clone https://github.com/JesusFreke/smali.git
cd smali
./gradlew build
(windows上,使用gradlew.bat 代替**./gradlew**)
生成的jar包位置
smali/build/libs/smali-.jar
baksmali/build/libs/baksmali-.jar
除此之外,通过
./gradlew proguard
可以构建出体积更小混淆后的jar文件
smali/build/libs/smali--small.jar
baksmali/build/libs/baksmali--small.jar
从v2.1.0开始,baksmali支持反编译ART oat文件。 支持的最低oat版本为56,也就是Android 6.0 / Marshmallow版本中的oat文件版本。 由于与字段排序有关的一些潜在问题,暂不支持M版本之前的oat文件反编译(例如Lollipop)。
你可以在oat文件上运行baksmali以获取其中所有odex文件的列表。
> adb pull /system/framework/arm/boot.oat boot.oat
> baksmali boot.oat
boot.oat contains multiple dex files. You must specify which one to disassemble with the -e option
Valid entries include:
/system/framework/core-libart.jar
/system/framework/conscrypt.jar
/system/framework/okhttp.jar
/system/framework/core-junit.jar
/system/framework/bouncycastle.jar
/system/framework/ext.jar
/system/framework/framework.jar
/system/framework/framework.jar:classes2.dex
/system/framework/telephony-common.jar
/system/framework/voip-common.jar
/system/framework/ims-common.jar
/system/framework/mms-common.jar
/system/framework/android.policy.jar
/system/framework/apache-xml.jar
通过如下操作我们可以反编译boot.oat中的framework jar文件
> adb pull /system/framework/arm/boot.oat /tmp/framework/boot.oat
> baksmali -x -c boot.oat -d /tmp/framework -e /system/framework/framework.jar /tmp/framework/boot.oat -o framework
或者通过如下方式反编译一个应用程序oat
> adb pull /system/framework/arm/boot.oat /tmp/framework/boot.oat
> adb pull /system/app/Calculator/oat/arm/Calculator.odex Calculator.odex
> baksmali -x -c boot.oat -d /tmp/framework Calculator.odex -o Calculator
注意,在反编译oat文件时不需要-a选项来指定API Level,baksmali会自动使用oat文件中的API Level。
从v1.4.0开始,deodexing过程已经大大简化了,不再需要通过-c选项指定其他类路径。
Deodexing现在很简单
baksmali -a -x -d
简而言之,odex文件就是classes.dex文件的优化版本,针对特定设备的优化。 值得一提的是,odex文件依赖于生成时加载的每个“BOOTCLASSPATH”文件。 odex文件仅在与这些确切的BOOTCLASSPATH文件一起使用时才有效。 dalvik通过存储odex文件所依赖的每个文件的校验和来强制执行此操作,并确保在加载odex文件时每个文件的校验和匹配。
除了加载的主apk / jar之外,BOOTCLASSPATH只是可以加载类的jars / apk列表而已。 一般android系统的基础BOOTCLASSPATH中包含5个jar包 - core.jar,ext.jar,framework.jar,android.policy.jar和services.jar。 这些都可以在/ system / framework中找到。 但是,有些apks依赖于额外的jar或apks文件。 例如,对于使用谷歌地图的应用程序,com.google.android.maps.jar将附加到该应用程序的apk的BOOTCLASSPATH中。
下面这几个由于odex的依赖性造成的问题有时会让事情变得比较麻烦。 首先 - 你不能从一个系统镜像中获取apk + odex文件并在另一个系统映像上运行它(除非其他系统映像使用完全相同的framework文件)。 另一个问题是,如果对任何BOOTCLASSPATH文件进行任何更改,它将使依赖于该文件的每个odex无效 - 基本上每个设备上的apk / jar都会失效。
以下示例默认你使用下载页面上提供的baksmali wrapper脚本来调用baksmali。 如果你是直接调用jar,可以将“baksmali”替换为“java -jar baksmali.jar”
deodexing的两个主要选项是-x和-d。 -x是告诉baksmali你想要deodex的对象,-d告诉baksmali从哪里加载依赖项。
依赖内容可以是odex文件的形式,也可以是包含classes.dex文件的jar / apk。两者皆可。
例如,你想从ICS镜像中反编译Calculator.odex文件并且所有的framework odex文件和jar包都放在一个名为framework的文件夹中,你就可以通过如下方式操作:
baksmali -a 15 -x Calculator.odex -d framework -o Calculator
反编译Calculator.odex,并将生成的deodexed smali文件放入名为Calculator的目录中。
你可能遇到的一个问题是它是否已用完堆内存。 该错误可能类似于“java.lang.OutOfMemoryError:Java heap space”。 要解决此问题,可以使用-Xmx参数来增加堆大小。 尝试将其设置为512m。 当然,如果需要,你可以进一步增加它。 如果你使用wrapper script来调用baksmali,则可以使用-JXmx。 例如:
baksmali -JXmx512m -x blah.odex
或者通过如下方式直接调用jar包
java -Xmx512m -jar baksmali.jar -x blah.odex
在dalvik字节码中,寄存器是32位,并且可以保存任何类型的值。 2个寄存器用于保存64位类型(Long和Double)。
有两种方法可以指定方法中有多少可用寄存器。 .registers指令指定方法中寄存器的总数,而.locals指令指定方法中非参数寄存器的数量。 寄存器总数包括保存方法参数所需的寄存器。
调用方法时,方法的参数将会被放置到倒数的n个寄存器中。如果某个方法有2个参数,5个寄存器(v0-v4),此时参数会被放置到最后两个寄存器中,也就是v3和v4。
对于非static方法,第一个参数永远都是调用该方法的对象。
比如,非static方法LMyObject;->callMe(II)V.,这个方法有2个int型参数,但是还存在一个隐型的 LMyObject; 参数在两个整型参数之前,所以这个方法有3个参数。
假设通过.registers 5 指令或者.local 2指令(i.e. 2个local寄存器和3个参数寄存器)来指定这个方法有5个寄存器(v0 - v4),当方法被调用时,调用此方法的对象(也就是 this 引用)将会被放置到v2寄存器中,而第一个整型参数将会被放置到v3寄存器中,第二个整型参数会被放置到v4寄存器中。
对于static方法,只是少了一个隐型参数,this而已。
对于寄存器,有两种命名方案:普通的v开头命名方案和针对参数寄存器的p开头命名方案。我们继续看上面那个有3个参数,5个寄存器的方法,下面这个表格分别用两种命名方式来表示寄存器。
Local | Param | 数量 |
---|---|---|
v0 | 第一个local寄存器 | |
v1 | 第二个local寄存器 | |
v2 | p0 | 第一个参数寄存器 |
v3 | p1 | 第二个参数寄存器 |
v4 | p2 | 第三个参数寄存器 |
p开头命名方案的引入是为了解决大家在编辑smali过程中的共同烦恼。比方说,你想添加一下代码到一个有几个参数的方法,然后你发现,你需要一个额外的寄存器。你可能会想“这不是小问题嘛,只需要在.registers指令上加一个不就完事了”。
显然,问题不可能那么简单。记住一点,方法参数是存放在可用寄存器中倒数的几个寄存器中。如果你增加寄存器的数量,你将会改变方法参数存放的寄存器。因此,你必须更改.registers指令并重新编号每个参数寄存器。
但是如果在整个方法中使用p命名方案引用参数寄存器,则可以轻松更改方法中的寄存器数量,而无需担心重新编号任何现有寄存器。
注意:默认情况下,baksmali将使用参数寄存器的p命名方案。如果由于某种原因要禁用此命令并强制baksmali始终使用v命名方案,则可以使用-p / - no-parameter-registers选项。
如前所述,Long和Double类型(分别为J和D)是64位值,需要2个寄存器。在引用方法参数时要记住这一点。
比如,有一个非static方法
LMyObject;->MyMethod(IJZ)V
方法参数类型依次是:LMyObject;, int, long, bool。所以,这个方法需要5个寄存器来存放参数。
Register | Type |
---|---|
p0 | this |
p1 | I |
p2,p3 | J |
p4 | Z |
v2.2版本后,smail和baksmali有了新的脚手架工具。
baksmali disassemble app.apk -o app
smali assemble app -o classes.dex
执行baksmali help 和 smali help 查看入门内容。
注意以下几点:
baksmali disassemble app.apk/classes2.dex
。执行baksmali help input
查阅更多相关信息。adb pull /system/framework framework && baksmali deodex app.odex -b framework/arm/boot.oat
baksmali list dex boot.oat
列举出oat文件中所有dex(此命令也适用于apk文件)baksmali list classes app.apk
列举apk/dex等文件中的所有类baksmali list methods app.apk | wc -l
获取当前dex文件中的方法数smali/baksmali 2.0版本是下一个重大更新版本,主要就是dexlib的更新。当前正处于测试阶段,如果想体验一下,可以自行前往下载界面下载2.0b1 jar文件。
这对你意味着什么?大部分内容对你没有影响,值得一提的是它运行更快并且使用更少的内存。
但是,下面这几点你还是需要稍微注意一下。
smali和baksmali现在是多线程的!默认情况下,它们使用可用的CPU数,最多为6。你可以使用-j选项进行设置。
为了在具有多线程的smali中获得最佳性能,最好稍微提高内存。如果你正在使用smali wrapper script,则可以通过添加-JXmx512m选项手动执行此操作。
smali -JXmx512m out -o classes.dex
当然,你可以使用默认只位512m的smali wrapper script
我利用一次重大修订的机会来调整语言本身的一些内容。
.method parameters(IILjava/lang/String;)V
.parameter
.parameter
.parameter stringParameter
...
变成
.method parameters(IILjava/lang/String;)V
.param p3, "stringParameter"
...
.parameter指令现在为.param,它的工作方式与.local指令类似。 .param指令不是按顺序将.parameter信息与每个参数匹配,而是使用register参数指定与之关联的参数。
.array-data 0x4
0x0t 0x0t 0x80t 0x0t
0x0t 0x0t 0x40t 0x0t
0x0t 0x0t 0x20t 0x0t
0x0t 0x0t 0x10t 0x0t
.end array-data
变成
.array-data 4
0x800000
0x400000
0x200000
0x100000
.end array-data
.array-data中的每个数字现在都是它自己的元素,而不是之前的语法,它会连接每个数字的little-endian编码,然后将完整的连接字节数组重新解释为n-byte little-endian序编码元素。
const/high16 v0, 0x1234
const-wide/high16 v0, 0x1234
变成
const/high16 v0, 0x12340000
const-wide/high16 v0, 0x1234000000000000
更改了* / high16指令的语法,以便它使用加载到寄存器中的实际值,而不仅仅是存储在指令中的16位。
也就是说指定除了顶部16位以外的位为非0是错误的。例如const/high16 v0, 0x12340001
将会是错误的。
smalidea是一个针对Intellij IDEA以及Android Studio 的smali语言插件。
注意:单指令单步调试在 IDEA 14.1及以上版本支持(Android Studio是基于IDEA的,所以类似)。而此之前的版本,执行单步调试的时候,回调转到下一个.line指令,而不是下一条指令。
baksmali d myapp.apk -o ~/projects/myapp/src
~/projects/myapp
或者 在Android Studio3.2下进行如下操作:
baksmali d myapp.apk -o ~/projects/myapp/src
adb forward tcp:8700 jdwp:$(timeout 0.5 adb jdwp | tail -n 1)
dalvik字节码有两个主要的类型,基本类型和引用类型。引用对象指的是对象,数组等基础类型外的所有类型。
每个基本类型都由单个字母代表。这并不是我想出来的点子,他们真真实实地以字符串的形式存储在dex文件中。它们在 dex-format.html 文档中被明确的指出来过(Android源码仓中dalvik/docs/dex-format.html)。
V | void - 只能用于返回类型 |
---|---|
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long(64bits) |
F | float |
D | double(64bits) |
Object则是以Lpackage/name/ObjectName;
的形式出现,L
表明这是一个对象类型,package/name/
指代包名,ObjectName
为当前对象类型名称,:
作为对象的结尾。这等价于java的package.name.ObjectName
,以String这个具体的类型为例子,Ljava/lang/String;
等价于java.lang.String
。
Arrays(数字)则是以[I
的形式出现,这指代的是一个int类型一维数组,也就是java中int[]
。至于多维数组,只需要在前面添加一个或者多个[
字符。[[I
=int[][]
,[[[I
=int[][][]
(注意,数字的最大维度为255)。
对象数据,如[Ljava/lang/String;
道理和上面是一样的。
方法总是以非常详细的形式指定,包括方法的类型,方法名称,参数类型和返回类型。虚拟机需要所有这些信息才能找到正确的方法,并能够对字节码执行静态分析(用于验证/优化目的)。
一般形式如下:
Lpackage/name/ObjectName;->MethodName(III)Z
上面这个示例中,Lpackage/name/ObjectName;
表示类型,MethodName
表示方法名,(III)Z
中III
表示参数类型(3个int型),Z
表示方法的返回类型(bool类型)。
方法参数一个接一个地列出,它们之间没有分隔符。
下面有一个更复杂的示例:
method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
等价于
String method(int, int[][], int, String, Object[])
字段同样总是以详细形式指定,包括包含字段的类型,字段名称和字段类型。同样,这些信息也是为了帮助虚拟机能够找到正确的字段,以及对字节码执行静态分析。
一般形式如下:
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
这应该是不言自明的 - 它们分别是包名,字段名和字段类型。
在deodexing过程中,我们使用baksmali产生的一些smali文件中,你可能会注意到baksmali用一个throw或其他东西替换odexed指令的一些情况,其中的注释如“Replaced unresolvable optimized instruction with a throw”。
比如,下面这段java代码:
Object blah = null;
blah.toString();
相对应的Smali代码为:
const v0, 0
invoke-virtual {v0}, Ljava/lang/Object;->toString();
这当然会导致空指针异常 - 但它是有效的代码。在实践中,这些案例更加隐蔽。
当代码被odexed时,invoke-virtual指令将被一个invoke-virtual-quick指令替换,如下所示:
const v0, 0
invoke-virtual-quick {v0), vtable@7
其中vtable @ 7是指向Object是抽象方法表中toString()方法的索引。
但请注意,上面的代码并没有提到该方法所在的类。由于v0始终为null,并且我们只有vtable索引,因此baksmali不可能知道要查看哪个类。所以,不可能deodex 这条指令。但是,我们可以尽量对其进行处理:用具有完全相同效果的内容来替换这条指令。记住v0始终为null,所以通过它调用任何方法都会导致空指针。所以Samli就使用同样会抛出空指针的内容来替换这条无法解决的odex指令。
除此之外,紧随上面这些无法解决的odex指令的code,由于永远无法执行而成为了冗余代码(除非有其他分支可以访问到它们)。或者在一些其他情况下,代码依赖的是一个我们无法解决的odex指令方法结果,那么这部分代码也是不可能被deodex的,因为,这部分代码无法被访问到,全被移除或者注释掉了。
因此,简而言之,这些情况不应影响字节码的语义/功能。当你看到“replaced unresolvable optimized instruction”,不必大惊小怪。Baksmali对这部分呢日用的处理不影响代码功能/语义(如果对功能/语义造成了影响,那就是baksmali的bug)。
目前,存在与此相关的已知问题,其中如果try块中的所有代码都被注释掉,因为它依赖一个无法解析的odex指令,则(空)try块保留在其中,并且当安装到设备上时,空 try块会导致dalvik拒绝dex文件。