android的got表HOOK实现代码
作者:小道安全 发布时间:2023-11-18 17:04:51
标签:android,HOOK,got表
概述
对于android的so文件的hook根据ELF文件特性分为:Got表hook、Sym表hook和inline hook等。
全局符号表(GOT表)hook,它是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数。
Androd so注入和函数Hook(基于got表)的步骤:
1.ptrace附加目标pid进程;
2.在目标pid进程中,查找内存空间(用于存放被注入的so文件的路径和so中被调用的函数的名称或者shellcode);
3.调用目标pid进程中的dlopen、dlsym等函数,用于加载so文件实现Android so的注入和函数的Hook;
4.释放附加的目标pid进程和卸载注入的so文件。
具体代码实现
以下以fopen函数进行got hook为例。
//获取模块地址功能实现
void* getModuleBase(pid_t pid, const char* module_name){
FILE* fp;
long address = 0;
char* pch;
char filename[32];
char line[1024];
// 格式化字符串得到 "/proc/pid/maps"
if(pid < 0){
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, "-");
// 字符串转长整形
address = strtoul(pch, NULL, 16);
}
break;
}
}
}
fclose(fp);
return (void*)address;
}
//hook fopen进行实现
//(libxxxx.so文件是ELF32文件)
#define LIBPATH "/data/app-lib/com.xxxx/libxxxx.so"
int hookFopen(){
// 获取目标pid中"/data/app-lib/com.xxxx/libxxxx.so"模块的加载地址
void* base_addr = getModuleBase(getpid(), LIBPATH );
// 保存Hook目标函数的原始调用地址
old_fopen = fopen;
int fd;
// 用open打开内存模块文件"/data/app-lib/com.xxxx/libxxxx.so"
fd = open(LIB_PATH, O_RDONLY);
if(-1 == fd){
return -1;
}
// elf32文件的文件头结构体Elf32_Ehdr
Elf32_Ehdr ehdr;
// 读取elf32格式的文件"/data/app-lib/com.xxxx/libxxxx.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_Shdr shdr;
lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);
// 读取elf32文件中的描述每个节区的信息的结构体(这里是保存elf32文件的每个节区的名称字符串的)
read(fd, &shdr, shent_size);
// 为保存elf32文件的所有的节区的名称字符串申请内存空间
char * string_table = (char *)malloc(shdr.sh_size);
// 定位到具体存放elf32文件的所有的节区的名称字符串的文件偏移处
lseek(fd, shdr.sh_offset, SEEK_SET);
read(fd, string_table, shdr.sh_size);
lseek(fd, shdr_addr, SEEK_SET);
int i;
uint32_t out_addr = 0;
uint32_t out_size = 0;
uint32_t got_item = 0;
int32_t got_found = 0;
// 循环遍历elf32文件的节区表(描述每个节区的信息的结构体)
for(i = 0; 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") == 0
|| strcmp(&(string_table[name_idx]), ".got") == 0){
// 获取节区".got"或者".got.plt"在内存中实际数据存放地址
out_addr = base_addr + shdr.sh_addr;
// 获取节区".got"或者".got.plt"的大小
out_size = shdr.sh_size;
int j = 0;
// 遍历节区".got"或者".got.plt"获取保存的全局的函数调用地址
for(j = 0; j<out_size; j += 4){
// 获取节区".got"或者".got.plt"中的单个函数的调用地址
got_item = *(uint32_t*)(out_addr + j);
// 判断节区".got"或者".got.plt"中函数调用地址是否是将要被Hook的目标函数地址
if(got_item == old_fopen){
got_found = 1;
// 获取当前内存分页的大小
uint32_t page_size = getpagesize();
// 获取内存分页的起始地址(需要内存对齐)
uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1));
// 修改内存属性为可读可写可执行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){
return -1;
}
// Hook的函数,是我们自己定义的函数
got_item = new_fopen;
// 进行恢复内存属性为可读可执行
if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){
return -1;
}
break;
// 目标函数的调用地址已经被Hook了
}else if(got_item == new_fopen){
break;
}
}
// 对目标函数HOOk成功,跳出循环
if(got_found)
break;
}
}
}
free(string_table);
close(fd);
}
来源:https://blog.csdn.net/c_kongfei/article/details/119620172


猜你喜欢
- 前言好久没写 linphone-sdk-android 相关的文章了,本文记录下笔者分析 linphone-sdk 版本号生成的过程。分析注
- 实现效果如图所示:首先公布实现代码:一. 自定义实现import.org.springframework.security.core.use
- 传统界面的布局方式总是行列分明、坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳。这个时候瀑布流布局的出现,就给人
- 一、前言闭锁与栅栏是在多线程编程中的概念,因为在多线程中,我们不能控制线程的执行状态,所以给线程加锁,让其按照我们的想法有秩序的执行。闭锁C
- 本文实例讲述了C#交错数组用法。分享给大家供大家参考。具体分析如下:交错数组是数组的数组,交错数组的元素可以是不同的尺寸和大小。交错数组有时
- 一、Error:All flavors must now belong to a named flavor dimension问题描述:Er
- 本文实例讲述了java基于递归算法实现汉诺塔问题。分享给大家供大家参考,具体如下:package test;import java.util
- Java的static关键字和C/C++语言的关键字有所不同:一旦在Java里使用了static关键字,那么这样的内容不再属于对象自己,而是
- 在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQue
- Junit中的基本注解:@Test:使用该注解标注的public void方法会表示为一个测试方法;@BeforeClass:表示在类中的任
- 上篇给大家介绍了Spring Boot启动过程完全解析(一),大家可以点击参考下该说refreshContext(context)了,首先是
- 目录效果展示实现原理实现步骤完整代码展示效果展示实现原理首先需要生成绘制小花的坐标点,坐标点的横坐标是根据控件的宽度随机生成的,而纵坐标则设
- 线性表是其组成元素间具有线性关系的一种数据结构,对线性表的基本操作主要有,获取元素,设置元素值,遍历,插入,删除,查找,替换,排序等。而线性
- 本文实例为大家分享了Android绘制钟表的具体代码,供大家参考,具体内容如下首先要画一个表,我们要先知道步骤如何:1、仪表盘----外面最
- 前言关于ThreadLocal (线程本地存储),从字面意思上看主要是存储一些本地变量,使它们能在一个线程内共用,与其他的线程进行数据隔离,
- SpringAOP 通过JoinPoint获取参数名和值在Java8之前,代码编译为class文件后,方法参数的类型固定,但是方法名称会丢失
- 一:模式说明模式定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求
- 本文句句走心,希望老铁们用心阅读并实战,一定会有收获的。摘要:本文主要讨论生产环境中枚举类的使用。首先会通过对枚举类概念进行简单的介绍,引入
- LRU缓存替换策略缓存是一种非常常见的设计,通过将数据缓存到访问速度更快的存储设备中,来提高数据的访问速度,如内存、CPU缓存、硬盘缓存等。
- JAVA操作XML文档主要有四种方式,分别是DOM、SAX、JDOM和DOM4J,DOM和SAX是官方提供的,而JDOM和DOM4J则是引用