前文介绍了导入表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. 17.Odoo产品分析 (二) – 商业板块(10) – 电子商务(1)

    查看Odoo产品分析系列--目录 安装电子商务模块 1. 主页 点击"商店"菜单:  2. 添加商品 在odoo中,你不用进入"销售"模块,再进入产品列表添加产 ...

  2. Android为TV端助力 修改videoview的宽度和高度

    如果直接用android的videoview.他是不允许你随意的修改宽度和高度的,所以我们要重写videoview! package com.hysmarthotel.view; import and ...

  3. git上传中的排除的配置文件, git实际的操作代码;

    git上传中的排除的配置文件: git实际的操作 在主目录建立.gitignore文件并输入以下保存: *.class #package file *.war *.ear #kdiff3 ignore ...

  4. 用户不在 sudoers 文件中,此事将被报告

    在使用Linux系统过程中,通常情况下,我们都会使用普通用户进行日常操作,而root用户只有在权限分配及系统设置时才会使用,而root用户的密码也不可能公开.普通用户执行到系统程序时,需要临时提升权限 ...

  5. 《JavaScript面向对象的编程指南》--读书笔记

    第一章.引言 1.5 面向对象的程序设计常用概念 对象(名词):是指"事物"在程序设计语言中的表现形式. 这里的事物可以是任何东西,我们可以看到它们具有某些明确特征,能执行某些动作 ...

  6. MySQL----mysql57服务突然不见了的,解决方法

    一. G:\MySQL\MySQL Server 5.7\bin>mysqld --initialize G:\MySQL\MySQL Server 5.7\bin>mysqld -ins ...

  7. [转载] erp开发-数据查询优化方法

    系统运行环境:MSSQL 2008随着公司业务快速发展,各种业务数据如火箭般的高速增长,出现一个又一个千万行数据的表,往往大表之间的关联,耗费系统大量的磁盘io,并且会影响正常的实时业务的操作,所以我 ...

  8. 根据flickr id 下载图片

    #coding=utf-8 import flickrapi import requests import os n=1 flickr=flickrapi.FlickrAPI('*********** ...

  9. Windows SDK 8安装失败的绕坑办法

    安装win sdk 8,提示错误:管道正在被关闭. 查看安装log文件,有如下错误: Error 0x800700e8: Failed to write message type to pipe.Er ...

  10. ELK收集tomcat状态日志

    1.先查看之前的状态日志输出格式:在logs/catalina.out这个文件中 最上面的日志格式我们可能不太习惯使用,所以能输出下面的格式是最好的,当然需要我们自定义日志格式,接下来看看如何修改 2 ...