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