smali注入小技巧

admin 2021年12月7日10:09:21评论187 views字数 5237阅读17分27秒阅读模式

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代码:

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

smali代码:

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代码:

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

smali代码:

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代码:

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

smali代码:

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代码中,我们可以看到里面会使用到许多寄存器,而我们在注入的时候,往往需要使用寄存器暂时存储我们的变量,比如

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

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

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.选择一个正确的寄存器

.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变量不会影响到后面的程序逻辑。

修改后的代码:

.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.增加一个寄存器

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

.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。所以,我们在方法的声明里增加一个新的寄存器,这样就不会和源程序使用的寄存器冲突了。修改后的代码:

.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方法,这个方法的参数就是我们想要输出的值

.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

接着在源程序中调用

.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注入在保持源程序逻辑完整性方面的技巧以及这些技巧的利弊分析,从实现角度来说,选用第三种方法会比较好,当然如果只是简单地注入一两句,那么第一种和第二种方法或许会更简单方便,只是看使用的场景。


smali注入小技巧
smali注入小技巧


本文始发于微信公众号(WhiteCellClub):smali注入小技巧

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月7日10:09:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   smali注入小技巧http://cn-sec.com/archives/490478.html

发表评论

匿名网友 填写信息