文章目录
  1. 1. 0x00 前言
  2. 2. 0x01 smali注入
    1. 2.1. 1.smali注入过程
      1. 2.1.1. 1.1 反编译apk
      2. 2.1.2. 1.2 打开smali文件,注入语句
      3. 2.1.3. 1.3 重打包
      4. 2.1.4. 1.4 签名
    2. 2.2. 2.常用的一些smali注入代码
      1. 2.2.1. 2.1 输出Log信息
      2. 2.2.2. 2.2 Toast提示消息
      3. 2.2.3. 2.3 弹出消息框
      4. 2.2.4. 2.4 修改代码逻辑
  3. 3. 0x02 smali注入小技巧
    1. 3.1. 1.选择一个正确的寄存器
    2. 3.2. 2.增加一个寄存器
    3. 3.3. 3.编写一个单独的smali文件
  4. 4. 0x03 总结

0x00 前言

本文为科普文,在蒸米的《安卓动态调试七种武器之长生剑 - Smali Instrumentation》一文的基础上,针对smali注入保持源程序逻辑完整性总结一些方法和思考,现和大家分享一下,欢迎大家一起探讨。

0x01 smali注入

相信大家在入门Android安全的时候都会接触到smali注入吧。通过smali注入,我们可以修改程序的逻辑,或者在程序适当的位置插入一段代码,通过执行这段代码我们可以得到程序的一些中间运行结果。

1.smali注入过程

可能大家对这个smali注入过程都比较熟悉了,这里稍微过一过流程吧。

1.1 反编译apk

可以使用apktool对apk进行反编译,命令是apktool d apk名

1.2 打开smali文件,注入语句

我们在上一步中反编译得到了一个装有smali文件的目录,那么我们可以在特定的smali文件中注入我们想要插入的smali语句。

1.3 重打包

修改好smali文件之后,就要重新打包,那么这里同样可以使用apktool工具,命令是apktool b apk名打包成功后,新的apk会在反编译目录下的dist目录中

1.4 签名

如果不对重新打包的apk重新签名,就会有问题,那么签名完之后就可以去模拟器或者真机上运行看看效果啦。

2.常用的一些smali注入代码

2.1 输出Log信息

插入一段log方法的代码之后,可以在logcat中查看我们想要输出的信息,这对于想看到程序的中间结果是一种常用的方法。

java代码:

1
Log.v("tag","msg");

smali代码:

1
2
3
const-string v0, "tag"
const-string v1, "msg"
invoke-static {v0, v1}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I

2.2 Toast提示消息

Toast也可以看到一些我们想看到的中间值。并且运行应用,到特定模块,从界面就可以看到结果了。

java代码:

1
2
Toast.makeText(getApplicationContext(), "msg",
Toast.LENGTH_SHORT).show();

smali代码:

1
2
3
4
5
const-string v1, "msg"
const/4 v2, 0x0
invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V

2.3 弹出消息框

弹框的效果和Toast也比较像

java代码:

1
new AlertDialog.Builder(this).setTitle("result").setMessage("msg").show();

smali代码:

1
2
3
4
5
6
7
8
9
new-instance v0, Landroid/app/AlertDialog$Builder;
invoke-direct {v0, p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V
const-string v1, "result"
invoke-virtual {v0, v1}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
move-result-object v0
const-string v1, "msg"
invoke-virtual {v0, v1}, Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;
move-result-object v0
invoke-virtual {v0}, Landroid/app/AlertDialog$Builder;->show()Landroid/app/AlertDialog;

2.4 修改代码逻辑

这里的修改代码逻辑指的是修改源码里的一些条件语句等等,这个视具体功能实现要求而定。

0x02 smali注入小技巧

在反编译之后的smali代码中,我们可以看到里面会使用到许多寄存器,而我们在注入的时候,往往需要使用寄存器暂时存储我们的变量,比如

1
Log.v("tag","msg");

上面的这个方法里,msg是我们想要输出的信息,而tag是标识,下面是它对应的smali代码

1
2
const-string v3,”tag”
invoke-static {v3,v0},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I

从上面我们可以看到,这里使用了v3寄存器去存放tag的值(这里的值就是tag),接着invoke,v0就是存放msg。

那么问题来了,如果我们没有选择正确的寄存器,可能会导致程序运行之后崩溃。比如我们这里选择了v3寄存器,假如v3寄存器本身存放着重要的值,而我们在注入的时候把它的值给刷掉了,那么在后面的程序中就可能会发生错误。

下面就是要介绍如何解决这方面的问题,在修改smali的基础上,使得尽可能不影响程序的正常运行。我们进行smali注入的时候,用的比较普遍的一个就是插入log方法,所以本文会通过插入log代码作为例子来介绍。

1.选择一个正确的寄存器

1
2
3
4
5
6
7
8
9
10
.line 67
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
.line 70
.local v6, "userSN":Ljava/lang/String;
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
move-result v8

例如,上面那段smali代码,v6是我们想要通过log输出的值,于是我们想要在把v6的值赋值给userSN变量之后插入一段log代码,输出v6的值,那么我们怎么选择存放tag值得寄存器呢?在这里,我们可以稍微往后面的代码看一看。下面调用了一个方法,并且把结果放在了v8寄存器中,就是说,v8寄存器在我们插入log代码之后,不管v8本身的值是多少,都会因为move-result而被重新刷掉。所以,这个v8寄存器刚好符合我们的需求。我们使用v8寄存器存储插入的tag变量不会影响到后面的程序逻辑。

修改后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.line 67
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
.line 70
.local v6, "userSN":Ljava/lang/String;
const-string v8,”SN”
invoke-static {v8,v6},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
move-result v8

这种寻找寄存器的方法要看具体源程序的实现逻辑而选择,如果注入的地方比较多,那么我们可能需要花费一些功夫去寻找。

2.增加一个寄存器

上面那种方法,要顾虑的地方比较多,那么我们可不可以不管上下文,直接使用一个新的寄存器呢。还是那个例子

1
2
3
4
5
6
7
.method private checkSN(Ljava/lang/String;Ljava/lang/String;)Z
.locals 10
.param p1, "userName" # Ljava/lang/String;
.param p2, "sn" # Ljava/lang/String;
.prologue
const/4 v7, 0x0

上面那一段smali代码是一个方法开头的前面几行。locals指明了在这个方法中会使用多少个寄存器,在这里可以看到checkSN方法中会使用10个寄存器,而这10个寄存器是v0~v9。所以,我们在方法的声明里增加一个新的寄存器,这样就不会和源程序使用的寄存器冲突了。修改后的代码:

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
.method private checkSN(Ljava/lang/String;Ljava/lang/String;)Z
.locals 11
.param p1, "userName" # Ljava/lang/String;
.param p2, "sn" # Ljava/lang/String;
.prologue
const/4 v7, 0x0
... ...
... ...
.line 67
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
.line 70
.local v6, "userSN":Ljava/lang/String;
const-string v10,”SN”
invoke-static {v10,v6},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
move-result v8

这里比较需要注意的地方就是寄存器是从v0开始的。

3.编写一个单独的smali文件

方法二中,我们需要修改寄存器的个数,那如果我们要注入的方法比较多,那么我们就需要在每个方法的开头都去修改,那么有没有比较省事的办法?

我们可以单独写一个smali文件,将我们想要实现的方法写进去,接着在源程序中就可以只写一句调用方法的代码,这样做可以降低对源程序的影响。

那么在这里,使用到类似于分离和复用的思想。将我们要注入的语句从源程序分离出来,单独形成一个文件,文件里使用的寄存器不会直接影响源程序的代码逻辑,另外从代码复用的角度上来说,如果我们需要在源程序多处注入类似的代码,那么就可以通过传入不同的参数,然后调用这些文件里的方法。

首先编写一个smali文件,方法smali的根目录,然后在源程序中调用这个文件中的方法。例如,我们在crack.smali文件中编写了一个log方法,这个方法的参数就是我们想要输出的值

1
2
3
4
5
6
7
8
9
10
11
12
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static LogS(Ljava/lang/String;)V
.locals 1
.prologue
const-string v0, "tag"
invoke-static {v0, p0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method

接着在源程序中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
.line 67
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
.line 70
.local v6, "userSN":Ljava/lang/String;
invoke-static {v6},Lcrack;->LogS(Ljava/lang/String;)V
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
move-result v8

0x03 总结

在上一节中我们介绍了三种关于smali注入在保持源程序逻辑完整性方面的技巧以及这些技巧的利弊分析,从实现角度来说,选用第三种方法会比较好,当然如果只是简单地注入一两句,那么第一种和第二种方法或许会更简单方便,只是看使用的场景。

文章目录
  1. 1. 0x00 前言
  2. 2. 0x01 smali注入
    1. 2.1. 1.smali注入过程
      1. 2.1.1. 1.1 反编译apk
      2. 2.1.2. 1.2 打开smali文件,注入语句
      3. 2.1.3. 1.3 重打包
      4. 2.1.4. 1.4 签名
    2. 2.2. 2.常用的一些smali注入代码
      1. 2.2.1. 2.1 输出Log信息
      2. 2.2.2. 2.2 Toast提示消息
      3. 2.2.3. 2.3 弹出消息框
      4. 2.2.4. 2.4 修改代码逻辑
  3. 3. 0x02 smali注入小技巧
    1. 3.1. 1.选择一个正确的寄存器
    2. 3.2. 2.增加一个寄存器
    3. 3.3. 3.编写一个单独的smali文件
  4. 4. 0x03 总结