全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数。
  GOT表其实包含了导入表和导出表,导出表指将当前动态库的一些函数符号保留,供外部调用,导入表中的函数实际是在该动态库中调用外部的导出函数。
  这里有几个关键点要说明一下:
  (1) so文件的绝对路径和加载到内存中的基址是可以通过 /proc/[pid]/maps 获取到的。
  (2) 修改导入表的函数地址的时候需要修改页的权限,增加写权限即可。
  (3) 一般的导入表Hook是基于注入操作的,即把自己的代码注入到目标程序,本次实例重点讲述Hook的实现,注入代码采用上节所有代码inject.c。
  导入表的hook有两种方法,以hook fopen函数为例。

  方法一:

  通过解析elf格式,分析Section header table找出静态的.got表的位置,并在内存中找到相应的.got表位置,这个时候内存中.got表保存着导入函数的地址,读取目标函数地址,与.got表每一项函数入口地址进行匹配,找到的话就直接替换新的函数地址,这样就完成了一次导入表的Hook操作了。
  hook流程如下图所示:

  

 图1 导入表Hook流程图 

  具体代码实现如下:

  entry.c:

 #include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <elf.h>
#include <fcntl.h>
#include <sys/mman.h> #define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) //FILE *fopen(const char *filename, const char *modes)
FILE* (*old_fopen)(const char *filename, const char *modes);
FILE* new_fopen(const char *filename, const char *modes){
LOGD("[+] New call fopen.\n");
if(old_fopen == -){
LOGD("error.\n");
}
return old_fopen(filename, modes);
} void* get_module_base(pid_t pid, const char* module_name){
FILE* fp;
long addr = ;
char* pch;
char filename[];
char line[]; // 格式化字符串得到 "/proc/pid/maps"
if(pid < ){
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, "-");
// 字符串转长整形
addr = strtoul(pch, NULL, ); // 特殊内存地址的处理
if(addr == 0x8000){
addr = ;
}
break;
}
}
}
fclose(fp);
return (void*)addr;
} #define LIB_PATH "/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
int hook_fopen(){ // 获取目标pid进程中"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"模块的加载地址
void* base_addr = get_module_base(getpid(), LIB_PATH);
LOGD("[+] libvivosgmain.so address = %p \n", base_addr); // 保存被Hook的目标函数的原始调用地址
old_fopen = fopen;
LOGD("[+] Orig fopen = %p\n", old_fopen); int fd;
// 打开内存模块文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
fd = open(LIB_PATH, O_RDONLY);
if(- == fd){
LOGD("error.\n");
return -;
} // elf32文件的文件头结构体Elf32_Ehdr
Elf32_Ehdr ehdr;
// 读取elf32格式的文件"/data/app-lib/com.bbk.appstore-2/libvivosgmain.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文件中节区表的每个单元信息结构体(描述每个节区的信息的结构体)
Elf32_Shdr shdr;
// elf32文件中定位到存放每个节区名称的字符串表的信息结构体位置.shstrtab
lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);
// 读取elf32文件中的描述每个节区的信息的结构体(这里是保存elf32文件的每个节区的名称字符串的)
read(fd, &shdr, shent_size);
LOGD("[+] String table offset is %lu, size is %lu", shdr.sh_offset, shdr.sh_size); //41159, size is 254 // 为保存elf32文件的所有的节区的名称字符串申请内存空间
char * string_table = (char *)malloc(shdr.sh_size);
// 定位到具体存放elf32文件的所有的节区的名称字符串的文件偏移处
lseek(fd, shdr.sh_offset, SEEK_SET);
// 从elf32内存文件中读取所有的节区的名称字符串到申请的内存空间中
read(fd, string_table, shdr.sh_size); // 重新设置elf32文件的文件偏移为节区信息结构的起始文件偏移处
lseek(fd, shdr_addr, SEEK_SET); int i;
uint32_t out_addr = ;
uint32_t out_size = ;
uint32_t got_item = ;
int32_t got_found = ; // 循环遍历elf32文件的节区表(描述每个节区的信息的结构体)
for(i = ; 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") ==
|| strcmp(&(string_table[name_idx]), ".got") == ){
// 获取节区".got"或者".got.plt"在内存中实际数据存放地址
out_addr = base_addr + shdr.sh_addr;
// 获取节区".got"或者".got.plt"的大小
out_size = shdr.sh_size;
LOGD("[+] out_addr = %lx, out_size = %lx\n", out_addr, out_size);
int j = ;
// 遍历节区".got"或者".got.plt"获取保存的全局的函数调用地址
for(j = ; j<out_size; j += ){
// 获取节区".got"或者".got.plt"中的单个函数的调用地址
got_item = *(uint32_t*)(out_addr + j);
// 判断节区".got"或者".got.plt"中函数调用地址是否是将要被Hook的目标函数地址
if(got_item == old_fopen){
LOGD("[+] Found fopen in got.\n");
got_found = ;
// 获取当前内存分页的大小
uint32_t page_size = getpagesize();
// 获取内存分页的起始地址(需要内存对齐)
uint32_t entry_page_start = (out_addr + j) & (~(page_size - ));
LOGD("[+] entry_page_start = %lx, page size = %lx\n", entry_page_start, page_size);
// 修改内存属性为可读可写可执行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -){
LOGD("mprotect false.\n");
return -;
}
LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "before hook function", got_item, new_fopen); // Hook函数为我们自己定义的函数
got_item = new_fopen;
LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "after hook function", got_item, new_fopen);
// 恢复内存属性为可读可执行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -){
LOGD("mprotect false.\n");
return -;
}
break;
// 此时,目标函数的调用地址已经被Hook了
}else if(got_item == new_fopen){
LOGD("[+] Already hooked.\n");
break;
}
}
// Hook目标函数成功,跳出循环
if(got_found)
break;
}
}
}
free(string_table);
close(fd);
} int hook_entry(char* a){
LOGD("[+] Start hooking.\n");
hook_fopen();
return ;
}

运行ndk-build编译,得到libentry.so,push到/data/local/tmp目录下,运行上节所得到的inject程序,得到如下结果,表明hook成功:

图2.

方法二

  通过分析program header table查找got表。导入表对应在动态链接段.got.plt(DT_PLTGOT)指向处,但是每项的信息是和GOT表中的表项对应的,因此,在解析动态链接段时,需要解析DT_JMPREL、DT_SYMTAB,前者指向了每一个导入表表项的偏移地址和相关信息,包括在GOT表中偏移,后者为GOT表。

  具体代码如下:

 #include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <elf.h>
#include <fcntl.h>
#include <sys/mman.h> #define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) //FILE *fopen(const char *filename, const char *modes)
FILE* (*old_fopen)(const char *filename, const char *modes);
FILE* new_fopen(const char *filename, const char *modes){
LOGD("[+] New call fopen.\n");
if(old_fopen == -){
LOGD("error.\n");
}
return old_fopen(filename, modes);
} void* get_module_base(pid_t pid, const char* module_name){
FILE* fp;
long addr = ;
char* pch;
char filename[];
char line[]; // 格式化字符串得到 "/proc/pid/maps"
if(pid < ){
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, "-");
// 字符串转长整形
addr = strtoul(pch, NULL, ); // 特殊内存地址的处理
if(addr == 0x8000){
addr = ;
}
break;
}
}
}
fclose(fp);
return (void*)addr;
} #define LIB_PATH "/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"
int hook_fopen(){ // 获取目标pid进程中"/data/app-lib/com.bbk.appstore-2/libvivosgmain.so"模块的加载地址
void* base_addr = get_module_base(getpid(), LIB_PATH);
LOGD("[+] libvivosgmain.so address = %p \n", base_addr); // 保存被Hook的目标函数的原始调用地址
old_fopen = fopen;
LOGD("[+] Orig fopen = %p\n", old_fopen); //计算program header table实际地址
Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
if (memcmp(header->e_ident, "\177ELF", ) != ) {
return ;
} Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + header->e_phoff);
if (phdr_table == )
{
LOGD("[+] phdr_table address : 0");
return ;
}
size_t phdr_count = header->e_phnum;
LOGD("[+] phdr_count : %d", phdr_count); //遍历program header table,ptype等于PT_DYNAMIC即为dynameic,获取到p_offset
unsigned long dynamicAddr = ;
unsigned int dynamicSize = ;
int j = ;
for (j = ; j < phdr_count; j++)
{
if (phdr_table[j].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[j].p_vaddr + base_addr;
dynamicSize = phdr_table[j].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;
*/
Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr);
unsigned long jmpRelOff = ;
unsigned long strTabOff = ;
unsigned long pltRelSz = ;
unsigned long symTabOff = ;
int i;
for(i=;i < dynamicSize / ;i ++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == DT_JMPREL)
{
jmpRelOff = val;
}
if (dynamic_table[i].d_tag == DT_STRTAB)
{
strTabOff = val;
}
if (dynamic_table[i].d_tag == DT_PLTRELSZ)
{
pltRelSz = val;
}
if (dynamic_table[i].d_tag == DT_SYMTAB)
{
symTabOff = val;
}
} Elf32_Rel* rel_table = (Elf32_Rel*)(jmpRelOff + base_addr);
LOGD("[+] jmpRelOff : %x",jmpRelOff);
LOGD("[+] strTabOff : %x",strTabOff);
LOGD("[+] symTabOff : %x",symTabOff);
//遍历查找要hook的导入函数,这里以fopen做示例
for(i=;i < pltRelSz / ;i++)
{
int number = (rel_table[i].r_info >> ) & 0xffffff;
Elf32_Sym* symTableIndex = (Elf32_Sym*)(number* + symTabOff + base_addr);
char* funcName = (char*)(symTableIndex->st_name + strTabOff + base_addr);
//LOGD("[+] Func Name : %s",funcName);
if(memcmp(funcName, "fopen", ) == )
{
// 获取当前内存分页的大小
uint32_t page_size = getpagesize();
// 获取内存分页的起始地址(需要内存对齐)
uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)rel_table[i].r_offset + base_addr)) & (~(page_size - ));
LOGD("[+] mem_page_start = %lx, page size = %lx\n", mem_page_start, page_size);
//void* pstart = (void*)MEM_PAGE_START(((Elf32_Addr)rel_table[i].r_offset + base_addr));
mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
LOGD("[+] r_off : %x",rel_table[i].r_offset + base_addr);
LOGD("[+] new_fopen : %x",new_fopen);
*(unsigned int*)(rel_table[i].r_offset + base_addr) = new_fopen;
}
} return ;
} int hook_entry(char* a){
LOGD("[+] Start hooking.\n");
hook_fopen();
return ;
}

运行后的结果为:

图3

参考文章:

http://gslab.qq.com/portal.php?mod=view&aid=169

https://blog.csdn.net/u011247544/article/details/78668791

https://blog.csdn.net/qq1084283172/article/details/53942648

Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook的更多相关文章

  1. Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook

    前文介绍了导入表hook,现在来说下导出表的hook.导出表的hook的流程如下.1.获取动态库基值 void* get_module_base(pid_t pid, const char* modu ...

  2. Android so注入( inject)和Hook(挂钩)的实现思路讨论

    本文博客:http://blog.csdn.net/qq1084283172/article/details/54095995 前面的博客中分析一些Android的so注入和Hook目标函数的代码,它 ...

  3. Android Native Hook技术(二)

    Hook技术应用 已经介绍了安卓 Native hook 原理,这里介绍 hook 技术的应用,及 Cyida Substrate 框架. 分析某APP,发现其POST请求数据经过加密,我们希望还原其 ...

  4. Android so注入(inject)和Hook技术学习(一)

    以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下. 首先来看注入流程.Android so的注入流程如下: attach到远程进程 -> ...

  5. Smarty模板技术学习(二)

    本文主要包括以下内容 公共文件引入与继承 内容捕捉 变量调剂器 缓存 Smarty过滤器 数据对象.注册对象 与已有项目结合 公共文件引入与继承 可以把许多模板页面都用到的公共页面放到单独文件里边,通 ...

  6. (转)全文检索技术学习(二)——配置Lucene的开发环境

    http://blog.csdn.net/yerenyuan_pku/article/details/72589380 Lucene下载 Lucene是开发全文检索功能的工具包,可从官方网站http: ...

  7. Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...

  8. Android Native Hook技术(一)

    原理分析 ADBI是一个著名的安卓平台hook框架,基于 动态库注入 与 inline hook 技术实现.该框架主要由2个模块构成:1)hijack负责将so注入到目标进程空间,2)libbase是 ...

  9. Hook技术

    hook钩子: 使用技术手段在运行时动态的将额外代码依附现进程,从而实现替换现有处理逻辑或插入额外功能的目的. 它的技术实现要点有两个: 1)如何注入代码(如何将额外代码依附于现有代码中). 2)如何 ...

随机推荐

  1. 使用SQL Server Analysis Services数据挖掘的关联规则实现商品推荐功能(七)

    假如你有一个购物类的网站,那么你如何给你的客户来推荐产品呢?这个功能在很多电商类网站都有,那么,通过SQL Server Analysis Services的数据挖掘功能,你也可以轻松的来构建类似的功 ...

  2. Weblogic 启动报错:java.lang.NoClassDefFoundError

    Weblogic 启动报错:java.lang.NoClassDefFoundError  ####<2015-6-17 下午03时30分47秒 CST> <Error> &l ...

  3. SweetAlert2 使用教程

    SweetAlert2是一款功能强大的纯Js模态消息对话框插件.SweetAlert2用于替代浏览器默认的弹出对话框,它提供各种参数和方法,支持嵌入图片,背景,HTML标签等,并提供5种内置的情景类, ...

  4. 三星手机 Samsung Galaxy S3 无法复制粘贴的不完美解决方法

    问题简单描述 从上周开始我的Samsung Galaxy S3手机就无法实现复制粘贴功能了,每次复制时都提示复制到了剪贴板,但是粘贴时就会发现根本粘贴不了,无法打开剪贴板.真的是莫明其妙啊,我的手机没 ...

  5. 源码来袭:bind手写实现

    JavaScript中的this指向规则 源码来袭:call.apply手写实现与应用 理解建议:如果对this指向规则不了解的话,建议先了解this指向规则,最好还能对call和apply的使用和内 ...

  6. python-imaging-tk : Depends: python-imaging (= 1.1.7-4ubuntu0.12.04.3) but 3.1.2-0ubuntu1.1 is to be installed E: Unable to corre

    最近,将电脑主机升级到ubuntu16.04,但是需要用到 python-imaging-tk,先是报错: import PIL.ImageTk as ImageTkImportError: No m ...

  7. docker开源仓库Harbor部署笔记

    Harbor介绍Harbor是Vmvare团队开发的开源企业级registry仓库,相比docker官方拥有更丰富的权限权利和完善的架构设计,适用大规模docker集群部署提供仓库服务.项目地址:ht ...

  8. Word 2010 制作文档结构之图标自动编号设置

    注意: 使用图片自动编号时,如果文档标题使用的样式是通过“将所选内容保存为新快速样式”所生成的样式,则图片自动编号不会生效 因此设置标题样式时,不要 新建样式,直接使用word预设的“标题 1”样式和 ...

  9. P3456 [POI2007]GRZ-Ridges and Valleys(bfs)

    P3456 [POI2007]GRZ-Ridges and Valleys 八个方向都跑一遍bfs,顺便判断一下是山峰还是山谷,或者是山坡(俩都不是) (实在不知道要说啥了qwq) #include& ...

  10. 流行创意风格教师求职简历免费word模板

    18款流行创意风格教师求职简历免费word模板,也可用于其他专业和职业,个人免费简历模板,个人简历表免费,个人简历表格. 声明:该简历模板仅用于个人欣赏使用,请勿用于商业用途,谢谢. 下载地址:百度网 ...