网上对于Android中各种HOOK的实现都有很多的介绍了,之前看懂了原理相关的东西,一直没有去尝试手动实现,最近刚好想起就手动实现了一下,简单尝试记录下实现过程以及坑点。
如何理解HOOK?
我理解的HOOK就是去动态的修改代码段中相关跳转地址或者指令,执行我们的代码,然后跳转回去接着执行。
那既然这样,肯定要保证动态修改的操作先于该函数被执行,因此一般会将HOOK代码放在比较靠前被执行的地方,如init段、JNI_OnLoad等等。
实际操作:
实现了一个这样的函数,给他传递一个参数值,然后他返回这个参数值:
JNIEXPORT jint JNICALL ReturnGiveValue(int val)
{
return val;
}
此时,我想用另外的函数替换这个函数的执行,比如这个函数:
JNIEXPORT jint JNICALL ReturnFakeValue(int val)
{
return val * 100;
}
将这两个函数封装一个动态库,用以下代码构造的可执行文件去调用:
#include <jni.h>
#include <dlfcn.h>
#include <stdio.h>
typedef int (*RetValFunc)(int);
int main()
{
int a = 10;
void* testlib = dlopen("/data/local/tmp/libtest.so",RTLD_LAZY);
if(testlib != NULL)
{
RetValFunc retval = (RetValFunc)dlsym(testlib,"ReturnGiveValue");
int nBase = (int*)(testlib+0x8C);
printf("ret val : %x\n",nBase);
printf("ret val : %d\n",retval(a));
dlclose(testlib);
}
return 0;
}
测试结果:
可以看到我输入的是10,输出的也是10。
接下来开始构造GOT表HOOK,我们需要阅读相关源码,加以理解文件结构方便找寻相应地址进行替换。
1、获取动态库基值
文件我直接放在固定目录下,然后硬编码的方式再次打开文件获取到文件的基值(参考soinfo结构体)
void* testlib = dlopen("/data/local/tmp/libtest.so",RTLD_LAZY);
int nBase = *(int*)(testlib+0x8C);
LOGD("nBase : %x",nBase);
2、计算program header table实际地址
通过ELF文件头获取到程序表头的偏移地址及表头的个数
ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
return 0;
}
int phOffset = header->e_phoff;
int phNumber = header->e_phnum;
int phPhyAddr = phOffset + nBase;
3、遍历program header table,ptype等于2即为dynameic,获取到p_offset
这里需要参照程序表头结构体的相关信息,程序表头结构体结构如下:
struct Elf32_Phdr {
Elf32_Word p_type; // Type of segment
Elf32_Off p_offset; // File offset where segment is located, in bytes
Elf32_Addr p_vaddr; // Virtual address of beginning of segment
Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific)
Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero)
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment constraint
};
因此得到dynam段对应的地址
for (int i = 0; i < phNumber; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + nBase;
dynamicSize = phdr_table[i].p_memsz;
break;
}
}
4、开始遍历dynamic段结构,d_tag为6即为GOT表地址
同样需要参考动态链接段每项的结构体:
typedef struct dynamic {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
因此知道遍历的方式:
for(i=0;i < dynamicSize / 8;i ++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == 6)
{
systemTableAddr = val + nBase;
break;
}
}
5、遍历GOT表,查找GOT表中标记的ReturnGiveValue函数地址,替换为ReturnFakeValue的地址
最后一步我们要知道表项的结构:
typedef struct elf32_sym {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
然后进行替换目标函数的st_value,
while(1)
{
LOGD("func Addr : %x",symTab[i].st_value);
if(symTab[i].st_value == giveFunc)
{
symTab[i].st_value = fakeFunc;
LOGD("New Give func Addr : %x",symTab[i].st_value);
break;
}
i ++;
}
HOOK之后的效果:
成功调用了我们hook的函数。
注意点:
1、我们知道代码段一般都只会设置为可读可执行的,因此需要使用mprotect改变内存页为可读可写可执行;
2、如果执行完这些操作后hook并没有生效,可能是由于缓存的原因,需要使用cacheflush函数对该内存进行操作。
附上SO完整代码:
#include <jni.h>
#include <dlfcn.h>
#include <elf.h>
#include <android/log.h>
#include <sys/mman.h>
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "ror_got_hook", __VA_ARGS__))
#define MEM_PAGE_SIZE 4096
#define MEM_PAGE_MASK (MEM_PAGE_SIZE-1)
#define MEM_PAGE_START(x) ((x) & ~MEM_PAGE_MASK)
#define MEM_PAGE_END(x) MEM_PAGE_START((x) + (MEM_PAGE_SIZE-1))
#define ElfW(type) Elf32_ ## type
JNIEXPORT jint JNICALL ReturnGiveValue(int val)
{
return val;
}
JNIEXPORT jint JNICALL ReturnFakeValue(int val)
{
return val * 100;
}
int __attribute__((constructor)) gothook()
{
//获取动态库基值
void* testlib = dlopen("/data/data/com.test.test/libtest.so",RTLD_LAZY);
int nBase = *(int*)(testlib+0x8C);
LOGD("nBase : %x",nBase);
//计算program header table实际地址
ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
return 0;
}
int phOffset = header->e_phoff;
int phNumber = header->e_phnum;
int phPhyAddr = phOffset + nBase;
LOGD("phOffset : %x",phOffset);
LOGD("phNumber : %x",phNumber);
LOGD("phPhyAddr : %x",phPhyAddr);
int i = 0;
ElfW(Phdr)* phdr_table = (ElfW(Phdr)*)(nBase + phOffset);
if (phdr_table == 0)
{
LOGD("phdr_table address : 0");
return 0;
}
/*
// Program header for ELF32.
struct Elf32_Phdr {
Elf32_Word p_type; // Type of segment
Elf32_Off p_offset; // File offset where segment is located, in bytes
Elf32_Addr p_vaddr; // Virtual address of beginning of segment
Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific)
Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero)
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment constraint
};
*/
//遍历program header table,ptype等于2即为dynameic,获取到p_offset
unsigned long dynamicAddr = 0;
unsigned int dynamicSize = 0;
for (int i = 0; i < phNumber; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + nBase;
dynamicSize = phdr_table[i].p_memsz;
break;
}
}
LOGD("Dynamic Addr : %x",dynamicAddr);
LOGD("Dynamic Size : %x",dynamicSize);
/*
typedef struct dynamic {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
*/
//开始遍历dynamic段结构,d_tag为6即为GOT表地址
int systemTableAddr = 0;
ElfW(Dyn)* dynamic_table = (ElfW(Dyn)*)(dynamicAddr);
for(i=0;i < dynamicSize / 8;i ++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == 6)
{
systemTableAddr = val + nBase;
break;
}
}
LOGD("System Table Addr : %x",systemTableAddr);
/*
typedef struct elf32_sym {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
*/
//遍历GOT表,查找GOT表中标记的ReturnGiveValue函数地址,替换为ReturnFakeValue的地址
int giveValuePtr = 0;
int fakeValuePtr = 0;
int fakeFunc = (int)ReturnFakeValue - nBase;
int giveFunc = (int)ReturnGiveValue - nBase;
i = 0;
LOGD("fakeFunc Addr : %x",fakeFunc);
LOGD("giveFunc Addr : %x",giveFunc);
void* pstart = (void*)MEM_PAGE_START(((ElfW(Addr))systemTableAddr));
mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_WRITE | PROT_EXEC);
ElfW(Sym)* symTab = (ElfW(Sym)*)(systemTableAddr);
while(1)
{
LOGD("func Addr : %x",symTab[i].st_value);
if(symTab[i].st_value == giveFunc)
{
symTab[i].st_value = fakeFunc;
LOGD("New Give func Addr : %x",symTab[i].st_value);
break;
}
i ++;
}
mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_EXEC);
return 0;
}