前文介绍了导入表hook,现在来说下导出表的hook。导出表的hook的流程如下。
1、获取动态库基值   

 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;
}

2、计算program header table实际地址

通过ELF文件头获取到程序表头的偏移地址及表头的个数

     Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
if (memcmp(header->e_ident, "\177ELF", ) != ) {
return ;
}
int phOffset = header->e_phoff;
int phNumber = header->e_phnum;
int phPhyAddr = phOffset + base_addr; int i = ; Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset);
if (phdr_table == )
{
LOGD("[+] phdr_table address : 0");
return ;
}

3、遍历program header table,找到类型为PT_DYNAMIC的区段(动态链接段),ptype等于2即为dynamic,获取到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
};

因此得到dynamic段对应的地址:

 for (i = ; i < phNumber; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + base_addr;
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=; i < dynamicSize / ; i++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == )
{
symbolTableAddr = val + base_addr;
break;
}
}

5、遍历GOT表,查找GOT表中标记的目标函数地址,替换为新函数的地址。

我们需要知道符号表的结构:

/* Symbol Table Entry */
typedef struct elf32_sym {
Elf32_Word st_name; /* name - index into string table */
Elf32_Addr st_value; /* symbol value */
Elf32_Word st_size; /* symbol size */
unsigned char st_info; /* type and binding */
unsigned char st_other; /* 0 - no defined meaning */
Elf32_Half st_shndx; /* section header index */
} Elf32_Sym;

然后替换成目标函数的st_value值,即偏移地址

 while()
{
//LOGD("[+] func Addr : %x", symTab[i].st_value);
if(symTab[i].st_value == oldFunc)
{
//st_value 保存的是偏移地址
symTab[i].st_value = newFunc;
LOGD("[+] New Give func Addr : %x", symTab[i].st_value);
break;
}
i++;
}

注意点:

1、我们知道代码段一般都只会设置为可读可执行的,因此需要使用mprotect改变内存页为可读可写可执行;
2、如果执行完这些操作后hook并没有生效,可能是由于缓存的原因,需要使用cacheflush函数对该内存进行操作。
3、获取目标函数的偏移地址,可以通过dlsym得到绝对地址,再减去基址。

我们以hook libvivosgmain.so中的check_signatures函数为例,完整代码如下:

 #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 <dlfcn.h>
#include <sys/mman.h> #define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) int (*old_check_signatures)();
int new_check_signatures(){
LOGD("[+] New call check_signatures.\n");
if(old_check_signatures == -){
LOGD("error.\n");
}
return old_check_signatures();
} 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_check_signatures(){ // 获取目标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); //计算program header table实际地址
Elf32_Ehdr *header = (Elf32_Ehdr*)(base_addr);
if (memcmp(header->e_ident, "\177ELF", ) != ) {
return ;
} void* handle = dlopen("/data/app-lib/com.bbk.appstore-2/libvivosgmain.so", RTLD_LAZY);
//获取原函数地址
void* funcaddr = dlsym(handle, "check_signatures");
LOGD("[+] libvivosgmain.so check_signatures address = %p \n", (int)funcaddr); int phOffset = header->e_phoff;
int phNumber = header->e_phnum;
int phPhyAddr = phOffset + base_addr;
LOGD("[+] phOffset : %x", phOffset);
LOGD("[+] phNumber : %x", phNumber);
LOGD("[+] phPhyAddr : %x", phPhyAddr);
int i = ; Elf32_Phdr* phdr_table = (Elf32_Phdr*)(base_addr + phOffset);
if (phdr_table == )
{
LOGD("[+] phdr_table address : 0");
return ;
} /*
// 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即为dynamic,获取到p_offset
unsigned long dynamicAddr = ;
unsigned int dynamicSize = ; for (i = ; i < phNumber; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + base_addr;
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 symbolTableAddr = ;
Elf32_Dyn* dynamic_table = (Elf32_Dyn*)(dynamicAddr); for(i=; i < dynamicSize / ; i++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == )
{
symbolTableAddr = val + base_addr;
break;
}
}
LOGD("Symbol Table Addr : %x", symbolTableAddr); /*
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表中标记的check_signatures函数地址,替换为new_check_signatures的地址
int giveValuePtr = ;
int fakeValuePtr = ;
int newFunc = (int)new_check_signatures - (int)base_addr;
int oldFunc = (int)funcaddr - (int)base_addr;
i = ;
LOGD("[+] newFunc Addr : %x", newFunc);
LOGD("[+] oldFunc Addr : %x", oldFunc); // 获取当前内存分页的大小
uint32_t page_size = getpagesize();
// 获取内存分页的起始地址(需要内存对齐)
uint32_t mem_page_start = (uint32_t)(((Elf32_Addr)symbolTableAddr)) & (~(page_size - ));
LOGD("[+] mem_page_start = %lx, page size = %lx\n", mem_page_start, page_size);
mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
Elf32_Sym* symTab = (Elf32_Sym*)(symbolTableAddr);
while()
{
//LOGD("[+] func Addr : %x", symTab[i].st_value);
if(symTab[i].st_value == oldFunc)
{
//st_value 保存的是偏移地址
symTab[i].st_value = newFunc;
LOGD("[+] New Give func Addr : %x", symTab[i].st_value);
break;
}
i++;
}
mprotect((uint32_t)mem_page_start, page_size, PROT_READ | PROT_EXEC); return ;
} int hook_entry(char* a){
LOGD("[+] Start hooking.\n");
hook_check_signatures();
return ;
}

我们还是通过执行“Android so注入( inject)和Hook技术学习(一)”文中的inject程序将本程序注入到目标进程进行hook,运行结果如下:

参考资料:

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

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

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

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

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

    全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...

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

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

  4. (转)全文检索技术学习(三)——Lucene支持中文分词

    http://blog.csdn.net/yerenyuan_pku/article/details/72591778 分析器(Analyzer)的执行过程 如下图是语汇单元的生成过程:  从一个Re ...

  5. Flask学习 三 web表单

    web表单 pip install flask-wtf 实现csrf保护 app.config['SECRET_KEY']='hard to guess string' # 可以用来存储框架,扩展,程 ...

  6. vue 学习三 v-model 表单绑定输入 以及修饰符的用处

    v-model 指定使用过vue的同学都应该是很熟悉的了,这里就不多介绍,本章主要就是记录一些v-model非常实用的修饰符和对于v-model在html文本框,多行文本框,选择框,单选框,复选框上对 ...

  7. x64内核HOOK技术之拦截进程.拦截线程.拦截模块

    x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可. 但是 ...

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

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

  9. Android Native Hook技术(一)

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

随机推荐

  1. html之CSS样式学习笔记

    本文内容: 字体样式 文本样式 背景样式 尺寸样式 盒子模型 边框样式 边距样式 浮动布局 定位布局 [CSS3]伸缩布局 标签显示方式 列表样式 [CSS3]过渡样式 [CSS3]变换样式之2D变形 ...

  2. 口碑点餐相关问题FAQ

    1.菜品上传中:出现重复错误或者违禁词 检查并修改商家中心本次上传中的重复菜品,或者删除口碑掌柜以及第三方平台已添加的重复菜品(重复菜品临时快捷办法:修改菜品名称) 2.手持pos 打开自动接单,无响 ...

  3. Redis内存数据库快速入门

    Redis简介 Redis是一个开源(BSD许可),内存数据结构存储,用作数据库,缓存和消息代理.它支持数据结构,如 字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的 ...

  4. celery任务进程关闭

    方法1: ps auxww|grep 方法2: Ctrl+C 方法3: celery multi 管理 celery multi start w1 -A proj -l info celery mul ...

  5. C# 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集

    1.在项目解决方案中,找到项目的app.config文件

  6. mssql sqlserver SQL 位运算举例权限应用

    摘要: 下文通过举例的方式讲述sqlserver中位运算的相关知识,如下所示: 实验环境:sqlserver 2008 R2 在sqlserver的权限设置,我们通常使用1.2.4.8.16.32.6 ...

  7. 为爱好舞蹈的人们做的软件,细究数据结构,操作系统,磁盘原理,用java/c/c++写一个开源 MP3助手

    1.可以给歌曲间插播空白音乐 2.拖拽式调整 3.先排序,后一键写入顺序文件. 国外的开源软件 MP3 播放排序  http://www.murraymoffatt.com/software-prob ...

  8. 【PAT】B1012 数字分类

    注意逻辑的描述,只要认真看题,就能做对,如果自己结果一直不正确,请仔细推一下样例结果 #include<stdio.h> int arr[1005]; int main(){ int N, ...

  9. 力扣算法题—052N皇后问题2

    跟前面的N皇后问题没区别,还更简单 #include "000库函数.h" //使用回溯法 class Solution { public: int totalNQueens(in ...

  10. Markdown编辑器开发记录(二):Markdown编辑器的使用与开发入门

    Markdown编辑器的使用与开发入门 在部门做技术分享的时候简单整理了一下手里的资料 1 是什么 1.1 Markdown是一种轻量级标记语言 Markdown是一种轻量级标记语言,创始人为约翰·格 ...