隨著移動安全越來越火,各種調試工具也都層出不窮,但因為環境和需求的不同,并沒有工具是萬能的。另外工具是死的,人是活的,如果能搞懂工具的原理再結合上自身的經驗,你也可以創造出屬于自己的調試武器。因此,筆者將會在這一系列文章中分享一些自己經常用或原創的調試工具以及手段,希望能對國內移動安全的研究起到一些催化劑的作用。
目錄如下:
安卓動態調試七種武器之長生劍 - Smali Instrumentation
安卓動態調試七種武器之孔雀翎 – Ida Pro
安卓動態調試七種武器之離別鉤 – Hooking (上)
安卓動態調試七種武器之離別鉤 – Hooking (下)
安卓動態調試七種武器之碧玉刀- Customized DVM
安卓動態調試七種武器之多情環- Customized Kernel
安卓動態調試七種武器之霸王槍 - Anti Anti-debugging
安卓動態調試七種武器之拳頭 - Tricks & Summary
文章中所有提到的代碼和工具都可以在我的github下載到,地址是:
https://github.com/zhengmin1989/TheSevenWeapons
Hooking翻譯成中文就是鉤子的意思,所以正好配合這一章的名字《離別鉤》。
“我知道鉤是種武器,在十八般兵器中名列第七,離別鉤呢?”
“離別鉤也是種武器,也是鉤。”
“既然是鉤,為什么要叫做離別?”
“因為這柄鉤,無論鉤住什么都會造成離別。如果它鉤住你的手,你的手就要和腕離別;如果它鉤住你的腳,你的腳就要和腿離別。”
“如果它鉤住我的咽喉,我就和這個世界離別了?”
“是的,”
“你為什么要用如此殘酷的武器?”
“因為我不愿被人強迫與我所愛的人離別。”
“我明白你的意思了。”
“你真的明白?”
“你用離別鉤,只不過為了要相聚。”
“是的。”
一提到hooking,讓我又回想起了2011年的時候。當時android才出來沒多久,各大安全公司都在忙著研發自己的手機助手。當時手機上最泛濫的病毒就是短信扣費類的病毒,但僅僅是靠云端的病毒庫掃描是遠遠不夠的。而這時候”LBE安全大師”橫空出世,提供了主動防御的技術,可以在病毒發送短信之前攔截下來,并讓用戶選擇是否發送。 其實這個主動防御技術就是hooking。雖然在PC上hooking的技術已經很成熟了,但是在android上的資料卻非常稀少,只有少數人掌握著android上hooking的技術,因此這些人也變成了各大公司爭相搶奪的對象。但是沒有什么東西是能夠永久保密的,這些技術早晚會被大家研究出來并對外公開的。因此,到了2015年,android上的hook資料已經遍地都是了,各種開源的hook框架也層出不窮,使用這些hook工具就可以輕松的hook native,jni和java層的函數。但這同樣也帶來了一些問題,新手想研究hook的時候因為資料和工具太多往往不知道如何下手,并且就算使用了工具成功的hook,也根本不知道原理是什么。因此筆者準備從hook的原理開始,配合開源工具循序漸進的介紹native,jni和java層的hook,方便大家對hook進行系統的學習。
其實無論是hook還是調試都離不開ptrace這個system call,利用ptrace,我們可以跟蹤目標進程,并且在目標進程暫停的時候對目標進程的內存進行讀寫。在linux上有一篇經典的文章叫《Playing with Ptrace》,簡單介紹了如何玩轉ptrace。在這里我們照貓畫虎,來試一下playing with Ptrace on Android
。PS:這里的一部分內容借鑒了harry大牛在百度Hi寫的一篇文章,可惜后來百度Hi關了,就失傳了。不過不用擔心,我這篇比他寫的還詳細。:)
首先來看我們要ptrace的目標程序,用來一直循環輸出一句話”Hello,LiBieGou!”:
#!c++
#include <stdio.h>
int count = 0;
void sevenWeapons(int number)
{
char* str = "Hello,LiBieGou!";
printf("%s %d\n",str,number);
}
int main()
{
while(1)
{
sevenWeapons(count);
count++;
sleep(1);
}
return 0;
}
想要編譯它非常簡單,首先建立一個Android.mk文件,然后填入如下內容,讓ndk將文件編譯為elf可執行文件:
#!bash
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := target
LOCAL_SRC_FILES := target.c
include $(BUILD_EXECUTABLE)
接下來我們寫出hook1.c程序來hook target程序的system call,main函數如下:
#!c++
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: %s <pid to be traced>\n", argv[0]);
return 1;
}
pid_t pid;
int status;
pid = atoi(argv[1]);
if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
{
printf("Trace process failed:%d.\n", errno);
return 1;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
while(1)
{
wait(&status);
hookSysCallBefore(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
wait(&status);
hookSysCallAfter(pid);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
首先要知道hook的目標的pid,這個用ps命令就能獲取到。然后我們使用ptrace(PTRACE_ATTACH, pid, NULL, NULL)
這個函數對目標進程進行加載。加載成功后我們可以使用ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
這個函數來對目標程序下斷點,每當目標程序調用system call前的時候,就會暫停下載。然后我們就可以讀取寄存器的值來獲取system call的各項信息。然后我們再一次使用ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
這個函數就可以讓system call在調用完后再一次暫停下來,并獲取system call的返回值。
獲取system call編號的函數如下:
#!c++
long getSysCallNo(int pid, struct pt_regs *regs)
{
long scno = 0;
scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
if(scno == 0)
return 0;
if (scno == 0xef000000) {
scno = regs->ARM_r7;
} else {
if ((scno & 0x0ff00000) != 0x0f900000) {
return -1;
}
scno &= 0x000fffff;
}
return scno;
}
ARM架構上,所有的系統調用都是通過SWI來實現的。并且在ARM 架構中有兩個SWI指令,分別針對EABI和OABI:
[EABI]
機器碼:1110 1111 0000 0000 -- SWI 0
具體的調用號存放在寄存器r7中.
[OABI]
機器碼:1101 1111 vvvv vvvv -- SWI immed_8
調用號進行轉換以后得到指令中的立即數。立即數=調用號 | 0x900000
既然需要兼容兩種方式的調用,我們在代碼上就要分開處理。首先要獲取SWI指令判斷是EABI還是OABI,如果是EABI,可從r7中獲取調用號。如果是OABI,則從SWI指令中獲取立即數,反向計算出調用號。
我們接著看hook system call前的函數,和hook system call后的函數:
#!c++
void hookSysCallBefore(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("Before SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
}
}
void hookSysCallAfter(pid_t pid)
{
struct pt_regs regs;
int sysCallNo = 0;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
sysCallNo = getSysCallNo(pid, ®s);
printf("After SysCallNo = %d\n",sysCallNo);
if(sysCallNo == __NR_write)
{
printf("__NR_write return: %ld\n",regs.ARM_r0);
}
printf("\n");
}
在獲取了system call的number以后,我們可以進一步獲取個個參數的值,比如說write這個system call。在arm上,如果形參個數少于或等于4,則形參由R0,R1,R2,R3四個寄存器進行傳遞。若形參個數大于4,大于4的部分必須通過堆棧進行傳遞。而執行完函數后,函數的返回值會保存在R0這個寄存器里。
下面我們就來實際運行一下看看效果。我們先把target和hook1 push到 /data/local/tmp
目錄下,再chmod 777一下。接著運行target。
#!bash
# ./target
Hello,LiBieGou! 0
Hello,LiBieGou! 1
Hello,LiBieGou! 2
Hello,LiBieGou! 3
Hello,LiBieGou! 4
Hello,LiBieGou! 5
Hello,LiBieGou! 6
….
我們隨后再開一個shell,然后ps獲取target的pid,然后使用hook1程序對target進行hook操作:
#!bash
# ./hook1 23442
Before SysCallNo = 162
After SysCallNo = 162
Before SysCallNo = 4
__NR_write: 1 0xadf020 19
After SysCallNo = 4
__NR_write return: 19
Before SysCallNo = 162
After SysCallNo = 162
Before SysCallNo = 4
__NR_write: 1 0xadf020 19
After SysCallNo = 4
__NR_write return: 19
我們可以看到第一個SysCallNo是162,也就是sleep函數。第二個SysCallNo是4,也就是write函數,因為printf本質就是調用write這個系統調用來完成的。關于system call number對應的具體system call可以參考我在github上的reference文件夾中的systemcalllist.txt文件,里面有對應的列表。我們的hook1程序還對write的參數做了解析,比如1表示stdout,0xadf020表示字符串的地址,19代表字符串的長度。而返回值19表示write成功寫入的長度,也就是字符串的長度。
整個過程用圖表達如下:
僅僅是用ptrace來獲取system call的參數和返回值還不能體現出ptrace的強大,下面我們就來演示用ptrace讀寫內存。我們在hook1.c的基礎上繼續進行修改,在write被調用之前對要輸出string進行翻轉操作。
我們在hookSysCallBefore()函數中加入modifyString(pid, regs.ARM_r1, regs.ARM_r2)這個函數:
#!c++
if(sysCallNo == __NR_write)
{
printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
modifyString(pid, regs.ARM_r1, regs.ARM_r2);
}
因為write的第二個參數是字符串的地址,第三個參數是字符串的長度,所以我們把R1和R2的值傳給modifyString()這個函數:
#!c++
void modifyString(pid_t pid, long addr, long strlen)
{
char* str;
str = (char *)calloc((strlen+1) * sizeof(char), 1);
getdata(pid, addr, str, strlen);
reverse(str);
putdata(pid, addr, str, strlen);
}
modifyString()
首先獲取在內存中的字符串,然后進行翻轉操作,最后再把翻轉后的字符串寫入原來的地址。這些操作用到了getdata()
和putdata()
函數:
#!c++
void getdata(pid_t child, 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,
child, 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,
child, addr + i * 4,
NULL);
memcpy(laddr, data.chars, j);
}
str[len] = '\0';
}
void putdata(pid_t child, 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, child,
addr + i * 4, data.val);
++i;
laddr += long_size;
}
j = len % long_size;
if(j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child,
addr + i * 4, data.val);
}
}
getdata()
和putdata()
分別使用PTRACE_PEEKDATA
和PTRACE_POKEDATA
對內存進行讀寫操作。因為ptrace的內存操作一次只能控制4個字節,所以如果修改比較長的內容需要進行多次操作。
我們現在運行一下target,并且在運行中用hook2程序進行hook:
#!bash
# ./target
Hello,LiBieGou! 0
Hello,LiBieGou! 1
Hello,LiBieGou! 2
Hello,LiBieGou! 3
Hello,LiBieGou! 4
Hello,LiBieGou! 5
Hello,LiBieGou! 6
Hello,LiBieGou! 7
8 !uoGeiBiL,olleH
9 !uoGeiBiL,olleH
01 !uoGeiBiL,olleH
11 !uoGeiBiL,olleH
21 !uoGeiBiL,olleH
31 !uoGeiBiL,olleH
Hello,LiBieGou! 14
Hello,LiBieGou! 15
Hello,LiBieGou! 16
哈哈,是不是看到字符串都被翻轉了。如果我們退出hook2程序,字符串又會回到原來的樣子。
上一節中我們介紹了如何使用ptrace來修改內存,現在繼續介紹如何用ptrace來執行libc .so中的sleep()函數。主要邏輯都在inject()這個函數中:
#!c++
void inject(pid_t pid)
{
struct pt_regs old_regs,regs;
long sleep_addr;
//save old regs
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(®s, &old_regs, sizeof(regs));
printf("getting remote sleep_addr:\n");
sleep_addr = get_remote_addr(pid, libc_path, (void *)sleep);
long parameters[1];
parameters[0] = 10;
ptrace_call(pid, sleep_addr, parameters, 1, ®s);
//restore old regs
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}
首先我們用ptrace(PTRACE_GETREGS, pid, NULL, &old_regs)
來保存一下寄存器的值,然后獲取sleep()函數在目標進程中的地址,接著利用ptrace執行sleep()函數,最后在執行完sleep()函數后再用ptrace(PTRACE_SETREGS, pid, NULL, &old_regs)
恢復寄存器原來值。
下面是獲取sleep()函數在目標進程中地址的代碼:
#!c++
void* get_module_base(pid_t pid, const char* module_name)
{
FILE *fp;
long addr = 0;
char *pch;
char filename[32];
char line[1024];
if (pid == 0) {
snprintf(filename, sizeof(filename), "/proc/self/maps");
} else {
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
}
fp = fopen(filename, "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
pch = strtok( line, "-" );
addr = strtoul( pch, NULL, 16 );
if (addr == 0x8000)
addr = 0;
break;
}
}
fclose(fp) ;
}
return (void *)addr;
}
long get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr)
{
void* local_handle, *remote_handle;
local_handle = get_module_base(0, module_name);
remote_handle = get_module_base(target_pid, module_name);
printf("module_base: local[%p], remote[%p]\n", local_handle, remote_handle);
long ret_addr = (long)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);
printf("remote_addr: [%p]\n", (void*) ret_addr);
return ret_addr;
}
因為libc.so在內存中的地址是隨機的,所以我們需要先獲取目標進程的libc.so的加載地址,再獲取自己進程的libc.so的加載地址和sleep()在內存中的地址。然后我們就能計算出sleep()函數在目標進程中的地址了。要注意的是獲取目標進程和自己進程的libc.so的加載地址是通過解析/proc/[pid]/maps
得到的。
接下來是執行sleep()函數的代碼:
#!c++
int ptrace_call(pid_t pid, long addr, long *params, uint32_t num_params, struct pt_regs* regs)
{
uint32_t i;
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = params[i];
}
//
// push remained params onto stack
//
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
putdata(pid, (long)regs->ARM_sp, (char*)¶ms[i], (num_params - i) * sizeof(long));
}
regs->ARM_pc = addr;
if (regs->ARM_pc & 1) {
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else {
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}
regs->ARM_lr = 0;
if (ptrace_setregs(pid, regs) == -1
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
int stat = 0;
waitpid(pid, &stat, WUNTRACED);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
return 0;
}
首先是將參數賦值給R0-R3,如果參數大于四個的話,再使用putdata()將參數存放在棧上。然后我們將PC的值設置為函數地址。接著再根據是否是thumb指令設置ARM_cpsr寄存器的值。隨后我們使用ptrace_setregs()將目標進程寄存器的值進行修改。最后使用waitpid()等待函數被執行。
編譯完后,我們使用hook3對target程序進行hook:
#!bash
# ./target
Hello,LiBieGou! 0
Hello,LiBieGou! 1
Hello,LiBieGou! 2
Hello,LiBieGou! 3
[…sleep 10 seconds…]
Hello,LiBieGou! 4
Hello,LiBieGou! 5
# ./hook3 24835
getting remote sleep_addr:
module_base: local[0xb6f35000], remote[0xb6eec000]
remote_addr: [0xb6f1a24b]
正常的情況是target程序每秒輸出一句話,但是用hook3程序hook后,就會暫停10秒鐘的時間,因為我們利用ptrace運行了sleep(10)在目標程序中。
僅僅是執行現有的libc函數是不能滿足我們的需求的,接下來我們繼續介紹如何動態的加載自定義so文件并且運行so文件中的函數。邏輯大概如下:
保存當前寄存器的狀態 -> 獲取目標程序的mmap, dlopen, dlsym, dlclose 地址 -> 調用mmap分配一段內存空間用來保存參數信息 –> 調用dlopen加載so文件 -> 調用dlsym找到目標函數地址 -> 使用ptrace_call執行目標函數 -> 調用 dlclose 卸載so文件 -> 恢復寄存器的狀態。
實現整個邏輯的函數 injectSo()的代碼如下:
#!c++
void injectSo(pid_t pid,char* so_path, char* function_name,char* parameter)
{
struct pt_regs old_regs,regs;
long mmap_addr, dlopen_addr, dlsym_addr, dlclose_addr;
//save old regs
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(®s, &old_regs, sizeof(regs));
// get remote addres
printf("getting remote addres:\n");
mmap_addr = get_remote_addr(pid, libc_path, (void *)mmap);
dlopen_addr = get_remote_addr( pid, libc_path, (void *)dlopen );
dlsym_addr = get_remote_addr( pid, libc_path, (void *)dlsym );
dlclose_addr = get_remote_addr( pid, libc_path, (void *)dlclose );
printf("mmap_addr=%p dlopen_addr=%p dlsym_addr=%p dlclose_addr=%p\n",
(void*)mmap_addr,(void*)dlopen_addr,(void*)dlsym_addr,(void*)dlclose_addr);
long parameters[10];
//mmap
parameters[0] = 0; //address
parameters[1] = 0x4000; //size
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; //WRX
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; //flag
parameters[4] = 0; //fd
parameters[5] = 0; //offset
ptrace_call(pid, mmap_addr, parameters, 6, ®s);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
long map_base = regs.ARM_r0;
printf("map_base = %p\n", (void*)map_base);
//dlopen
printf("save so_path = %s to map_base = %p\n", so_path, (void*)map_base);
putdata(pid, map_base, so_path, strlen(so_path) + 1);
parameters[0] = map_base;
parameters[1] = RTLD_NOW| RTLD_GLOBAL;
ptrace_call(pid, dlopen_addr, parameters, 2, ®s);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
long handle = regs.ARM_r0;
printf("handle = %p\n",(void*) handle);
//dlsym
printf("save function_name = %s to map_base = %p\n", function_name, (void*)map_base);
putdata(pid, map_base, function_name, strlen(function_name) + 1);
parameters[0] = handle;
parameters[1] = map_base;
ptrace_call(pid, dlsym_addr, parameters, 2, ®s);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
long function_ptr = regs.ARM_r0;
printf("function_ptr = %p\n", (void*)function_ptr);
//function_call
printf("save parameter = %s to map_base = %p\n", parameter, (void*)map_base);
putdata(pid, map_base, parameter, strlen(parameter) + 1);
parameters[0] = map_base;
ptrace_call(pid, function_ptr, parameters, 1, ®s);
//dlcose
parameters[0] = handle;
ptrace_call(pid, dlclose_addr, parameters, 1, ®s);
//restore old regs
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}
mmap()可以用來將一個文件或者其它對象映射進內存,如果我們把flag設置為MAP_ANONYMOUS并且把參數fd設置為0的話就相當于直接映射一段內容為空的內存。mmap()的函數聲明和參數如下:
#!bash
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射區的開始地址,設置為0時表示由系統決定映射區的起始地址。
length:映射區的長度。
prot:期望的內存保護標志,不能與文件的打開模式沖突。我們這里設置為RWX。
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。我們這里設置為:MAP_ANONYMOUS(匿名映射,映射區不與任何文件關聯),MAP_PRIVATE(建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件)。
fd:有效的文件描述詞。匿名映射設置為0。
off_toffset:被映射對象內容的起點。設置為0。
在我們使用ptrace_call(pid, mmap_addr, parameters, 6, ®s)
調用完mmap()函數之后,要記得使用ptrace(PTRACE_GETREGS, pid, NULL, ®s);
用來獲取保存返回值的regs.ARM_r0,這個返回值也就是映射的內存的起始地址。
mmap()映射的內存主要用來保存我們傳給其他函數的參數。比如接下來我們需要用dlopen()去加載”/data/local/tmp/libinject.so”這個文件,所以我們需要先用putdata()將”/data/local/tmp/libinject.so”這個字符串放置在mmap()所映射的內存中,然后就可以將這個映射的地址作為參數傳遞給dlopen()了。接下來的dlsym(),so中的目標函數,dlclose()都是相同調用的方式,這里就不一一贅述了。
我們再來看一下被加載的so文件,里面的內容為:
#!c++
int mzhengHook(char * str){
printf("mzheng Hook pid = %d\n", getpid());
printf("Hello %s\n", str);
LOGD("mzheng Hook pid = %d\n", getpid());
LOGD("Hello %s\n", str);
return 0;
}
這里我們不光使用printf()還使用了android debug的函數LOGD()用來輸出調試結果。所以在編譯時我們需要加上LOCAL_LDLIBS := -llog。
編譯完后,我們使用hook4對target程序進行hook:
#!bash
# ./target
Hello,LiBieGou! 0
Hello,LiBieGou! 1
Hello,LiBieGou! 2
mzheng Hook pid = 6043
Hello 7weapons
Hello,LiBieGou! 3
Hello,LiBieGou! 4
Hello,LiBieGou! 5
…
logcat:
D/DEBUG ( 6043): mzheng Hook pid = 6043
D/DEBUG ( 6043): Hello 7weapons
# ./hook4 6043
getting remote addres:
mmap_addr=0xb6f80c81 dlopen_addr=0xb6fd0f4d dlsym_addr=0xb6fd0e9d dlclose_addr=0xb6fd0e19
map_base = 0xb6f2f000
save so_path = /data/local/tmp/libinject.so to map_base = 0xb6f2f000
handle = 0xb6fcd494
save function_name = mzhengHook to map_base = 0xb6f2f000
function_ptr = 0xb6f29cbd
save parameter = 7weapons to map_base = 0xb6f2f000
可以看到無論是stdout還是logcat都成功的輸出了我們的調試信息。這意味著我們可以通過注入讓目標進程加載so文件并執行任意代碼了。
現在我們已經可以做到hook system call以及動態的加載自定義so文件并且運行so文件中的函數了,但離執行以及hook java層的函數還有一定距離。因為篇幅原因,我們的hook之旅就先進行到這里,敬請期待一下篇《離別鉤 - Hooking》(聽說打賞能讓作者寫的快一點…)。
文章中所有提到的代碼和工具都可以在我的github下載到,地址是:
https://github.com/zhengmin1989/TheSevenWeapons
本文獨家首發于烏云知識庫(drops.wooyun.org)。本文并沒有對任何單位和個人授權轉載。如本文被轉載,一定是屬于未經授權轉載,屬于嚴重的侵犯知識產權,本單位將追究法律責任。