利用Ptrace在Android平台實現應用程序控製
但凡做過安全軟件的人都知道,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" , null , null );
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;
// LOGI("target=%p code=%d pid=%d uid=%d\n", // txn->target, txn->code, txn->sender_pid, txn->sender_euid); if (txn->target != svcmgr_handle)
return -1;
// Equivalent to Parcel::enforceInterface(), reading the RPC
// header with the strict mode policy mask and the interface name.
// Note that we ignore the strict_policy and don't propagate it
// further (since we do no outbound RPCs anyway).
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); //b from 0x8950 to 0xa294
} void build_fun_asm( char * asm_bin, int * len)
{ unsigned long block_uid = 10038;
append_asm(asm_bin, len, 0xB502, 2); //push {r1,lr}
//fun asm
append_asm(asm_bin, len, 0xB4FF, 2); // push {r0-r7}
append_asm(asm_bin, len, 0x6967, 2); // LDR R7, [R4,#0x14]
append_asm(asm_bin, len, 0x4B05, 2); // ldr r3,=block_uid
append_asm(asm_bin, len, 0x429F, 2); // CMP R7, R3
append_asm(asm_bin, len, 0xBCFF, 2); // pop {r0-r7}
append_asm(asm_bin, len, 0xD002, 2); // BEQ return -1
//return
append_asm(asm_bin, len, 0x6803, 2); //LDR R3, [R0]
append_asm(asm_bin, len, 0x6822, 2); //LDR R2, [R4]
append_asm(asm_bin, len, 0xBD02, 2); //pop {r1,pc}
//return -1
append_asm(asm_bin, len, 0x2300, 2); //mov r3, #0
append_asm(asm_bin, len, 0x2201, 2); //mov r2, #1
append_asm(asm_bin, len, 0xBD02, 2); //pop {r1,pc}
append_asm(asm_bin, len, 0x1C00, 2); //nop
//write return address
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獲得成功,哇哢哢~~
最後更新:2017-04-03 05:39:33