今天受好友nameless的委托,对一个名叫nokelock的apk进行插桩,希望在日志中打印出蓝牙加密包的密文,密钥与明文,由于本人是第一次对apk进行插桩,于是写了这一篇文章用以学习和记录.
0x01 什么是插桩?
依据本人的拙见,插桩就是在原来的代码中插入自己写的代码,用以打印某些想要知道的变量的值.
在过去学习fuzz的过程中,也有过插桩这一个概念,而过去使用的是在二进制汇编代码中插桩,这一次是需要在apk中进行插桩,今日总体的插桩流程体验下来,本人明显感觉到在apk中插桩的灵活性明显跟好,而且可操作性更强.
0x02 对于apk的初步处理
需要插桩的代码位置&&需要打印的变量名称
现在我们给到的是一个base.apk
如果我们想要对代码进行插桩,那么我们首先需要知道我们要在代码的什么地方去插桩
很幸运当我给到这一个apk的时候,nameless已经找到了需要插桩的代码位于com.nokelock.blelibrary.b.b
中
我们用jadx
反编译看一下相关的java代码
一看代码,这就是很典型的AES
加密,而我们需要在日志中打印的变量也一目了然,分别是bArr
,bArr2
和instance.doFinal(bArr)
得到apk的smali代码
假如我们想要对apk进行插桩,我们无法直接修改反编译出的java代码,而是需要修改比java代码更加底层的smali代码
简单来说,java和smali代码的关系可以简单类比为c语言和汇编语言之间的关系
那么如何查看smali代码呢?
简单查看smali代码
如果在jadx
中,我们可以直接对相关的java代码按一下tab
键,然后就能得到更加原始的smali代码
但是需要注意的是,虽然这种方式可以查看其smali代码,但是无法重新编译并打包apk,就是说你可以看,但是你改不了:]
修改smali代码并重新打包签名生成apk
这里我强烈推荐使用这个工具,问就是相当的好用;)
下载完成之后打开apk easy tool
,然后把我们需要反编译的apk拖到这个工具里面,直接先点一下反编译,然后在点一下打开反编译目录,就可以看到apk的smali代码了
之后根据我们需要插桩的代码的路径(com.nokelock.blelibrary.b.b
)一级一级的点进去,就得到了可以直接编辑的smali代码
0x03 如何插桩?
当我们打开b.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
|
.method public static a([B[B)[B
.
locals
2
:try_start_0
new
-
instance v0, Ljavax
/
crypto
/
spec
/
SecretKeySpec;
const
-
string v1,
"AES"
invoke
-
direct {v0, p1, v1}, Ljavax
/
crypto
/
spec
/
SecretKeySpec;
-
><init>([BLjava
/
lang
/
String;)V
const
-
string p1,
"AES/ECB/NoPadding"
invoke
-
static {p1}, Ljavax
/
crypto
/
Cipher;
-
>getInstance(Ljava
/
lang
/
String;)Ljavax
/
crypto
/
Cipher;
move
-
result
-
object
p1
const
/
4
v1,
0x1
invoke
-
virtual {p1, v1, v0}, Ljavax
/
crypto
/
Cipher;
-
>init(ILjava
/
security
/
Key;)V
invoke
-
virtual {p1, p0}, Ljavax
/
crypto
/
Cipher;
-
>doFinal([B)[B
move
-
result
-
object
p0
:try_end_0
.catch Ljava
/
lang
/
Exception; {:try_start_0 .. :try_end_0} :catch_0
return
-
object
p0
:catch_0
const
/
4
p0,
0x0
return
-
object
p0
.end method
.method public static b([B[B)[B
.
locals
2
:try_start_0
new
-
instance v0, Ljavax
/
crypto
/
spec
/
SecretKeySpec;
const
-
string v1,
"AES"
invoke
-
direct {v0, p1, v1}, Ljavax
/
crypto
/
spec
/
SecretKeySpec;
-
><init>([BLjava
/
lang
/
String;)V
const
-
string p1,
"AES/ECB/NoPadding"
invoke
-
static {p1}, Ljavax
/
crypto
/
Cipher;
-
>getInstance(Ljava
/
lang
/
String;)Ljavax
/
crypto
/
Cipher;
move
-
result
-
object
p1
const
/
4
v1,
0x2
invoke
-
virtual {p1, v1, v0}, Ljavax
/
crypto
/
Cipher;
-
>init(ILjava
/
security
/
Key;)V
invoke
-
virtual {p1, p0}, Ljavax
/
crypto
/
Cipher;
-
>doFinal([B)[B
move
-
result
-
object
p0
:try_end_0
.catch Ljava
/
lang
/
Exception; {:try_start_0 .. :try_end_0} :catch_0
return
-
object
p0
:catch_0
const
/
4
p0,
0x0
return
-
object
p0
.end method
|
初见这种smali代码可能会有手足无措的感觉,但是我自己感觉smali代码其实要比汇编更加的简单易懂
回到正题,对于首次插桩,我们可以在github上下载现成的插桩代码
用法很简单,首先将代码下载下来之后,将SewellDinGLog.smali
直接复制到apk easy tool
反编译完成后的那个smail文件夹中如图所示
在哪里插桩?
ok我们重新回到b.smali
中
我们可以插桩的代码位置有两处(为什么是这两处!?没有别的地方吗----亲身经历,要是插桩在别的地方会无法回编译报错:0)
- 在invoke-static/invoke-virtual/invoke-direct指令返回类型是V之后可以加入
- 在invoke-static/invoke-virtual/invoke-direct指令返回类型不是V,那么在move-result-object命令之后可以加入
插桩代码
1
2
3
|
invoke
-
static {}, LSewellDinGLog;
-
>Log()V
invoke
-
static {v1}, LSewellDinGLog;
-
>Log(Ljava
/
lang
/
Object
;)V
invoke
-
static {v1}, LSewellDinGLog;
-
>Log([Ljava
/
lang
/
Object
;)V
|
这里的插桩代码我们可以结合SewellDinGLog.java
来看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import
java.util.Arrays;
import
android.util.Log;
public
class
SewellDinGLog {
public static void Log(String tag, String msg) {
/
/
两个参数
Log.d(tag, msg);
}
public static void Log() {
/
/
无参数
Log(
"SewellDinG"
,
"DeBug ..."
);
}
public static void Log(
Object
someObj) {
/
/
一个参数,打印字符串
Log(
"SewellDinG"
, someObj.toString());
}
public static void Log(
Object
[] someObj) {
/
/
一个参数,打印数组
Log(
"SewellDinG"
, Arrays.toString(someObj));
}
}
|
代码很简单,就是直接使用Log来打印变量
随后我们再次打开apk easy tool
,点一下回编译,在点一下打开回编译目录,就能找到最新编译成功的apk
查看日志
之后使用adb
命令运行该apk,然后使用命令就可以查看打印的日志
1
|
adb logcat
-
s SewellDinG
|
0x04 意料之外的错误
但是当我将插桩后的代码发给nameless后,确实有日志打印出来,但是却是长这个样子的
1
2
3
4
5
6
7
8
|
F:\platform
-
tools>adb logcat
-
s SewellDinG
-
-
-
-
-
-
-
-
-
beginning of main
03
-
27
12
:
38
:
56.882
14325
14325
D SewellDinG: [B@
3ad0a72
03
-
27
12
:
38
:
56.882
14325
14325
D SewellDinG: [B@
85ec3
03
-
27
12
:
38
:
56.882
14325
14325
D SewellDinG: [B@e41f340
03
-
27
12
:
39
:
05.392
14325
14325
D SewellDinG: [B@
4ee0ab4
03
-
27
12
:
39
:
05.392
14325
14325
D SewellDinG: [B@bd596dd
03
-
27
12
:
39
:
05.392
14325
14325
D SewellDinG: [B@
9d6c252
|
我正疑惑呢难道密钥密文明文都是[B@
开头的?后来才发现是SewellDinGLog.smali
代码有问题
首先我需要打印的是一个byte
类型是数组,而打印数组的java代码是长这个样子的
1
2
3
|
public static void Log(
Object
[] someObj) {
/
/
一个参数,打印数组
Log(
"SewellDinG"
, Arrays.toString(someObj));
}
|
这里就有问题了呀,直接使用toString
方法,对于字节数组来说返回是字节数组的地址而非字节数组的值!
0x05 处理bug,重新编写代码SewellDinGLog.smali
在这个过程中,我清楚的认识到我应该修改的是smali代码,但是如何修改?
我认为最为简便的方法就是先编写java代码,然后再编译成smali代码,可是要怎么实现呢?
我java的运行平台都是IDEA,但是我发现SewellDinGLog.java
所需要的包android.util.Log
根本就没有,在网上找了找也找不到这个包,之后通过google了很久,才知道我需要安装一个
安装好了之后,就新建一个项目,然后把要运行的代码复制粘贴进去
要注意这里Language
一定要选Java,不要选Kotlin
,不然得像我一样下了很久的安装包最后发现代码根本运行不了
第一次使用Android studio
,得需要等大约几十分钟安装运行所需要的包
随后点击File-->Settings-->Plugins
,然后在插件市场搜索java2smali
,安装完之后重启Android studio
,然后在Build-->Compile to smali
就可以直接生成smali代码了
为了能够成功Log打印字节数组,我们需要对原先的SewellDinGLog.java
代码进行相应的修改,然后使用Build-->Compile to smali
生成smail代码
修改后的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
|
package com.example.newlog;
import
android.util.Log;
public
class
SewellDinGLog {
final protected static char[] hexArray
=
"0123456789ABCDEF"
.toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars
=
new char[bytes.length
*
2
];
for
(
int
j
=
0
; j < bytes.length; j
+
+
) {
int
v
=
bytes[j] &
0xFF
;
hexChars[j
*
2
]
=
hexArray[v >>>
4
];
hexChars[j
*
2
+
1
]
=
hexArray[v &
0x0F
];
}
return
new String(hexChars);
}
public static void Log(String tag, String msg) {
Log.d(tag, msg);
}
public static void Log() {
Log(
"SewellDinG"
,
"DeBug ..."
);
}
public static void Log(byte[] someObj) {
String result
=
bytesToHex(someObj);
Log(
"SewellDinG"
, result);
}
}
|
随后在同级文件夹下就会生成SewellDinGLog.smali
这里要注意的是由于我们的SewellDinGLog.smali
是放在apk easy tool
反编译后的smali文件夹的根文件夹下,所以我们需要对SewellDinGLog.smali
内的代码进行修改
例如我这里
那么需要将所有的Lcom/example/newlog/SewellDinGLog
全部替换成LSewellDinGLog
变成这样
随后和之前的步骤一模一样,把SewellDinGLog.smali
复制到smali
文件夹内,然后插桩,回编译就可以了
插桩后的apk我们再用jadx
反编译看看
发现相较之前多了SewellDinGLog.Log
函数用以打印变量的值
最后我们也是成功打印出变量的值
1
2
3
4
5
6
7
8
9
10
|
F:\platform
-
tools>adb logcat
-
s SewellDinG
-
-
-
-
-
-
-
-
-
beginning of main
03
-
27
17
:
18
:
08.895
31117
31117
D SewellDinG:
05010630303030303082E3C89616017D
03
-
27
17
:
18
:
08.895
31117
31117
D SewellDinG:
241F632E5907042061014C1A3A45193B
03
-
27
17
:
18
:
08.895
31117
31117
D SewellDinG:
6629B62C88A7E50525E92C328AF258E6
03
-
27
17
:
18
:
10.114
31117
31117
D SewellDinG:
732F5CB22C06B0C2D0D17AD31D165805
03
-
27
17
:
18
:
10.114
31117
31117
D SewellDinG:
241F632E5907042061014C1A3A45193B
03
-
27
17
:
18
:
10.114
31117
31117
D SewellDinG:
05020100E3C896010202000000000000
03
-
27
17
:
18
:
11.770
31117
31117
D SewellDinG:
530B1FCA1467A408A321E71F3D152127
03
-
27
17
:
18
:
11.771
31117
31117
D SewellDinG:
241F632E5907042061014C1A3A45193B
|
0x06 一个自问自答
Q: 为什么不直接使用android.util.Log中的Log函数来打印,即在需要插桩的位置插入这串代码
1
|
invoke
-
static {p0, p1}, Landroid
/
util
/
Log;
-
>d(Ljava
/
lang
/
String;Ljava
/
lang
/
String;)I
|
,而是调用另一个类中的函数来打印呢?
A: 我认为重写一个Log打印方法,灵活性将会更高,我们可以将复杂的代码先用java编写,然后再编译成smali,这样比直接用smali写代码要更简单方便,只需要插桩的位置添加一个函数方法的调用就可以了.