概述
對于android的so檔案的hook根據ELF檔案特性分為:Got表hook、Sym表hook和inline hook等,
全域符號表(GOT表)hook,它是通過決議SO檔案,將待hook函式在got表的地址替換為自己函式的入口地址,這樣目標行程每次呼叫待hook函式時,實際上是執行了我們自己的函式,
Androd so注入和函式Hook(基于got表)的步驟:
1.ptrace附加目標pid行程;
2.在目標pid行程中,查找記憶體空間(用于存放被注入的so檔案的路徑和so中被呼叫的函式的名稱或者shellcode);
3.呼叫目標pid行程中的dlopen、dlsym等函式,用于加載so檔案實作Android so的注入和函式的Hook;
4.釋放附加的目標pid行程和卸載注入的so檔案,
具體代碼實作
以下以fopen函式進行got hook為例,
//獲取模塊地址功能實作
void* getModuleBase(pid_t pid, const char* module_name){
FILE* fp;
long address = 0;
char* pch;
char filename[32];
char line[1024];
// 格式化字串得到 "/proc/pid/maps"
if(pid < 0){
snprintf(filename, sizeof(filename), "/proc/self/maps");
}else{
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
}
// 打開檔案/proc/pid/maps,獲取指定pid行程加載的記憶體模塊資訊
fp = fopen(filename, "r");
if(fp != NULL){
// 每次一行,讀取檔案 /proc/pid/maps中內容
while(fgets(line, sizeof(line), fp)){
// 查找指定的so模塊
if(strstr(line, module_name)){
// 分割字串
pch = strtok(line, "-");
// 字串轉長整形
address = strtoul(pch, NULL, 16);
}
break;
}
}
}
fclose(fp);
return (void*)address;
}
//hook fopen進行實作
//(libxxxx.so檔案是ELF32檔案)
#define LIBPATH "/data/app-lib/com.xxxx/libxxxx.so"
int hookFopen(){
// 獲取目標pid中"/data/app-lib/com.xxxx/libxxxx.so"模塊的加載地址
void* base_addr = getModuleBase(getpid(), LIBPATH );
// 保存Hook目標函式的原始呼叫地址
old_fopen = fopen;
int fd;
// 用open打開記憶體模塊檔案"/data/app-lib/com.xxxx/libxxxx.so"
fd = open(LIB_PATH, O_RDONLY);
if(-1 == fd){
return -1;
}
// elf32檔案的檔案頭結構體Elf32_Ehdr
Elf32_Ehdr ehdr;
// 讀取elf32格式的檔案"/data/app-lib/com.xxxx/libxxxx.so"的檔案頭資訊
read(fd, &ehdr, sizeof(Elf32_Ehdr));
// elf32檔案中節區表資訊結構的檔案偏移
unsigned long shdr_addr = ehdr.e_shoff;
// elf32檔案中節區表資訊結構的數量
int shnum = ehdr.e_shnum;
// elf32檔案中每個節區表資訊結構中的單個資訊結構的大小(描述每個節區的資訊的結構體的大小)
int shent_size = ehdr.e_shentsize;
// elf32檔案節區表中每個節區的名稱存放的節區名稱字串表,在節區表中的序號index
unsigned long stridx = ehdr.e_shstrndx;
Elf32_Shdr shdr;
lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);
// 讀取elf32檔案中的描述每個節區的資訊的結構體(這里是保存elf32檔案的每個節區的名稱字串的)
read(fd, &shdr, shent_size);
// 為保存elf32檔案的所有的節區的名稱字串申請記憶體空間
char * string_table = (char *)malloc(shdr.sh_size);
// 定位到具體存放elf32檔案的所有的節區的名稱字串的檔案偏移處
lseek(fd, shdr.sh_offset, SEEK_SET);
read(fd, string_table, shdr.sh_size);
lseek(fd, shdr_addr, SEEK_SET);
int i;
uint32_t out_addr = 0;
uint32_t out_size = 0;
uint32_t got_item = 0;
int32_t got_found = 0;
// 回圈遍歷elf32檔案的節區表(描述每個節區的資訊的結構體)
for(i = 0; i<shnum; i++){
// 依次讀取節區表中每個描述節區的資訊的結構體
read(fd, &shdr, shent_size);
// 判斷當前節區描述結構體描述的節區是否是SHT_PROGBITS型別
//型別為SHT_PROGBITS的.got節區包含全域偏移表
if(shdr.sh_type == SHT_PROGBITS){
// 獲取節區的名稱字串在保存所有節區的名稱字串段.shstrtab中的序號
int name_idx = shdr.sh_name;
// 判斷節區的名稱是否為".got.plt"或者".got"
if(strcmp(&(string_table[name_idx]), ".got.plt") == 0
|| strcmp(&(string_table[name_idx]), ".got") == 0){
// 獲取節區".got"或者".got.plt"在記憶體中實際資料存放地址
out_addr = base_addr + shdr.sh_addr;
// 獲取節區".got"或者".got.plt"的大小
out_size = shdr.sh_size;
int j = 0;
// 遍歷節區".got"或者".got.plt"獲取保存的全域的函式呼叫地址
for(j = 0; j<out_size; j += 4){
// 獲取節區".got"或者".got.plt"中的單個函式的呼叫地址
got_item = *(uint32_t*)(out_addr + j);
// 判斷節區".got"或者".got.plt"中函式呼叫地址是否是將要被Hook的目標函式地址
if(got_item == old_fopen){
got_found = 1;
// 獲取當前記憶體分頁的大小
uint32_t page_size = getpagesize();
// 獲取記憶體分頁的起始地址(需要記憶體對齊)
uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1));
// 修改記憶體屬性為可讀可寫可執行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){
return -1;
}
// Hook的函式,是我們自己定義的函式
got_item = new_fopen;
// 進行恢復記憶體屬性為可讀可執行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){
return -1;
}
break;
// 目標函式的呼叫地址已經被Hook了
}else if(got_item == new_fopen){
break;
}
}
// 對目標函式HOOk成功,跳出回圈
if(got_found)
break;
}
}
}
free(string_table);
close(fd);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/293648.html
標籤:其他
