[原创]对某apk的一次插桩记录
2023-3-31 22:26:0 Author: bbs.pediy.com(查看原文) 阅读量:41 收藏

今天受好友nameless的委托,对一个名叫nokelock的apk进行插桩,希望在日志中打印出蓝牙加密包的密文,密钥与明文,由于本人是第一次对apk进行插桩,于是写了这一篇文章用以学习和记录.

0x01 什么是插桩?

依据本人的拙见,插桩就是在原来的代码中插入自己写的代码,用以打印某些想要知道的变量的值.

在过去学习fuzz的过程中,也有过插桩这一个概念,而过去使用的afl-fuzz是在二进制汇编代码中插桩,这一次是需要在apk中进行插桩,今日总体的插桩流程体验下来,本人明显感觉到在apk中插桩的灵活性明显跟好,而且可操作性更强.

0x02 对于apk的初步处理

需要插桩的代码位置&&需要打印的变量名称

现在我们给到的是一个base.apk

image-20230327184321422

如果我们想要对代码进行插桩,那么我们首先需要知道我们要在代码的什么地方去插桩

很幸运当我给到这一个apk的时候,nameless已经找到了需要插桩的代码位于com.nokelock.blelibrary.b.b

我们用jadx反编译看一下相关的java代码

image-20230327184914926

一看代码,这就是很典型的AES加密,而我们需要在日志中打印的变量也一目了然,分别是bArr,bArr2instance.doFinal(bArr)

得到apk的smali代码

假如我们想要对apk进行插桩,我们无法直接修改反编译出的java代码,而是需要修改比java代码更加底层的smali代码

简单来说,java和smali代码的关系可以简单类比为c语言和汇编语言之间的关系

那么如何查看smali代码呢?

简单查看smali代码

如果在jadx中,我们可以直接对相关的java代码按一下tab键,然后就能得到更加原始的smali代码

image-20230327185747093

但是需要注意的是,虽然这种方式可以查看其smali代码,但是无法重新编译并打包apk,就是说你可以看,但是你改不了:]

修改smali代码并重新打包签名生成apk

这里我强烈推荐使用apk easy tool这个工具,问就是相当的好用;)

下载完成之后打开apk easy tool,然后把我们需要反编译的apk拖到这个工具里面,直接先点一下反编译,然后在点一下打开反编译目录,就可以看到apk的smali代码了

image-20230327190347337

image-20230327190543349

之后根据我们需要插桩的代码的路径(com.nokelock.blelibrary.b.b)一级一级的点进去,就得到了可以直接编辑的smali代码

image-20230327190738365

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上下载现成的插桩代码 Smali插桩自用代码库

用法很简单,首先将代码下载下来之后,将SewellDinGLog.smali直接复制到apk easy tool反编译完成后的那个smail文件夹中如图所示

image-20230327192145198

在哪里插桩?

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来打印变量

随后我们便可以根据插桩的规则,插入相应的代码,位置类似下图

image-20230327193625493

随后我们再次打开apk easy tool,点一下回编译,在点一下打开回编译目录,就能找到最新编译成功的apk

image-20230327193848391

查看日志

之后使用adb命令运行该apk,然后使用命令就可以查看打印的日志

1

0x04 意料之外的错误

但是当我将插桩后的代码发给nameless后,确实有日志打印出来,但是却是长这个样子的

我正疑惑呢难道密钥密文明文都是[[email protected]开头的?后来才发现是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了很久,才知道我需要安装一个Android Studio

安装好了之后,就新建一个项目,然后把要运行的代码复制粘贴进去

image-20230327195721325

要注意这里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

image-20230327200425926

这里要注意的是由于我们的SewellDinGLog.smali是放在apk easy tool反编译后的smali文件夹的根文件夹下,所以我们需要对SewellDinGLog.smali内的代码进行修改

例如我这里

image-20230327202329460

那么需要将所有的Lcom/example/newlog/SewellDinGLog全部替换成LSewellDinGLog

变成这样

image-20230327202432870

随后和之前的步骤一模一样,把SewellDinGLog.smali复制到smali文件夹内,然后插桩,回编译就可以了

插桩后的apk我们再用jadx反编译看看

发现相较之前多了SewellDinGLog.Log函数用以打印变量的值

image-20230331194727784

最后我们也是成功打印出变量的值

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写代码要更简单方便,只需要插桩的位置添加一个函数方法的调用就可以了.

[2023春季班]2023,新的征程,脱壳机更新、iOS/eBPF、赠送云手机套装!一块裸板虚拟化五个容器云手机!3月25日起同时上调价格并赠送新设备!


文章来源: https://bbs.pediy.com/thread-276708.htm
如有侵权请联系:admin#unsafe.sh