惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
S
SegmentFault 最新的问题
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
F
Fortinet All Blogs
TaoSecurity Blog
TaoSecurity Blog
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
K
Kaspersky official blog
宝玉的分享
宝玉的分享
腾讯CDC
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
T
The Exploit Database - CXSecurity.com
T
The Blog of Author Tim Ferriss
V
V2EX
S
Securelist
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
CERT Recently Published Vulnerability Notes
A
Arctic Wolf
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Y
Y Combinator Blog
P
Proofpoint News Feed
T
Tor Project blog
AWS News Blog
AWS News Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
博客园 - 聂微东
T
Threat Research - Cisco Blogs
B
Blog
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
N
News and Events Feed by Topic
博客园 - 司徒正美
H
Help Net Security
C
Cisco Blogs
C
Check Point Blog
S
Secure Thoughts

博客园 - hyamw

【转】代码审查工具fisheye/crucible安装及破解 Win11单语言系统添加美式键盘的方法 Unity中AndroidJavaProxy方法参数为null的坑 CentOS下安装dotnet tools工具报错 (转)支持 PS/2 与 USB 的键盘过滤驱动(可卸载) Unity编辑器扩展-Custom List, displaying data your way 值得推荐的C/C++框架和库 (真的很强大)〔转〕 SIGGRAPH2016【转】 在64位windows下使用instsrv.exe和srvany.exe创建windows服务[转] VS2015调试UWP程序时提示错误DEP0700 : Registration of the app failed. Another user has already installed Unity 4.x Asset Bundle 重名 VC++ Debugger Tips[转] centos下postgresql的安装与配置[转] CentOS 访问Windows7共享文件夹 svn+ssh方式svn服务器和客户端的配置[转载] AS3地图拼接与战争迷雾的实现[转载] 一些游戏开发相关资料(收集) Bit Twiddling Hacks[转] Unity3D实用工具汇总[转]
利用Ptrace在Android平台实现应用程序控制[转]
hyamw · 2013-03-07 · via 博客园 - hyamw

但凡做过安全软件的人都知道,API Hook和App Control是经常要实现的功能。

为了实现这两个功能,最常用的方法就是写driver,在kernel中拦截检查相应的调用。这种做法的好处是大小通吃,不用关心系统里面到底有多少进程,反正你要做的操作最终总要过我这一关。而缺点就是在kernel中拦截往往得不到我想要的一些参数而无法做出正确的判断。举个例子,手机平台中很多应用都会发短信,我想组织某些应用发短信而允许另一些应用发短信。而发短信的操作并不是由每个应用直接调用的,它们把发送请求发给一个叫SmsService的服务进程,由这个服务进程再调用系统API来发短信。当我们在Kernel里面拦截到这个API的时候,发现调用者都是SmsService,无法区分原始请求者,因而无法做到有目的的拦截。

为了解决上面提到的问题,很多厂商开始想办法Hook SmsService,在SmsService里面可以区分请求的应用和请求的内容,进而做到有区分的拦截。那么如何Hook SmsService 呢?在Linux/Unix/Mac/iOS系统里面最简单的办法可能就是Ptrace了。所以说利用Ptrace可以很容易的做到精准打击,可以具体到Hook每个进程里面的每个点,这是它的优点。那么再说一下它的缺点,如果你想Hook多个进程,你就要给每个进程准备不同的代码,除非你可以找到SmsService这样的“关口”,即便找到了关口,还要提放有没有办法绕过它。

综上所述,Ptrace是一个用来实现API Hook和App Control 的好工具。很多情况下它的缺点可以被忽视,尤其在Android这种很容易找到"关口"的平台上。因此很多Android上的安全软件都是利用Ptrace来实现API Hook和App Control的。

前面扯的有点多,先来说一下这次的目标:

平台:Android 2.3

实现:利用Ptrace剥夺某些应用请求service的功能

1. 编写测试程序SmsSender1

SmsSender1非常简单,它有一个Activity,在onCreate的时候发送一条SMS. 我们的目标就是拦截这个应用请求系统Service的能力而不影响其他应用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        SmsManager smsM = SmsManager.getDefault();

        if(smsM != null)

        {

            smsM.sendTextMessage("123456"null"abc"nullnull);

            Toast.makeText(this"send sms", Toast.LENGTH_SHORT).show(); 

        }

        else {

            Toast.makeText(this"sm is null", Toast.LENGTH_SHORT).show(); 

        }

    }

2. 研究servicemanager

servicemanager是我们用Ptrace要修改的目标。为什么选servicemanager? 根据Android的架构,系统的各种service (比如phone, sms, camera)是手机核心功能的“关口”,而servicemanager就是这些service的总"关口"。每个service都要到servicemanager注册,而每个app要想获取服务,需要先到servicemanager来查询各个服务的ID,然后才能根据ID和相应的service利用binder进行通信。所以说把住了servicemanager,就把住了所有关键的服务。

那么从何下手呢?

俗话说源码之前,了无秘密。既然有Android源码,就看源码来加速我们的研究。看了源码以后发现servicemanager里面有一个loop,用来处理各种请求,算是个下手的好地方:

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

int svcmgr_handler(struct binder_state *bs,

                   struct binder_txn *txn,

                   struct binder_io *msg,

                   struct binder_io *reply)

{

    struct svcinfo *si;

    uint16_t *s;

    unsigned len;

    void *ptr;

    uint32_t strict_policy;

    if (txn->target != svcmgr_handle)

        return -1;

    strict_policy = bio_get_uint32(msg);

    s = bio_get_string16(msg, &len);

    if ((len != (sizeof(svcmgr_id) / 2)) ||

        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {

        fprintf(stderr,"invalid id %s\n", str8(s));

        return -1;

    }

    switch(txn->code) {

    case SVC_MGR_GET_SERVICE:

    case SVC_MGR_CHECK_SERVICE:

        s = bio_get_string16(msg, &len);

        ptr = do_find_service(bs, s, len);

        if (!ptr)

            break;

        bio_put_ref(reply, ptr);

        return 0;

    case SVC_MGR_ADD_SERVICE:

        s = bio_get_string16(msg, &len);

        ptr = bio_get_ref(msg);

        if (do_add_service(bs, s, len, ptr, txn->sender_euid))

            return -1;

        break;

    case SVC_MGR_LIST_SERVICES: {

        unsigned n = bio_get_uint32(msg);

        si = svclist;

        while ((n-- > 0) && si)

            si = si->next;

        if (si) {

            bio_put_string16(reply, si->name);

            return 0;

        }

        return -1;

    }

    default:

        LOGE("unknown code %d\n", txn->code);

        return -1;

    }

    bio_put_uint32(reply, 0);

    return 0;

}

Hook这个函数,通过参数txn和msg可以获知是谁在发起请求,请求什么东西。我们的计划是通过txn->sender_euid来判断是哪个app在请求,然后屏蔽前面写的SmsSender1的所有请求,让它什么都干不了。

具体如何实现?我们现在有两个选择:

选择1, 写一个trace程序,利用Ptrace attach到servicemanager进程,在函数svcmgr_handler中添加断点。中断之后trace根据txn->sender_euid的值来修改相应的寄存器或者内存值,达到改变程序流程的目的。这种做法需要trace一直运行,处于调试servicemanager的状态,每次svcmgr_handler都需要从trace过一次。如果trace挂了会导致不可预料的结果。

选项2,写一个trace程序,只运行一次,利用Ptrace attach到servicemanager进程,在text段修改svcmgr_handler的逻辑并插入我们的代码,然后dettach. 这样的话trace不必长期运行,也不用每次调用都过trace,稳定性和效率都大大提高。

就技术而言,选项2需要考虑更多的问题,也更复杂。但我还是喜欢选项2,呵呵,后面就按照选项2来实现。

3. 编写injection的汇编代码

反编译servicemanager,定位到svcmgr_handler函数:

其中0x8950,0x8952,0x8954,0x8956四条指令对应于源码中这两行:

1

2

if (txn->target != svcmgr_handle)

        return -1;

我们就从0x8950开始修改,将LDR R3,[R0]; LDR R2,[R4];替换成我们的跳转指令BL,跳转的目的地是我们即将插入的逻辑代码,就是判断txn->sender_euid并决定是否屏蔽请求的代码。

我们先将SmsSender1安装到Android上,并查看它的uid. 我们知道在Android系统中每个app都被分配了一个用户,利用用户来进行权限管理,而uid就是用户id,用户分配表可以在/data/system/packages.list里面找到:

可见我们要屏蔽的uid是10038,所以我们的判断逻辑代码编写如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

push {r1,lr}

push {r0-r7}

LDR R7, [R4,#0x14]

ldr r3,=10013

CMP R7, R3

pop {r0-r7}

BEQ loc_ret_1

LDR R3, [R0]

LDR R2, [R4]

pop {r1,pc}

loc_ret_1:

mov r3, #0

mov r2, #1

pop {r1,pc}

逻辑很简单,line3是获取txn->sender_euid,并和10013比较,如果相等,则将r2和r3的值分别设为1和0,目的是让它们不等,因为servicemanager后续的逻辑如果r2!=r3就会return -1;

另外别忘了我们覆盖了servicemanager两条指令LDR R3,[R0]; LDR R2,[R4];,如果uid!=10013,我们需要让程序按照原来的逻辑执行下去,所以line8-9是补充执行被我们覆盖的两条指令。

好了,万事俱备只欠东风,接下来我们可以开始编写trace程序了。

3. 编写trace程序

我们最终完成的trace程序如下:

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

#include <sys/ptrace.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

#include <sys/syscall.h>

int long_size = sizeof(long);

void append_asm(char* asm_bin, int* len, unsigned long aasm, int alen)

{

    int i;

    char* aasmp = &aasm;

    for(i=0; i<alen; i++)

    {

        *(asm_bin+(*len)) = *(aasmp+i);

        (*len)++;

    }

}

void getdata(pid_t pid, long addr,

        char *str, int len)

{   

    char *laddr;

    int i, j;

    union u {

        long val;

        char chars[long_size];

    }data;

    i = 0;

    j = len / long_size;

    laddr = str;

    while(i < j) {

        data.val = ptrace(PTRACE_PEEKDATA,

                pid, addr + i * 4,

                NULL);

        memcpy(laddr, data.chars, long_size);

        ++i;

        laddr += long_size;

    }

    j = len % long_size;

    if(j != 0) {

        data.val = ptrace(PTRACE_PEEKDATA,

                pid, addr + i * 4,

                NULL);

        memcpy(laddr, data.chars, j);

    }

    str[len] = '\0';

}

void putdata(pid_t pid, long addr,

        char *str, int len)

{   

    char *laddr;

    int i, j;

    union u {

        long val;

        char chars[long_size];

    }data;

    i = 0;

    j = len / long_size;

    laddr = str;

    while(i < j) {

        memcpy(data.chars, laddr, long_size);

        ptrace(PTRACE_POKEDATA, pid,

                addr + i * 4, data.val);

        ++i;

        laddr += long_size;

    }

    j = len % long_size;

    if(j != 0) {

        memcpy(data.chars, laddr, j);

        ptrace(PTRACE_POKEDATA, pid,

                addr + i * 4, data.val);

    }

}

void build_jmp_asm(char* asm_bin, int* len, unsigned long freeaddr)

{

    append_asm(asm_bin, len, 0xFCA0F001, 4); 

}

void build_fun_asm(char* asm_bin, int* len)

{

    unsigned long block_uid = 10038;

    append_asm(asm_bin, len, 0xB502, 2); 

    append_asm(asm_bin, len, 0xB4FF, 2); 

    append_asm(asm_bin, len, 0x6967, 2); 

    append_asm(asm_bin, len, 0x4B05, 2); 

    append_asm(asm_bin, len, 0x429F, 2); 

    append_asm(asm_bin, len, 0xBCFF, 2); 

    append_asm(asm_bin, len, 0xD002, 2); 

    append_asm(asm_bin, len, 0x6803, 2); 

    append_asm(asm_bin, len, 0x6822, 2); 

    append_asm(asm_bin, len, 0xBD02, 2); 

    append_asm(asm_bin, len, 0x2300, 2); 

    append_asm(asm_bin, len, 0x2201, 2); 

    append_asm(asm_bin, len, 0xBD02, 2); 

    append_asm(asm_bin, len, 0x1C00, 2); 

    append_asm(asm_bin, len, block_uid, 4);

}

void print_asm(char* asm_bin, int len)

{

    int i;

    for(i=0; i<len; i++)

    {

        printf("%x ", *(asm_bin+i));

    }

    printf("\n\n");

}

void tracePro(int pid)

{

    unsigned long replace_addr;

    unsigned long freeaddr;

    char asm_jump[32];

    int asm_jump_len=0;

    char asm_fun[1024];

    int asm_fun_len=0;

    char temp[1024];

    replace_addr = 0x8950;

    freeaddr = 0xA294;

    build_jmp_asm(asm_jump, &asm_jump_len, freeaddr);

    build_fun_asm(asm_fun, &asm_fun_len);

    putdata(pid, replace_addr, asm_jump, asm_jump_len);

    putdata(pid, freeaddr, asm_fun, asm_fun_len);

    getdata(pid, replace_addr, temp, 64);

    print_asm(temp, 64);

    getdata(pid, freeaddr, temp, 64);

    print_asm(temp, 64);

}

int main(int argc, char *argv[])

{   

    if(argc != 2) {

        printf("Usage: %s <pid to be traced>\n", argv[0], argv[1]);

    return 1;

    }

    pid_t traced_process;

    int status;

    traced_process = atoi(argv[1]);

    if(0 != ptrace(PTRACE_ATTACH, traced_process, NULL, NULL))

    {

        printf("Trace process failed:%d.\n"errno);

    return 1;

    }

    tracePro(traced_process);

    ptrace(PTRACE_DETACH, traced_process, NULL, NULL);

    return 0;

}

其中replace_addr = 0x8950;是我们修改svcmgr_handler函数进行拦截的地址。freeaddr = 0xA294;是我们找到的可以插入我们的逻辑代码并且不会影响原有程序的地址。

我觉得程序已经写的很明白了,不需要再进一步解释了,后面开始实测。

4. 测试

先运行模拟器,上传trace程序。运行SmsSender1和系统自带的SMS程序,均可正常运行发送短信。

然后ps查看servicemanager的pid,发现是28. 运行trace 28, 修改内存,inject代码,发现我们的代码已成功写入:

然后运行系统自带的SMS,可正常运行发送短信。运行SmsSender1,无法正常运行。

事实证明App Control获得成功,哇咔咔~~