關(guān)于這個名字感覺很奇怪感覺是兩個題目,其實(shí)從原理來說是基本上一個原理藤巢,稍加改動就作用于兩個不同的場景掂咒,一個是在Android(Linux)下的ARM匯編注入系統(tǒng)調(diào)用温圆,另一個是在Android下面注入調(diào)用另一個.so庫捌木,我們先開啟上半篇《Android(Linux)下的ARM注入》
好的讓我開始Android下的Hook大家一定都不陌生了刨裆,經(jīng)常見的如下幾種
- 修改虛擬機(jī)NativeFun的指針調(diào)用的方式
- 修改虛擬機(jī)的ArtMethod 函數(shù)結(jié)構(gòu)體整體替換為想要的ArtMethod結(jié)構(gòu)體
- AOP + Reflection 的方式進(jìn)行調(diào)用
- 利用Android下的類加載器加載目標(biāo)的Dex達(dá)到Hook目的
- 利用ARM匯編底層修改替換寄存器來實(shí)現(xiàn)Hook
前兩個在熱修復(fù)HotFix場景中非常常見,例如AndFix框架努潘,這個框架已經(jīng)開源很久了大家可以去下載看看源碼疯坤,我有一篇文章簡單介紹了AndFix的原理,大家有興趣的可以去觀看 《AndFix各個版本的改動以及原理》
第三個 AOP + Reflection 廣泛運(yùn)用于一些插件化框架里面例如 DroidPlugin 框架菌瘫,老實(shí)說這個框架里面包含了很多的細(xì)節(jié)雨让,最難的是在于找到合適的注入點(diǎn)栖忠,但是想找到合適的注入點(diǎn)就必須對于Android各個層面的調(diào)用都非常清楚才行娃闲, 但是話說回來最后實(shí)現(xiàn)的主要手法還是運(yùn)用了AOP + Reflection,網(wǎng)絡(luò)上有一個高手叫 weishu 專門寫了幾篇分析這個框架的文章可以給大家分享一下 http://weishu.me/
第四個用于一些Dex里面的方法替換属拾,常見的例如QQZone團(tuán)隊(duì)很早就在網(wǎng)絡(luò)上開源的MutiDex來進(jìn)行兩個Dex里面的方法替換
今天我們要說的是第五個也就是利用ARM匯編底層修改替換寄存器來實(shí)現(xiàn)Hook。
其實(shí)關(guān)于這個網(wǎng)絡(luò)上已經(jīng)有了一些文章以及資料來講解了尊浓,而且這次這個課題是我以前在公司分享公開課的一次分享的課題栋齿,最近是又拿出了這部分來自己進(jìn)行提煉以及升華來做一次總結(jié)瓦堵。這次我爭取從一個通俗易懂的角度來講解下這兩部分,順便講解一下別文章沒有的東西惋鸥。
在這之前我先把我調(diào)試的Demo傳給大家供大家調(diào)試,這個Demo來源于網(wǎng)絡(luò)上面迎卤,我進(jìn)行一點(diǎn)改進(jìn)以及注釋,另外我已經(jīng)運(yùn)行過了在以前的小米手機(jī)以及模擬器都能夠運(yùn)行成功八堡。
Demo 下載
項(xiàng)目文件夾結(jié)構(gòu)如下:
jni文件夾里面是主要的代碼,libs是編譯出的文件挂谍,detail文件夾是我以前做的簡單的原理總結(jié)口叙。
首先這種Hook的分如下幾個步驟:
1.先找到被注入進(jìn)程的pid
2.附加當(dāng)前進(jìn)程到被注入進(jìn)程
3.保存原寄存器的值
4.找到需要Hook的系統(tǒng)調(diào)用函數(shù)
5.修改目標(biāo)進(jìn)程寄存器
6.執(zhí)行目標(biāo)函數(shù)調(diào)用
7.恢復(fù)寄存器的值
8.分離附加進(jìn)程
我們先可以運(yùn)行看看效果:
hello_sleep.c 是我們想要注入的目標(biāo)進(jìn)程代碼如下
#include<stdio.h>
#include<stdlib.h>
int count = 0;
void print()
{
printf("hello,%d\n",count);
sleep(1);
}
int main(int argc, char const *argv[])
{
while(1){
print();
count++;
}
return 0;
}
很簡單隔一秒打印一句話俺亮,我們要做的目的是注入sleep 系統(tǒng)調(diào)用把里面的參數(shù)給他改了,改成比如10秒一執(zhí)行本讥,inject_sleep.c 是要運(yùn)行Hook效果的進(jìn)程
其完整代碼如下
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <android/log.h>
#if defined(__i386__)
#define pt_regs user_regs_struct
#endif
#define ENABLE_DEBUG 1
#if ENABLE_DEBUG
#define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, fmt, ##args)
#define DEBUG_PRINT(format,args...) \
LOGD(format, ##args)
#else
#define DEBUG_PRINT(format,args...)
#endif
#define CPSR_T_MASK ( 1u << 5 )
const char *libc_path = "/system/lib/libc.so";
const char *linker_path = "/system/bin/linker";
int ptrace_readdata(pid_t pid, uint8_t *src, uint8_t *buf, size_t size)
{
uint32_t i, j, remain;
uint8_t *laddr;
union u {
long val;
char chars[sizeof(long)];
} d;
j = size / 4;
remain = size % 4;
laddr = buf;
for (i = 0; i < j; i ++) {
d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);
memcpy(laddr, d.chars, 4);
src += 4;
laddr += 4;
}
if (remain > 0) {
d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);
memcpy(laddr, d.chars, remain);
}
return 0;
}
int ptrace_writedata(pid_t pid, uint8_t *dest, uint8_t *data, size_t size)
{
uint32_t i, j, remain;
uint8_t *laddr;
union u {
long val;
char chars[sizeof(long)];
} d;
j = size / 4; //分兩部分,一部分4的整數(shù)倍來寫,一部分4的余數(shù)來寫
remain = size % 4;
laddr = data;
for (i = 0; i < j; i ++) { //向目標(biāo)進(jìn)程中寫入4字節(jié)的整數(shù)倍的數(shù)據(jù)
memcpy(d.chars, laddr, 4);
ptrace(PTRACE_POKETEXT, pid, dest, d.val);
dest += 4;
laddr += 4;
}
if (remain > 0) {
d.val = ptrace(PTRACE_PEEKTEXT, pid, dest, 0); //感覺沒什么用
for (i = 0; i < remain; i ++) {
d.chars[i] = *laddr ++;
}
ptrace(PTRACE_POKETEXT, pid, dest, d.val); //向目標(biāo)進(jìn)程中寫入剩余字節(jié)的的數(shù)據(jù)
}
return 0;
}
#if defined(__arm__)
int ptrace_call(pid_t pid, uint32_t 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];
}
// 前4個參數(shù)通過r0-r3 寄存器傳遞
// push remained params onto stack
// 后面的參數(shù)通過sp 寄存器傳遞
if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ;
ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)¶ms[i], (num_params - i) * sizeof(long));
}
regs->ARM_pc = addr; //pc寄存器賦值到函數(shù)基地址
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; ///設(shè)置附加進(jìn)程的LR寄存器的值為0晓褪,觸發(fā)地址0異城诼回到當(dāng)前進(jìn)程中
if (ptrace_setregs(pid, regs) == -1 //設(shè)計(jì)寄存器并且調(diào)用函數(shù)
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
int stat = 0;
waitpid(pid, &stat, WUNTRACED); //等待附加進(jìn)程的調(diào)用結(jié)束
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}
return 0;
}
#elif defined(__i386__)
long ptrace_call(pid_t pid, uint32_t addr, long *params, uint32_t num_params, struct user_regs_struct * regs)
{
regs->esp -= (num_params) * sizeof(long) ;
ptrace_writedata(pid, (void *)regs->esp, (uint8_t *)params, (num_params) * sizeof(long));
long tmp_addr = 0x00;
regs->esp -= sizeof(long);
ptrace_writedata(pid, regs->esp, (char *)&tmp_addr, sizeof(tmp_addr));
regs->eip = addr;
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;
}
#else
#error "Not supported"
#endif
int ptrace_getregs(pid_t pid, struct pt_regs * regs)
{
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {
perror("ptrace_getregs: Can not get register values");
return -1;
}
return 0;
}
int ptrace_setregs(pid_t pid, struct pt_regs * regs)
{
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("ptrace_setregs: Can not set register values");
return -1;
}
return 0;
}
int ptrace_continue(pid_t pid)
{
if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {
perror("ptrace_cont");
return -1;
}
return 0;
}
int ptrace_attach(pid_t pid)
{
if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {
perror("ptrace_attach");
return -1;
}
int status = 0;
waitpid(pid, &status , WUNTRACED);
return 0;
}
int ptrace_detach(pid_t pid)
{
if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {
perror("ptrace_detach");
return -1;
}
return 0;
}
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) {
/* self process */
snprintf(filename, sizeof(filename), "/proc/self/maps", pid);
} 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;
}
void* get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr)
{
void* local_handle, *remote_handle;
local_handle = get_module_base(-1, module_name); //獲取本進(jìn)程指定函數(shù)的模塊的基地址
remote_handle = get_module_base(target_pid, module_name); //獲取被附加進(jìn)程指定函數(shù)的模塊的基地址
DEBUG_PRINT("[+] get_remote_addr: local[%x], remote[%x]\n", local_handle, remote_handle);
//根據(jù)便偏移量計(jì)算出被附加進(jìn)程指定函數(shù)的基地址
void * ret_addr = (void *)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);
#if defined(__i386__)
if (!strcmp(module_name, libc_path)) {
ret_addr += 2;
}
#endif
return ret_addr;
}
int find_pid_of(const char *process_name)
{
int id;
pid_t pid = -1;
DIR* dir;
FILE *fp;
char filename[32];
char cmdline[256];
struct dirent * entry;
if (process_name == NULL)
return -1;
dir = opendir("/proc");
if (dir == NULL)
return -1;
while((entry = readdir(dir)) != NULL) {
id = atoi(entry->d_name);
if (id != 0) {
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp) {
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strcmp(process_name, cmdline) == 0) {
/* process found */
pid = id;
break;
}
}
}
}
closedir(dir);
return pid;
}
long ptrace_retval(struct pt_regs * regs)
{
#if defined(__arm__)
return regs->ARM_r0;
#elif defined(__i386__)
return regs->eax;
#else
#error "Not supported"
#endif
}
long ptrace_ip(struct pt_regs * regs)
{
#if defined(__arm__)
return regs->ARM_pc;
#elif defined(__i386__)
return regs->eip;
#else
#error "Not supported"
#endif
}
int ptrace_call_wrapper(pid_t target_pid, const char * func_name, void * func_addr, long * parameters, int param_num, struct pt_regs * regs)
{
DEBUG_PRINT("[+] Calling %s in target process.\n", func_name);
if (ptrace_call(target_pid, (uint32_t)func_addr, parameters, param_num, regs) == -1)
return -1;
if (ptrace_getregs(target_pid, regs) == -1)
return -1;
DEBUG_PRINT("[+] Target process returned from %s, return value=%x, pc=%x \n",
func_name, ptrace_retval(regs), ptrace_ip(regs));
return 0;
}
int inject_remote_process(pid_t target_pid, const char *library_path, const char *function_name, const char *param, size_t param_size)
{
int ret = -1;
void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
void *local_handle, *remote_handle, *dlhandle;
uint8_t *map_base = 0;
uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr;
struct pt_regs regs, original_regs;
extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \
_dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \
_saved_cpsr_s, _saved_r0_pc_s;
uint32_t code_length;
long parameters[10];
DEBUG_PRINT("[+] Injecting process: %d\n", target_pid);
if (ptrace_attach(target_pid) == -1) //開始進(jìn)程的附加
goto exit;
if (ptrace_getregs(target_pid, ®s) == -1) //讀取被附加進(jìn)程的寄存器
goto exit;
/* save original registers */
memcpy(&original_regs, ®s, sizeof(regs)); //保存附加進(jìn)程所有的寄存器r0-r15,cpsr
mmap_addr = get_remote_addr(target_pid, libc_path, (void *)mmap); //獲得被附加進(jìn)程mmap函數(shù)的基地址
DEBUG_PRINT("[+] Remote mmap address: %x\n", mmap_addr);
/* call mmap */ //構(gòu)造mmap函數(shù)的參數(shù)
parameters[0] = 0; // addr
parameters[1] = 0x4000; // size
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // flags
parameters[4] = 0; //fd
parameters[5] = 0; //offset
if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, ®s) == -1) //開始調(diào)用被附加進(jìn)程的mmap函數(shù)
goto exit;
map_base = ptrace_retval(®s); //獲取函數(shù)調(diào)用的返回值
dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen ); //獲得附加進(jìn)程dlopen函數(shù)的基地址
dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );
dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose );
dlerror_addr = get_remote_addr( target_pid, linker_path, (void *)dlerror );
DEBUG_PRINT("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n",
dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);
printf("library path = %s\n", library_path);
ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1); //被附加進(jìn)程里面寫入.so 庫的內(nèi)容
parameters[0] = map_base; //設(shè)置參數(shù) 被附加進(jìn)程的mmap函數(shù)的基地址
parameters[1] = RTLD_NOW| RTLD_GLOBAL; //常量值
if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, ®s) == -1) //調(diào)用被附加進(jìn)程里面指定的函數(shù)
goto exit;
void * sohandle = ptrace_retval(®s);
#define FUNCTION_NAME_ADDR_OFFSET 0x100
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
parameters[0] = sohandle;
parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET;
if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)
goto exit;
void * hook_entry_addr = ptrace_retval(®s); //獲取被附加進(jìn)程被調(diào)用函數(shù)的地址
DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);
#define FUNCTION_PARAM_ADDR_OFFSET 0x200
ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;
if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, ®s) == -1) //調(diào)用被附加進(jìn)程被調(diào)用函數(shù)
goto exit;
printf("Press enter to dlclose and detach\n");
getchar();
parameters[0] = sohandle;
if (ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, 1, ®s) == -1) //關(guān)閉調(diào)用
goto exit;
/* restore */
ptrace_setregs(target_pid, &original_regs); //恢復(fù)寄存器
ptrace_detach(target_pid); //分離附加
ret = 0;
exit:
return ret;
}
void inject(int pid)
{
struct pt_regs old_regs,regs;
long sleep_addr;
//保存寄存器
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
memcpy(®s, &old_regs, sizeof(regs));
long parameters[1];
parameters[0] = 10;
sleep_addr = get_remote_addr(pid, "libc.so", (void*)sleep);
ptrace_call(pid,sleep_addr,parameters,1,®s);
//恢復(fù)寄存器
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}
int main(int argc, char** argv) {
pid_t target_pid;
target_pid = find_pid_of("./hello_sleep"); //查找所需要注入的進(jìn)程的pid
if (-1 == target_pid) {
printf("Can't find the process\n");
return -1;
}
if(0 != ptrace(PTRACE_ATTACH, target_pid, NULL, NULL)){
printf("attach failed.");
return 1;
}
inject(target_pid);
ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
return 0;
}
目標(biāo)函數(shù)是 libc.so 中的 sleep 函數(shù).正常情況是每輸出一次暫停一秒,現(xiàn)在我們讓它暫停10秒.
第一步:拿到被注入的進(jìn)行ID號 find_pid_of("./hello_sleep");
int find_pid_of(const char *process_name)
{
int id;
pid_t pid = -1;
DIR* dir;
FILE *fp;
char filename[32];
char cmdline[256];
struct dirent * entry;
if (process_name == NULL)
return -1;
dir = opendir("/proc");
if (dir == NULL)
return -1;
while((entry = readdir(dir)) != NULL) {
id = atoi(entry->d_name);
if (id != 0) {
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp) {
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strcmp(process_name, cmdline) == 0) {
/* process found */
pid = id;
break;
}
}
}
}
closedir(dir);
return pid;
}
原理很簡單去找到/proc 虛擬文件系統(tǒng)然后對比名字拿到進(jìn)程號ID
然后調(diào)用 Ptrace 函數(shù)附加當(dāng)前進(jìn)程到被注入進(jìn)程塘秦,這里介紹下Ptrace函數(shù):
ptrace()系統(tǒng)調(diào)用函數(shù)提供了一個進(jìn)程(the “tracer”)監(jiān)察和控制另一個進(jìn)程(the “tracee”)的方法爪幻。并且可以檢查和改變“tracee”進(jìn)程的內(nèi)存和寄存器里的數(shù)據(jù)挨稿。它可以用來實(shí)現(xiàn)斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用跟蹤奶甘。
當(dāng)進(jìn)程被跟蹤后甩十,每當(dāng)信號量傳來侣监,甚至信號量會被忽略時(shí)橄霉,tracee會暫停姓蜂。tracer會在下次調(diào)用waitpid(或者其它wait系統(tǒng)調(diào)用)處被通知逮京。該調(diào)用會返回一個包含tracee暫停原因信息的狀態(tài)碼懒棉。當(dāng)tracee暫停后策严,tracer可以使用一系列ptrace請求來查看和修改tracee中的信息妻导。tracer接著可以讓tracee繼續(xù)執(zhí)行倔韭。
這里面我一直有個疑問就是為什么這個Demo中的 ptrace 居然沒有用 wait 來等待目標(biāo)進(jìn)程暫停后切換執(zhí)行狐肢,但是這樣也能正常運(yùn)行,我查了下資料說是
PTRACE_ATTACH:
根據(jù)pid將調(diào)試進(jìn)程附加到被調(diào)試進(jìn)程上,PTRACE_ATTACH向被調(diào)試進(jìn)程發(fā)送SIGSTOP信號使之停下.
但是在ptrace(PTRACE_ATTACH,pid,0,0)執(zhí)行完畢時(shí)被調(diào)試進(jìn)程可能還沒有暫停,可以使用waitpid()等待其停下.
意思是說有被附加進(jìn)程可能停下執(zhí)行附加進(jìn)程妓美,也可能被附加進(jìn)程停不下來使用wait使其停下來壶栋,保險(xiǎn)起見還是使用wait使其停下來為好 (如果有高見的可以給我留言)
if(0 != ptrace(PTRACE_ATTACH, target_pid, NULL, NULL)){
printf("attach failed.");
return 1;
}
附加成功以后琉兜,目標(biāo)進(jìn)程會暫停豌蟋,Hook進(jìn)程會得到執(zhí)行的機(jī)會,這個時(shí)候調(diào)用 inject 函數(shù)运准,函數(shù)首先通過PTRACE_GETREGS拿到源寄存器该互,然后保存宇智,設(shè)置好等下要注入的sleep系統(tǒng)調(diào)用所需要的參數(shù)parameters[0] = 10 也就是睡10秒普筹,然后通過get_remote_addr函數(shù)拿到了目標(biāo)進(jìn)程的sleep系統(tǒng)調(diào)用在目標(biāo)進(jìn)程的地址:
首先拿到本進(jìn)程與目標(biāo)進(jìn)程的基地址也就是代碼段的地址,也就是去查詢虛擬文件系統(tǒng)/proc/%d/maps 查詢里面代碼段的libc庫地址作為基地址蜒车,然后計(jì)算出偏移量,偏移量的計(jì)算很簡單拿本進(jìn)程的sleep地址 - 本進(jìn)程libc基地址 + 目標(biāo)進(jìn)程的libc基地址
待會會涉及到ARM寄存器的操作幔嗦,讓我們先復(fù)習(xí)下 ATPCS 即ARM-THUMB procedure call standard(ARM-Thumb過程調(diào)用標(biāo)準(zhǔn))的簡稱:
寄存器的使用規(guī)則:
- 子程序通過寄存器R0~R3來傳遞參數(shù). 這時(shí)寄存器可以記作: A0~A3 , 被調(diào)用的子程序在返回前無需恢復(fù)寄存器R0~R3的內(nèi)容.
- 在子程序中,使用R4-R11來保存局部變量,這時(shí)寄存器R4-R11可以記作: V1-V8 .如果在子程序中使用到V1-V8的某些寄存器,子程序進(jìn)入時(shí)必須保存這些寄存器的值,在返回前必須恢復(fù)這些寄存器的值,對于子程序中沒有用到的寄存器則不必執(zhí)行這些操作.在THUMB程序中酿愧,通常只能使用寄存器R4-R7來保存局部變量.
3.寄存器R12用作子程序間scratch寄存器,記作ip; 在子程序的連接代碼段中經(jīng)常會有這種使用規(guī)則.- 寄存器R13用作數(shù)據(jù)棧指針,記做SP,在子程序中寄存器R13不能用做其他用途. 寄存器SP在進(jìn)入子程序時(shí)的值和退出子程序時(shí)的值必須相等.
- 寄存器R14用作連接寄存器,記作lr ; 它用于保存子程序的返回地址,如果在子程序中保存了返回地址,則R14可用作其它的用途.
- 寄存器R15是程序計(jì)數(shù)器,記作PC ; 它不能用作其他用途.
- ATPCS中的各寄存器在ARM編譯器和匯編器中都是預(yù)定義的.
上面說的是大致的總規(guī)則,下面具體介紹下 參數(shù)的傳遞規(guī)則 與 子程序結(jié)果返回規(guī)則
參數(shù)的傳遞規(guī)則 :對于參數(shù)個數(shù)可變的子程序,當(dāng)參數(shù)不超過4個時(shí),可以使用寄存器R0~R3來進(jìn)行參數(shù)傳遞,當(dāng)參數(shù)超過4個時(shí),還可以使用數(shù)據(jù)棧來傳遞參數(shù). 在參數(shù)傳遞時(shí),將所有參數(shù)看做是存放在連續(xù)的內(nèi)存單元中的字?jǐn)?shù)據(jù)邀泉。然后,依次將各名字?jǐn)?shù)據(jù)傳送到寄存器R0,R1,R2,R3; 如果參數(shù)多于4個,將剩余的字?jǐn)?shù)據(jù)傳送到數(shù)據(jù)棧中(即sp寄存器),入棧的順序與參數(shù)順序相反,即最后一個字?jǐn)?shù)據(jù)先入棧. 按照上面的規(guī)則,一個浮點(diǎn)數(shù)參數(shù)可以通過寄存器傳遞,也可以通過數(shù)據(jù)棧傳遞,也可能一半通過寄存器傳遞嬉挡,另一半通過數(shù)據(jù)棧傳遞.
子程序結(jié)果返回規(guī)則:
1.結(jié)果為一個32位的整數(shù)時(shí),可以通過寄存器R0返回.
2.結(jié)果為一個64位整數(shù)時(shí),可以通過R0和R1返回,依此類推.
3.結(jié)果為一個浮點(diǎn)數(shù)時(shí),可以通過浮點(diǎn)運(yùn)算部件的寄存器f0,d0或者s0來返回.
4.結(jié)果為一個復(fù)合的浮點(diǎn)數(shù)時(shí),可以通過寄存器f0-fN或者d0~dN來返回.
5.對于位數(shù)更多的結(jié)果,需要通過調(diào)用內(nèi)存來傳遞.
??我們下面會用到R0-R3 , SP , LR , PC寄存器汇恤,大家可以參考上面的說明來查看使用
然后進(jìn)行 ptrace_call 調(diào)用庞钢,這個是這里面最重要的函數(shù)因谎,我們只看ARM架構(gòu)的河爹,一開始參數(shù)賦值給寄存器炊苫,前4個參數(shù)通過r0-r3 寄存器傳遞(直接賦值就可以)唠梨,后面的后面的參數(shù)通過sp 寄存器傳遞(熟悉ARM編程這個肯定不陌生蚁鳖,說直白的就是利用椉タ悖空間存儲吴旋,然后傳遞這個棧的地址)褂傀,然后把pc寄存器賦值為目標(biāo)進(jìn)程的sleep地址
regs->ARM_pc = addr;
然后寄存器賦值給目標(biāo)進(jìn)程叠国,并且進(jìn)行函數(shù)調(diào)用(運(yùn)行pc寄存器地址的函數(shù))
if (ptrace_setregs(pid, regs) == -1 //設(shè)計(jì)寄存器并且調(diào)用函數(shù)
|| ptrace_continue(pid) == -1) {
printf("error\n");
return -1;
}
運(yùn)行完成以后由于由于我們在前面設(shè)置了regs->ARM_lr = 0悲雳,它就會返回到0地址處繼續(xù)執(zhí)行,這樣就會產(chǎn)生SIGSEGV了,程序中的 0xb7f 就表示子進(jìn)程進(jìn)入了暫停狀態(tài)凑队,且發(fā)送的錯誤信號為11(SIGSEGV)茸时,它表示試圖訪問未分配給自己的內(nèi)存, 或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù)渠牲,這樣就做到了目標(biāo)進(jìn)制執(zhí)行完暫停切換到注入進(jìn)程執(zhí)行
WUNTRACED:
告訴 waitpid 铣除,如果子進(jìn)程進(jìn)入暫停狀態(tài)或者已經(jīng)終止郎嫁,那么就立即返回 status 信息,正常情況是子進(jìn)程終止的時(shí)候才返回.
如果是被ptrace的子進(jìn)程,那么即使不提供WUNTRACED參數(shù),也會在子進(jìn)程進(jìn)入暫停狀態(tài)的時(shí)候立即返回礁击。
對于使用 ptrace_cont 運(yùn)行的子進(jìn)程强衡,它會在3種情況下進(jìn)入暫停狀態(tài):①下一次系統(tǒng)調(diào)用;②子進(jìn)程退出亿傅;③子進(jìn)程的執(zhí)行發(fā)生錯誤。
regs->ARM_lr = 0;
其中這個 stat != 0xb7f 這句話我理解很久都理解不清楚什么意思直到我找到了下面這張圖嘴脾,0xb7f表示stopped信號為11韵洋,進(jìn)程狀態(tài)為stopped齿桃,為此我還專門記錄了下這個問題有興趣大家可以去捧場:
最后寄存器恢復(fù)
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
這樣看來還不算復(fù)雜的梗脾,無非就是操作寄存器修改里面的值而已蔓倍,搞過匯編或者做過逆向的對于這些應(yīng)該很熟悉垦巴,再者就是要掌握Linux Ptrace函數(shù)的使用這個是個要點(diǎn),這個函數(shù)功能非常強(qiáng)大癣缅,GDB斷點(diǎn)調(diào)試都基于這個原理來實(shí)現(xiàn)的膨俐,大家一定要掌握匾委。
我們上篇算是打好了一個基礎(chǔ)咖气,下篇可能會復(fù)雜一點(diǎn)觉既,但是是基于這個基礎(chǔ)上實(shí)現(xiàn)的粹断,另外這種方式的Hook的優(yōu)缺點(diǎn)我們也會在下篇展開講解···