天天看點

AllHookInOne 中的hook基本流程

一、導入表(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;
}