Android逆向之smali

smali是安卓Dalvik VM上的字节码语言,记录一下smali类型、指令等,方便日后阅读smali字节码

类型

Dalvik字节码有两种类型:原始类型、引用类型(包括对象和数组)。

smali类型 java类型
V void (用于返回类型)
Z boolean
B byte
S short
C char
I int
J long (64 bits)
F float
D double (64 bits)
[XX Array(例如[I表示int数组)
Lxxx/yyy Object(例如Ljava/lang/String表示String对象)

详细smali类型可参考:Dalvik 可执行文件格式

函数定义

Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type,参数与参数之间没有分隔符。

例如 foo (Z[I[ILjava/lang/String;J)Ljava/lang/String 表示 String foo (boolean, int[], int[], String, long) 。

指令

指令 功能
.field private isFlag:z 定义变量
.method 方法
.param 方法参数
.prologue 方法开始
.line 12 此方法位于12行
invoke-super 调用父类方法
const/high16 v0,0x7fo3 把0x7fo3赋值给v0
invoke-direct 调用函数
return-void 函数返回void
.end method 函数结束
new-instance 创建实例
input-object 对象赋值
iget-object 调用对象
Invoke-static 调用静态函数

完整smali指令详解可参考:Dalvik 字节码

寄存器

Dalvik寄存器全部为32位,且支持任何类型,64位类型例如Long用2个寄存器表示。

一个方法可使用指令.registers指令指定了此方法中共有多少个可用的寄存器,或者使用指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。

例如某个有2个参数的非静态方法,共有5个寄存器(V0-V4),可用.register指令指定5个,或者使用.locals指令指定2个(2个local寄存器+3个参数寄存器,3个参数寄存器位被方法调用的对象this + 两个参数)。

静态方法没有调用的对象this,其余寄存器与非静态方法一致。

一个方法被调用时,该方法的参数被保存在最后N个寄存器中,如果一个方法有2个参数和5个寄存器(V0-V4),参数将被保存在最后的2个寄存器内V3和V4,调用方法的对象(即this引用)会保存在V2,参数在后,局部变量在前。

寄存器的命名方式:对于参数寄存器有普通的V命名方式和P命名方式。在方法中第一个参数寄存器,是使用P方式命名的第一个寄存器。如上述五个寄存器的非静态方法命名如下:

v字命名 p字命名 说明
v0 the first local register
v1 the second local register
v2 p0 the first parameter register
v3 p1 the second parameter register
v4 p2 the third parameter register

字节分析

在Android studio中创建SimpleLogin.java,代码如下:

1
2
3
4
5
6
7
8
9
10
11
package com.myelin.rootchecker;

public class SimpleLogin {

public boolean login(long phoneNumber, String password) {
if (phoneNumber != 13711111111L) {
return false;
}
return "233".equals(password);
}
}

Android studio安装java2smali插件,安装后点击"Build"–"Compile to Smali"生成SimpleLogin.smali

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
.class public Lcom/myelin/rootchecker/SimpleLogin; //类定义
.super Ljava/lang/Object; //类的父类
.source "SimpleLogin.java" //目标文件


# direct methods
.method public constructor <init>()V //构造器方法定义
.registers 1 //方法共一个寄存器

.prologue //方法开始
.line 3 //源码第3行
invoke-direct {p0}, Ljava/lang/Object;-><init>()V //调用Object的无参构造器

return-void //方法返回void
.end method //构造器方法结束


# virtual methods
.method public login(JLjava/lang/String;)Z //方法参数为Long和String,返回布尔
.registers 7 //共7个寄存器
.param p1, "phoneNumber" # J //参数1,long类型占用两个寄存器p1和p2
.param p3, "password" # Ljava/lang/String; //参数2,String一个寄存器p3

.prologue // 方法开始
.line 6 // 源码第6行
const-wide v0, 0x3313ef3c7L //将数值赋值给寄存器v0

cmp-long v0, p1, v0 //比较p1和v0:相等v0置为0;p1大v0置为正数;p1小v0置为负数;

if-eqz v0, :cond_b //v0相等跳转到cond_b分支

.line 7 //源码第7行
const/4 v0, 0x0 //寄存器v0赋值为0

.line 9 //源码第7行
:goto_a //goto_a分支
return v0 //返回v0寄存器结束

:cond_b
const-string v0, "233" //寄存器v0赋值为字符串"233"

invoke-virtual {v0, p3}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
//调用String实例v0的虚方法equals,参数为p3寄存器

move-result v0 //通过move-result获取上述调用方法结果赋值给v0

goto :goto_a //跳转到goto_a分支返回结果
.end method //方法结束

dex互转

下载smali/baksmali工具

1
2
dex转smali: java -jar baksmali-2.5.2.jar d classes.dex
smali转dex: java -jar smali-2.5.2.jar a smali-dir

插桩流程

  1. apktool反编译得到smali文件
  2. 修改smali文件加入逻辑
  3. apktool重新打包
  4. apk重新签名

注意:若插桩代码使用了寄存器,注意调整方法寄存器数量。

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!