一、導入表(GOT 表 HOOK)
熟悉 ELF 結構的讀者都知道,SO引用外部函數的時候,在編譯時會将外部函數的位址以 Stub 的形式存放在.GOT 表中,加載時 linker 再進行重定位,即将真實的外部函數寫到此 stub 中。
HOOK 的思路就是:替換GOT表中的外部函數位址。可以了解為hook導入函數。
具體流程:
1.注入程序
2.可能有讀者想到馬上就是讀取并解析 SO 的結構,找到外部函數對應在GOT 表中的存放位址。在 http://bbs.pediy.com/showthread.php?t=194053中已經讨論 dlopen 傳回的是 solist,已經包含 SO 資訊。(直接通過 SOLIST 實作替換 HOOK,代碼量就很小了)
導入表 HOOK 的實作是最簡單的了,但也不難看出,導入表的 HOOK 功能是很有限的。
例舉兩點:
1. 導入表 HOOK 對程序通過 dlopen 動态獲得并調用外部符号是無效的。
2. 導入表 HOOK 隻能影響被注入程序。
二、AllHookInOne基本流程
主要原理:通過解析映射到記憶體中的elf的結構,解析出got,然後進行hook重定位替換。其中必須要基于執行視圖(Execution View)進行符号解析;
ELF檔案格式是基于連結視圖(Linking View),連結視圖是基于節(Section)對ELF進行解析的。然而動态連結庫在加載的過程中,linker隻關注ELF中的段(Segment)資訊。是以ELF中的節資訊被完全篡改或者甚至删除掉,并不會影響linker的加載過程,這樣做可以防止靜态分析工具(比如IDA,readelf等)對其進行分析,一般加過殼的ELF檔案都會有這方面的處理。
對于這種ELF檔案,如果要實作hook功能,則必須要基于執行視圖(Execution View)進行符号解析;
AllHookInOne主要流程方法有:
1、從給定的so中擷取基址,擷取so句柄ElfHandle
ElfHandle* handle = openElfBySoname(soname);
2、從segment視圖擷取elf資訊(即加載到記憶體的so)
getElfInfoBySegmentView(info, handle);
3、根據符号名尋找函數位址Sym
findSymByName(info, symbol, &sym, &symidx);
4、周遊連結清單,進行一次替換relplt表函數位址操作,其中需要使用mprotect修改通路記憶體,然後調用系統指令 清除緩存
replaceFunc(addr, replace_func, old_func)
5、周遊連結清單,進行一次替換reldyn表函數位址操作,其中需要使用mprotect修改通路記憶體,然後調用系統指令 清除緩存
replaceFunc(addr, replace_func, old_func))
6、釋放資源,關閉elf句柄 closeElfBySoname(handle);
參考:https://github.com/boyliang/AllHookInOne
/*
* elfhook.cpp
*
* Created on: 2014年10月9日
* Author: boyliang
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include "common.h"
#include "elfutils.h"
#include "elfio.h"
#define PAGE_START(addr) (~(getpagesize() - 1) & (addr))
//使用mprotect修改通路記憶體
static int modifyMemAccess(void *addr, int prots){
void *page_start_addr = (void *)PAGE_START((uint32_t)addr);
return mprotect(page_start_addr, getpagesize(), prots);
}
//調用系統指令 清除緩存
static int clearCache(void *addr, size_t len){
void *end = (uint8_t *)addr + len;
syscall(0xf0002, addr, end);
}
//替換函數位址。
static int replaceFunc(void *addr, void *replace_func, void **old_func){
int res = 0;
if(*(void **)addr == replace_func){
LOGW("addr %p had been replace.", addr);
goto fails;
}
if(!*old_func){
*old_func = *(void **)addr;
}
if(modifyMemAccess((void *)addr, PROT_EXEC|PROT_READ|PROT_WRITE)){
LOGE("[-] modifymemAccess fails, error %s.", strerror(errno));
res = 1;
goto fails;
}
*(void **)addr = replace_func;
clearCache(addr, getpagesize());
LOGI("[+] old_func is %p, replace_func is %p, new_func %p.", *old_func, replace_func, *(uint32_t *)addr);
fails:
return res;
}
#define R_ARM_ABS32 0x02
#define R_ARM_GLOB_DAT 0x15
#define R_ARM_JUMP_SLOT 0x16
//http://blog.csdn.net/L173864930/article/details/40507359
int elfHook(const char *soname, const char *symbol, void *replace_func, void **old_func){
assert(old_func);
assert(replace_func);
assert(symbol);
//從給定的so中擷取基址,擷取so句柄ElfHandle
ElfHandle* handle = openElfBySoname(soname);
ElfInfo info;
//從segment視圖擷取elf資訊(即加載到記憶體的so)
getElfInfoBySegmentView(info, handle);
Elf32_Sym *sym = NULL;
int symidx = 0;
//根據符号名尋找函數位址Sym
findSymByName(info, symbol, &sym, &symidx);
if(!sym){
LOGE("[-] Could not find symbol %s", symbol);
goto fails;
}else{
LOGI("[+] sym %p, symidx %d.", sym, symidx);
}
for (int i = 0; i < info.relpltsz; i++) {
Elf32_Rel& rel = info.relplt[i];
if (ELF32_R_SYM(rel.r_info) == symidx && ELF32_R_TYPE(rel.r_info) == R_ARM_JUMP_SLOT) {
void *addr = (void *) (info.elf_base + rel.r_offset);
//進行一次替換relplt表函數位址操作,其中需要使用mprotect修改通路記憶體,然後調用系統指令 清除緩存
if (replaceFunc(addr, replace_func, old_func))
goto fails;
//only once
break;
}
}
for (int i = 0; i < info.reldynsz; i++) {
Elf32_Rel& rel = info.reldyn[i];
if (ELF32_R_SYM(rel.r_info) == symidx &&
(ELF32_R_TYPE(rel.r_info) == R_ARM_ABS32
|| ELF32_R_TYPE(rel.r_info) == R_ARM_GLOB_DAT)) {
void *addr = (void *) (info.elf_base + rel.r_offset);
//進行一次替換reldyn表函數位址操作,其中需要使用mprotect修改通路記憶體,然後調用系統指令 清除緩存
if (replaceFunc(addr, replace_func, old_func))
goto fails;
}
}
fails:
closeElfBySoname(handle);//釋放資源,關閉elf句柄
return 0;
}