So Tired !_! 逆水行舟, 不进则退!

10Jul/14

Smali注入之打造属于自己的安卓crack利器

Posted by Nick Xu

关于Smali注入大家应该了解过,网上有不少教程。那些注入代码看上去简单,实际用起来得花不少功夫。

普通的注入就是在软件源代码中添加几行代码,用于改变软件的功能,或查看某个寄存器在运行中具体的值。

需要注意的地方是:添加的注入代码所使用的寄存器不影响其他代码的执行。当注入代码较多时,这个要求就变得很困难了。

在这里我的解决办法是:把注入代码写进自己专属的crack.smali,然后在要注入的地方调用crack.smali里的注入方法即可,只用一行注入代码,而且不影响其他寄存器。

举个例子说明下两种方法的区别,假设原软件中有以下代码:

.method public methodName()Ljava/lang/String;
.locals 4
.prologue
const-string v0, "test1"

const-string v3, "test2"

invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;

move-result-object v1

invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V

new-instance v3, Ljava/lang/StringBuilder;

invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v0

invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0

return-object v0
.end method

假如我要查看move-result-object v1和move-result-object v2,两处中v1、v2的值该如何注入?

普通的log.d注入方法如下(可以用Logcat查看日志):

.method public methodName()Ljava/lang/String;
.locals 5
.prologue
const-string v0, "test1"

const-string v3, "test2"

invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;

move-result-object v1

const-string v4, "info"

invoke-static {v4, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

const-string v4, "info"

invoke-static {v4, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V

new-instance v3, Ljava/lang/StringBuilder;

invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v0

invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0

return-object v0
.end method

在上面的代码中,我先是修改了开头的“.locals 5”,表示使用的寄存器为v0-v4。然后我用v4作为log.d的第一个参数,寄存器v1、v2为第二个参数。

由于原代码中,v0、v1、v2、v3从头到尾都有使用,所以注入时使用这几个寄存器会影响软件的正常执行,所以我才修改“.locals”开辟新的寄存器v4。

明显这样注入很麻烦。而且刚好v1、v2都是字符串,符合log.d的要求。如果v1、v2为整数值,注入就更加复杂了。

下面看看创建了crack.smali的注入会如何。

假设我已经有了个crack.smali,代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log(Ljava/lang/String;)V
    .locals 1
    .prologue
    const-string v0, "info"
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
.end method

把crack.smali放到反编译后的smali根目录,在源代码中注入:

.method public methodName()Ljava/lang/String;
.locals 4
.prologue
const-string v0, "test1"

const-string v3, "test2"

invoke-static {v0}, Lpackage/name/ObjectName;——>methodName1(Ljava/lang/String;)Ljava/lang/String;

move-result-object v1

invoke-static {v1}, Lcrack;->log(Ljava/lang/String;)V

invoke-static {v1}, Lpackage/name/ObjectName;——>methodName2(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

invoke-static {v2}, Lcrack;->log(Ljava/lang/String;)V

invoke-static {v3}, Lpackage/name/ObjectName;——>methodName3(Ljava/lang/String;)V

new-instance v3, Ljava/lang/StringBuilder;

invoke-direct {v0, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v0

invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0

return-object v0
.end method

明显比前面的注入方式简单多了。crack.smali的方法可以不断丰富,需要用时信手拈来。

既然如此,就让我们打造属于自己的安卓crack利器吧!

最基本的crack.smali推荐加入log日志输出,代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log(Ljava/lang/String;)V
    .locals 1
    .prologue
    const-string v0, "info"
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
.end method

把crack.smali放进smali目录,在要查看的,保存了字符串的寄存器vx的下面,添加代码:

[Java] 纯文本查看 复制代码
1
invoke-static {vx}, Lcrack;->log(Ljava/lang/String;)V

保存并重新编译,在手机或模拟器上安装软件,连上电脑,打开cmd命令行:

<ignore_js_op>

cd到桌面,然后输入命令adb logcat>test.txt,然后在手机上运行软件,当软件执行了注入的代码(自行判断),按ctrl+c结束,然后回到桌面。

如无法识别adb命令,请先添加adb.exe所在位置的环境变量,如我安装了靠谱助手,我找到了它提供的adb.exe所在的路径,然后我照下面图设置:

<ignore_js_op>

打开test.txt,查找d/info,在找到的那一行的右边就是要查看的寄存器的值。

<ignore_js_op>

“info”是由“ const-string v0, "info" ”给出。可以自行修改。

上面的说的命令还可以添加过滤代码,有经验的人自行修改,使得test.txt的内容更简洁。

从上面test.txt的结果可以看出,如果需要注入的位置有多处,那么就很难分辨具体哪个结果对应哪个了。

解决办法是,给crack.smali的log方法加序号,并复制多份log方法,参照下面修改方式,如:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
.class public Lcrack;
.super Ljava/lang/Object;
.source "crack.java"
.method public static log1(Ljava/lang/String;)V
    .locals 1
    .prologue
    const-string v0, "info1"
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
.end method
.method public static log2(Ljava/lang/String;)V
    .locals 1
    .prologue
    const-string v0, "info2"
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
.end method

第一处注入调用log1,第二处注入调用log2,依次类推,然后根据序号对应结果。

上面说的注入,前提都是vx是String,如果vx是int型怎么办?

可以给crack.smali添加下面方法:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
.method public static I(I)V
    .locals 2
    .prologue
    const-string v0, "info_int"
    invoke-static {p0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
    move-result-object v1
    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
    return-void
.end method

上面方法调用代码为:

[Java] 纯文本查看 复制代码
1
invoke-static {vx}, Lcrack;->I(I)V

vx为要查看的寄存器。

当vx保存的是long型的话,就比较麻烦了,稍微用错就会导致程序停止运行,所以尽量避免查看long型vx。

给crack.smali添加以下代码:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
.method public static J(J)V
.locals 2
.prologue
const-string v0, "info_long"
invoke-static {p0, p1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method

上面方法调用代码为:

[Java] 纯文本查看 复制代码
1
invoke-static {vx, vx+1}, Lcrack;->J(J)V

vx为要查看的寄存器,同时确保vx+1在上面的代码中没有被使用过,且在.locals声明的可使用的寄存器范围内。

至于[C、[B、[Ljava/lang/String的查看,有机会再补上。

软件大部分时间都在跟字符串、数据打交道。很多时候就因为某个寄存器的值不知,导致软件代码很难分析下去。

因此在没有调试器的前提下,通过注入查看寄存器的值就变得非常重要了。

总是通过logcat查看寄存器值,多少有点不方便,所以下面讲下如何将字符串输出到文本。

直接用smali写有点麻烦,所以先用java写,编译后再反编译为smali,参考java代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
import java.io.*;
import android.util.Log;
public class crack
{
/*将字符串s输出到/sdcard/debug.txt*/
public static void puts(String s)
{
  try
  {
          String path= "/sdcard/debug.txt";
           
                  FileOutputStream outStream = new FileOutputStream(path,false);
                  OutputStreamWriter writer = new OutputStreamWriter(outStream,"gb2312");
                  writer.write(s);
                  writer.flush();
                  writer.close();
                  outStream.close();
  }
                  catch (Exception e)
                  {
                          Log.e("debug", "file write error");
                  }
}
}

反编译后smali代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
49
50
51
52
53
54
55
.method public static puts(Ljava/lang/String;)V
    .locals 7
    .prologue
    :try_start_0
    const-string v3, "/sdcard/debug.txt"
    new-instance v2, Ljava/io/FileOutputStream;
    const/4 v5, 0x0
    invoke-direct {v2, v3, v5}, Ljava/io/FileOutputStream;-><init>(Ljava/lang/String;Z)V
    .line 19
    new-instance v4, Ljava/io/OutputStreamWriter;
    const-string v5, "gb2312"
    invoke-direct {v4, v2, v5}, Ljava/io/OutputStreamWriter;-><init>(Ljava/io/OutputStream;Ljava/lang/String;)V
    .line 21
    invoke-virtual {v4, p0}, Ljava/io/OutputStreamWriter;->write(Ljava/lang/String;)V
    .line 23
    invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->flush()V
    .line 25
    invoke-virtual {v4}, Ljava/io/OutputStreamWriter;->close()V
    .line 27
    invoke-virtual {v2}, Ljava/io/FileOutputStream;->close()V
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
    .line 37
    :cond_0
    :goto_0
    return-void
    .line 30
    :catch_0
    move-exception v0
    .line 34
    const-string v5, "debug"
    const-string v6, "file write error"
    invoke-static {v5, v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
    goto :goto_0
.end method

使用方法为,在要查看的寄存器vx的下方换行添加代码:

[Java] 纯文本查看 复制代码
1
invoke-static {vx}, Lcrack;->puts(Ljava/lang/String;)V

至于输出整型变量到文本,请参考之前的例子,先将整型变量转为字符串,再输出,具体代码这里略过。。。

下面介绍通过注入,改变软件获得的imei号,让软件改为读取保存在 “/sdcard/deviceid/imei.txt” 里的串号。

参考java代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
import java.io.*;
public class crack {
        public static String getDeviceId()
        {
        String path = "/sdcard/deviceid/imei.txt";
        String str = null;
        File f = new File(path);
        if (f != null && f.exists()) 
        {
            FileInputStream fis = null;
            try {
                    fis = new FileInputStream(f);
            } catch (FileNotFoundException e1) {
            e1.printStackTrace(); 
        }
            InputStreamReader inputStreamReader = null;
            try {
                    inputStreamReader = new InputStreamReader(fis, "utf-8");
                    try {
                            BufferedReader reader = new BufferedReader(inputStreamReader);
                            StringBuffer sb = new StringBuffer("");
                            String line;
                            line = reader.readLine();
                            sb.append(line);
                            reader.close();       
                            fis.close();
                            str = sb.toString();
                    catch (IOException e) {  
                e.printStackTrace();  
            }
            } catch (UnsupportedEncodingException e) { 
                     e.printStackTrace();          
                 }  
        }
              return str;
        }
}

反编译后得到的smali代码如下:

[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
.method public static getDeviceId()Ljava/lang/String;
    .locals 13
    .prologue
    const-string v8, "/sdcard/deviceid/imei.txt"
    const/4 v11, 0x0
    new-instance v2, Ljava/io/File;
    invoke-direct {v2, v8}, Ljava/io/File;-><init>(Ljava/lang/String;)V
    if-eqz v2, :cond_0
    invoke-virtual {v2}, Ljava/io/File;->exists()Z
    move-result v12
    if-eqz v12, :cond_0
    const/4 v3, 0x0
    :try_start_0
    new-instance v4, Ljava/io/FileInputStream;
    invoke-direct {v4, v2}, Ljava/io/FileInputStream;-><init>(Ljava/io/File;)V
    :try_end_0
    .catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
    move-object v3, v4
    :goto_0
    const/4 v5, 0x0
    :try_start_1
    new-instance v6, Ljava/io/InputStreamReader;
    const-string v12, "utf-8"
    invoke-direct {v6, v3, v12}, Ljava/io/InputStreamReader;-><init>(Ljava/io/InputStream;Ljava/lang/String;)V
    :try_end_1
    .catch Ljava/io/UnsupportedEncodingException; {:try_start_1 .. :try_end_1} :catch_3
    :try_start_2
    new-instance v9, Ljava/io/BufferedReader;
    invoke-direct {v9, v6}, Ljava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
    new-instance v10, Ljava/lang/StringBuffer;
    const-string v12, ""
    invoke-direct {v10, v12}, Ljava/lang/StringBuffer;-><init>(Ljava/lang/String;)V
    invoke-virtual {v9}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String;
    move-result-object v7
    invoke-virtual {v10, v7}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;
    invoke-virtual {v9}, Ljava/io/BufferedReader;->close()V
    invoke-virtual {v3}, Ljava/io/FileInputStream;->close()V
    invoke-virtual {v10}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;
    :try_end_2
    .catch Ljava/io/IOException; {:try_start_2 .. :try_end_2} :catch_1
    .catch Ljava/io/UnsupportedEncodingException; {:try_start_2 .. :try_end_2} :catch_2
    move-result-object v11
    :cond_0
    :goto_1
    return-object v11
    :catch_0
    move-exception v1
    invoke-virtual {v1}, Ljava/io/FileNotFoundException;->printStackTrace()V
    goto :goto_0
    :catch_1
    move-exception v0
    :try_start_3
    invoke-virtual {v0}, Ljava/io/IOException;->printStackTrace()V
    :try_end_3
    .catch Ljava/io/UnsupportedEncodingException; {:try_start_3 .. :try_end_3} :catch_2
    goto :goto_1
    :catch_2
    move-exception v0
    move-object v5, v6
    :goto_2
    invoke-virtual {v0}, Ljava/io/UnsupportedEncodingException;->printStackTrace()V
    goto :goto_1
    :catch_3
    move-exception v0
    goto :goto_2
.end method

使用方法为:

查找

[Java] 纯文本查看 复制代码
1
Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;

在下面换行添加

[Java] 纯文本查看 复制代码
1
invoke-static {}, Lcrack;->getDeviceId()Ljava/lang/String;

 

   
site
site