目錄
  1. 1. smali简介
  2. 2. 一道题目剖析smali
  3. 3. smali反编译
  4. 4. 一道题目体现apk的反编译与回编译
  5. 5. 总结
smali

smali简介

既然是简介,我觉得一句话就够了。

1
Smali是Dalvik的寄存器语言,它与Java的关系,简单理解就是汇编之于C。

一道题目剖析smali

题目来自于jarvisOJ smali。Crackme.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
.class public Lnet/bluelotus/tomorrow/easyandroid/Crackme;
.super Ljava/lang/Object;
.source "Crackme.java"


# instance fields
.field private str2:Ljava/lang/String;


# direct methods
.method public constructor <init>()V
.locals 1

.prologue
.line 22
invoke-direct {p0}, Ljava/lang/Object;-><init>()V

.line 21
const-string v0, "cGhyYWNrICBjdGYgMjAxNg=="

iput-object v0, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;

.line 23
const-string v0, "sSNnx1UKbYrA1+MOrdtDTA=="

invoke-direct {p0, v0}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->GetFlag(Ljava/lang/String;)Ljava/lang/String;

.line 24
return-void
.end method

.method private GetFlag(Ljava/lang/String;)Ljava/lang/String;
.locals 4
.param p1, "str" # Ljava/lang/String;

.prologue
const/4 v3, 0x0

.line 27
invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B

move-result-object v2

invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B

move-result-object v0

.line 29
.local v0, "content":[B
new-instance v1, Ljava/lang/String;

iget-object v2, p0, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->str2:Ljava/lang/String;

invoke-virtual {v2}, Ljava/lang/String;->getBytes()[B

move-result-object v2

invoke-static {v2, v3}, Landroid/util/Base64;->decode([BI)[B

move-result-object v2

invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V

.line 30
.local v1, "kk":Ljava/lang/String;
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;

invoke-direct {p0, v0, v1}, Lnet/bluelotus/tomorrow/easyandroid/Crackme;->decrypt([BLjava/lang/String;)Ljava/lang/String;

move-result-object v3

invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

.line 31
const/4 v2, 0x0

return-object v2
.end method

.method private decrypt([BLjava/lang/String;)Ljava/lang/String;
.locals 8
.param p1, "content" # [B
.param p2, "password" # Ljava/lang/String;

.prologue
.line 35
const/4 v4, 0x0

.line 37
.local v4, "m":Ljava/lang/String;
:try_start_0
invoke-virtual {p2}, Ljava/lang/String;->getBytes()[B

move-result-object v3

.line 38
.local v3, "keyStr":[B
new-instance v2, Ljavax/crypto/spec/SecretKeySpec;

const-string v7, "AES"

invoke-direct {v2, v3, v7}, Ljavax/crypto/spec/SecretKeySpec;-><init>([BLjava/lang/String;)V

.line 39
.local v2, "key":Ljavax/crypto/spec/SecretKeySpec;
const-string v7, "AES/ECB/NoPadding"

invoke-static {v7}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;

move-result-object v0

.line 40
.local v0, "cipher":Ljavax/crypto/Cipher;
const/4 v7, 0x2

invoke-virtual {v0, v7, v2}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;)V

.line 41
invoke-virtual {v0, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B

move-result-object v6

.line 42
.local v6, "result":[B
new-instance v5, Ljava/lang/String;

invoke-direct {v5, v6}, Ljava/lang/String;-><init>([B)V
:try_end_0
.catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_1
.catch Ljavax/crypto/NoSuchPaddingException; {:try_start_0 .. :try_end_0} :catch_0
.catch Ljava/security/InvalidKeyException; {:try_start_0 .. :try_end_0} :catch_4
.catch Ljavax/crypto/IllegalBlockSizeException; {:try_start_0 .. :try_end_0} :catch_2
.catch Ljavax/crypto/BadPaddingException; {:try_start_0 .. :try_end_0} :catch_3

.end local v4 # "m":Ljava/lang/String;
.local v5, "m":Ljava/lang/String;
move-object v4, v5

.line 46
.end local v0 # "cipher":Ljavax/crypto/Cipher;
.end local v2 # "key":Ljavax/crypto/spec/SecretKeySpec;
.end local v3 # "keyStr":[B
.end local v5 # "m":Ljava/lang/String;
.end local v6 # "result":[B
.restart local v4 # "m":Ljava/lang/String;
:goto_0
return-object v4

.line 43
:catch_0
move-exception v1

.line 44
.local v1, "e":Ljava/security/GeneralSecurityException;
:goto_1
invoke-virtual {v1}, Ljava/security/GeneralSecurityException;->printStackTrace()V

goto :goto_0

.line 43
.end local v1 # "e":Ljava/security/GeneralSecurityException;
:catch_1
move-exception v1

goto :goto_1

:catch_2
move-exception v1

goto :goto_1

:catch_3
move-exception v1

goto :goto_1

:catch_4
move-exception v1

goto :goto_1
.end method

算了,还是不剖析了,以后碰到smali文件就直接转java文件好了。工具smali2java,得到java代码:

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
package net.bluelotus.tomorrow.easyandroid;

import android.util.Base64;
import java.io.PrintStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.GeneralSecurityException;

public class Crackme {
private String str2 = "cGhyYWNrICBjdGYgMjAxNg==";

public Crackme() {
GetFlag("sSNnx1UKbYrA1+MOrdtDTA==");
}

private String GetFlag(String p1) {
byte[] "content" = Base64.decode(p1.getBytes(), 0x0);
String "kk" = new String(Base64.decode(str2.getBytes(), 0x0));
System.out.println(decrypt("content", "kk"));
return null;
}

private String decrypt(byte[] p1, String p2) {
String "m" = 0x0;
try {
byte[] "keyStr" = p2.getBytes();
SecretKeySpec "key" = new SecretKeySpec("keyStr", "AES");
Cipher "cipher" = Cipher.getInstance("AES/ECB/NoPadding");
"cipher".init(0x2, "key");
byte[] "result" = "cipher".doFinal(p1);
return "m";
} catch(NoSuchPaddingException "e") {
"e".printStackTrace();
}
return "m";
}
}

这作为普通java文件当然是不能直接运行的,可以稍微修改一下

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
import java.util.Base64;
import java.io.PrintStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.GeneralSecurityException;

public class Crackme {
private String str2 = "cGhyYWNrICBjdGYgMjAxNg==";

public Crackme() {
GetFlag("sSNnx1UKbYrA1+MOrdtDTA==");
}

private String GetFlag(String p1) {
byte[] content = Base64.getDecoder().decode(p1);
String kk = new String(Base64.getDecoder().decode(str2));
System.out.println(decrypt(content, kk));
return null;
}

private String decrypt(byte[] p1, String p2) {
String m = "0x0";
try {
byte[] keyStr = p2.getBytes();
SecretKeySpec key = new SecretKeySpec(keyStr, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(0x2, key);
byte[] result = cipher.doFinal(p1);
String s = new String(result);
return m;
} catch(Exception e) {
e.printStackTrace();
}
return m;
}
}

配合test.java,可以直接在命令执行

1
2
3
4
5
6
//main.java
public class test{
public static void main(String args[]) {
Crackme crack = new Crackme();
}
}

结果如下

smali转成java后的逻辑很简单,就是AES的解密过程,可以直接用python实现

1
2
3
4
5
6
7
8
9
import base64 
from Crypto.Cipher import AES

key='cGhyYWNrICBjdGYgMjAxNg=='
cipher='sSNnx1UKbYrA1+MOrdtDTA=='
key=base64.b64decode(key)
cipher=base64.b64decode(cipher)
aes=AES.new(key,AES.MODE_ECB)
print aes.decrypt(cipher)

总结:对于smali文件,能阅读则阅读,比汇编代码还是有好一些;读不懂则直接转java。

smali反编译

对于C程序,可以直接修改汇编代码重新编译可改变程序逻辑。那么,对于apk文件,自然也可以修改smali代码回编译来改变apk的逻辑。

工具:夜神模拟器 apktoolbox v1.6.4 Smali2java

步骤:反编译apk得到smali文件 –> 修改smali文件 –> 回编译apk

一道题目体现apk的反编译与回编译

题目来自于jarvisOJ 爬楼梯

安卓模拟器打开先开开是个什么鬼

大概就是点击一下爬一层楼,一片的楼层加一,当已爬楼层大于要爬的楼层应该就可以看flag了。

手点是不可能的了,自动点击又不会,就只能试试能不能py一下了。

首先用apktool进行反编译,一定要忽略res资源,别问我为什么,为了纪念我消逝的青春

在smali\com\ctf\test\ctf_100找到MainActivity.smali,直接用smali2java转化为java,当然如果你熟悉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
49
50
51
public class MainActivity extends AppCompatActivity {
public int has_gone_int;
public int to_reach_int;

public native String get_flag(int p1);


protected void onCreate(Bundle p1) {
super.onCreate(p1);
setContentView(0x7f040019);
Button "bt" = (Button)findViewById(0x7f0c0056);
"bt".setClickable(false); //看flag按钮默认无效
has_gone_int = 0x0;
Random "random" = new Random();
to_reach_int = "random".nextInt();
if(to_reach_int < 0) {
to_reach_int = (to_reach_int * -0x1);
}
if(0x5 < to_reach_int) {
}
to_reach_int = "random".nextInt();
to_reach_int = (to_reach_int % 0x20);
to_reach_int = (to_reach_int * 0x4000);
TextView "tv" = (TextView)findViewById(0x7f0c0053);
"tv".setText("" + to_reach_int);
TextView "tv_result" = (TextView)findViewById(0x7f0c0057);
"tv_result".setText("");
}

public void Btn_up_onclick(View p1) {
has_gone_int = (has_gone_int + 0x1);
String "data" = "" + has_gone_int;
TextView "tv" = (TextView)findViewById(0x7f0c0054);
"tv".setText("data");
if(to_reach_int <= has_gone_int) {
Button "bt" = (Button)findViewById(0x7f0c0056);
"bt".setClickable(true); //看flag按钮有效
}
}

public void btn2_onclick(View p1) {
TextView "tv_result" = (TextView)findViewById(0x7f0c0057);
"tv_result".setText("{Flag:" + get_flag(to_reach_int) + "}");
}

static {
if(!Debug.isDebuggerConnected()) {
System.loadLibrary("ctf");
}
}
}

因此只要叫看flag按钮初始化为true就可以了。接下来去修改smali:先找到setClickable,发现两处

1
2
3
invoke-virtual {v0, v3}, Landroid/widget/Button;->setClickable(Z)V

invoke-virtual {v0, v5}, Landroid/widget/Button;->setClickable(Z)V

一个一个来,搜索v3,发现没有v3=0,搜索v5,发现

1
const/4 v5, 0x0

将0x0改为0x1,然后用apktool回编译成apk,用模拟器打开

总结

smali文件就像汇编文件,可以直接修改来改变apk的逻辑,从而达到想要的目的。这里只是小试牛刀,后面还有更大的功能等待挖掘。附上一篇大佬博客以供学习https://blog.csdn.net/u012573920/article/details/44034397

文章作者: kangel
文章鏈接: https://j-kangel.github.io/2019/04/18/smali/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL