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是 ...
随机推荐
- java8中Lambda表达式和Stream API
一.Lambda表达式 1.语法格式 Lambda是匿名函数,可以传递代码.使用“->”操作符,改操作符将lambda分成两部分: 左侧:指定了 Lambda 表达式需要的所有参数 右侧:指定了 ...
- Android--使用JobService实现进程保活
进程保活一直是广大APP开发者所希望的,因为进程活着我们就可以操作很多事情(推送,数据同步等等),但是google大大是不允许这样做的(优化),所以我们要另辟蹊径. 先来看看android中有几种进程 ...
- ChakraCore/JSRT使用问题汇总
QQ交流群:523723780(ChakraCore) ChakraCore是什么? 一个微软开源的,用于Windows IE/Edge内核的高效JS脚本引擎. 前不久微软开源了ChakraCore, ...
- [Python][小知识][NO.1] Python字符串前 加 u、r、b 的含义
1.字符串前加 u 例:u"我是含有中文字符组成的字符串." 作用:后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止因为源码储存格式问题,导致再次使用时出 ...
- 实现wc部分功能 java
GitHub地址:https://github.com/carlylewen/ruangong 相关要求 基本功能 wc.exe -c file.c //返回文件 file.c 的字符数(实现 ...
- JSON基础知识点
一.介绍: JSON是一种轻量级的数据交换格式.易于人阅读和编写.同时也易于机器解析和生成. 二.数据格式: 1.JSON建构于两种数据格式: “名称/值”对(键值对)的集合,不同的语言中,它被理解为 ...
- 深入了解IOC
老师在简书写的一篇博客 https://www.jianshu.com/p/79f8331e1f24
- c/c++ 函数模板初探
函数模板初探 1,由来:有时候,函数的逻辑是一样的,只是参数的类型不同,比如下面 int Max(int a, int b){ return a > b ? a : b; } double Ma ...
- THINKphp中常见的Request请求类
p($request->domain()); // 获取当前域名 https://jd3.kissneck.com p($request->baseFile()); // 获取当前入口文件 ...
- Python3 Selenium多窗口切换
Python3 Selenium多窗口切换 以腾讯网(http://www.qq.com/)为例,打开腾讯网,点击新闻,打开腾讯新闻,点击新闻中第一个新闻链接. 在WebDriver中封装了获取当前窗 ...