Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook
前文介绍了导入表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的更多相关文章
- Android so注入( inject)和Hook(挂钩)的实现思路讨论
本文博客:http://blog.csdn.net/qq1084283172/article/details/54095995 前面的博客中分析一些Android的so注入和Hook目标函数的代码,它 ...
- Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook
全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...
- Android so注入(inject)和Hook技术学习(一)
以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下. 首先来看注入流程.Android so的注入流程如下: attach到远程进程 -> ...
- (转)全文检索技术学习(三)——Lucene支持中文分词
http://blog.csdn.net/yerenyuan_pku/article/details/72591778 分析器(Analyzer)的执行过程 如下图是语汇单元的生成过程: 从一个Re ...
- Flask学习 三 web表单
web表单 pip install flask-wtf 实现csrf保护 app.config['SECRET_KEY']='hard to guess string' # 可以用来存储框架,扩展,程 ...
- vue 学习三 v-model 表单绑定输入 以及修饰符的用处
v-model 指定使用过vue的同学都应该是很熟悉的了,这里就不多介绍,本章主要就是记录一些v-model非常实用的修饰符和对于v-model在html文本框,多行文本框,选择框,单选框,复选框上对 ...
- x64内核HOOK技术之拦截进程.拦截线程.拦截模块
x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可. 但是 ...
- Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...
- Android Native Hook技术(一)
原理分析 ADBI是一个著名的安卓平台hook框架,基于 动态库注入 与 inline hook 技术实现.该框架主要由2个模块构成:1)hijack负责将so注入到目标进程空间,2)libbase是 ...
随机推荐
- html之CSS样式学习笔记
本文内容: 字体样式 文本样式 背景样式 尺寸样式 盒子模型 边框样式 边距样式 浮动布局 定位布局 [CSS3]伸缩布局 标签显示方式 列表样式 [CSS3]过渡样式 [CSS3]变换样式之2D变形 ...
- 口碑点餐相关问题FAQ
1.菜品上传中:出现重复错误或者违禁词 检查并修改商家中心本次上传中的重复菜品,或者删除口碑掌柜以及第三方平台已添加的重复菜品(重复菜品临时快捷办法:修改菜品名称) 2.手持pos 打开自动接单,无响 ...
- Redis内存数据库快速入门
Redis简介 Redis是一个开源(BSD许可),内存数据结构存储,用作数据库,缓存和消息代理.它支持数据结构,如 字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的 ...
- celery任务进程关闭
方法1: ps auxww|grep 方法2: Ctrl+C 方法3: celery multi 管理 celery multi start w1 -A proj -l info celery mul ...
- C# 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集
1.在项目解决方案中,找到项目的app.config文件
- mssql sqlserver SQL 位运算举例权限应用
摘要: 下文通过举例的方式讲述sqlserver中位运算的相关知识,如下所示: 实验环境:sqlserver 2008 R2 在sqlserver的权限设置,我们通常使用1.2.4.8.16.32.6 ...
- 为爱好舞蹈的人们做的软件,细究数据结构,操作系统,磁盘原理,用java/c/c++写一个开源 MP3助手
1.可以给歌曲间插播空白音乐 2.拖拽式调整 3.先排序,后一键写入顺序文件. 国外的开源软件 MP3 播放排序 http://www.murraymoffatt.com/software-prob ...
- 【PAT】B1012 数字分类
注意逻辑的描述,只要认真看题,就能做对,如果自己结果一直不正确,请仔细推一下样例结果 #include<stdio.h> int arr[1005]; int main(){ int N, ...
- 力扣算法题—052N皇后问题2
跟前面的N皇后问题没区别,还更简单 #include "000库函数.h" //使用回溯法 class Solution { public: int totalNQueens(in ...
- Markdown编辑器开发记录(二):Markdown编辑器的使用与开发入门
Markdown编辑器的使用与开发入门 在部门做技术分享的时候简单整理了一下手里的资料 1 是什么 1.1 Markdown是一种轻量级标记语言 Markdown是一种轻量级标记语言,创始人为约翰·格 ...