signed

QiShunwang

“诚信为本、客户至上”

GOT Hook的简单实现与原理

2021/6/9 8:26:07   来源:

简述

有什么用?

​ 通过hook全局符号表实现各种黑科技功能,比如我们可以hook open,write和read监控文件的IO读写,hook malloc,calloc,realloc 和 free统计分配了多少内存,内存是否被泄露,也可以对其他进程进行hook实现各种 黑科技功能(root),这种hook方式比较简单,只需更改表中符号地址即可,但只能hook导入函数。

ELF

​ ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。

在这里插入图片描述

具体实现

1. 获取模块基址

​ Elf加载的地址是随机的我们只有在elf加载后才能获取到他的基址,下面有两种方式获取

通用:

​ 我们可以读取 /proc/self/maps,对其进行解析,下面是maps文件的格式

 address           perms offset  dev   inode       pathname
 00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon
 00651000-00652000 r--p 00051000 08:02 173521      /usr/bin/dbus-daemon
 00652000-00655000 rw-p 00052000 08:02 173521      /usr/bin/dbus-daemon
 00e03000-00e24000 rw-p 00000000 00:00 0           [heap]
 00e24000-011f7000 rw-p 00000000 00:00 0           [heap]
 ...
Android 5.0 +:

​ 通过翻阅 Linux 资料我们找到了 dl_iterate_phdr 函数

static int callback(struct dl_phdr_info *info, size_t size, void *data) 
{
    ..
    auto *pInfo = new struct dl_phdr_info(*info);
    if (strstr(pInfo->dlpi_name, "模块名")) 
    {
        // pInfo->dlpi_addr便是我们的基地址
        ..
    }
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example1_gothook_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {
    ...
    dl_iterate_phdr(&callback, nullptr);
    ...
}

2. 获取Program_header_table

// dl_iterate_phdr
Elf32_Phdr *phdr = &(pInfo->dlpi_phdr[i]);

//通用,读取Elf_Ehdr

typedef struct{
  unsigned char e_ident[EI_NIDENT]; 
  Elf32_Half e_type;	//文件的类型
  Elf32_Half e_machine; //体系结构
  Elf32_Word e_version; //文件的版本
  Elf32_Addr e_entry; 	//入口地址
  Elf32_Off e_phoff;	//Program header table
  Elf32_Off e_shoff;	//Section header table
  Elf32_Word e_flags;
  Elf32_Half e_ehsize;
  Elf32_Half e_phentsize;
  Elf32_Half e_phnum;
  Elf32_Half e_shentsize;
  Elf32_Half e_shnum;
  Elf32_Half e_shstrndx;
} Elf32_Ehdr;

Elf_Ehdr *ehdr = (Elf_Ehdr *) base;
Elf32_Phdr *phdr = (Elf32_Phdr *)ehdr->e_phoff + base

3.获取DT_STRTAB,DT_JMPREL,DT_SYMTAB

我们对Program_header_table进行遍历获取 PT_DYNAMIC

在这里插入图片描述

通过 p_vaddr 获取Elf_Dyn

在这里插入图片描述

JMPREL

在这里插入图片描述

ELFStringTable

在这里插入图片描述

ELFSymolTable

在这里插入图片描述

for (int i = 0; i < pInfo->dlpi_phnum; ++i) {
    __u8 *str_table = nullptr;
    Elf_Rela *jmprel = nullptr;
    Elf_Sym *sym_table = nullptr;
    if (phdr->p_type == PT_DYNAMIC) {
        auto *dyn = (Elf_Dyn *) (phdr->p_vaddr + pInfo->dlpi_addr);
        int i1 = 1;
        while (dyn->d_tag) {
            if (dyn->d_tag == DT_STRTAB) { //是否DT_STRTAB
                str_table = (__u8 *) (dyn->d_un.d_ptr + pInfo->dlpi_addr);
            }
            if (dyn->d_tag == DT_JMPREL) {  //是否DT_JMPREL
                jmprel = (Elf_Rela *) (dyn->d_un.d_ptr + pInfo->dlpi_addr);
            }
            if (dyn->d_tag == DT_SYMTAB) {	//是否DT_SYMTAB
                sym_table = (Elf_Sym *) (dyn->d_un.d_ptr + pInfo->dlpi_addr);
            }

            dyn = reinterpret_cast<Elf_Dyn *>(phdr->p_vaddr + pInfo->dlpi_addr +
                                              sizeof(Elf_Dyn) * i1++);
        }
        ...
     }
}

4.进行Hook

查看strstr函数原型我们自己实现了一个用来替换的函数,每次调用strstr都会到我们自己的函数这里打印日志

在这里插入图片描述

Hook代码

if (jmprel && str_table && sym_table) {
    for (int j = 0; jmprel->r_info; ++j) { //遍历jmprel
        ElfW(Word) sym = ELFW(R_SYM)(jmprel[j].r_info); //取符号下标
        char *name = (char *) (sym_table[sym].st_name + str_table); 
 //从sym_table获取st_name在str_table的偏移
        
        if (strstr(name, "strstr")) { //需要hook的函数
            void *i2 = (void *) (jmprel[j].r_offset + pInfo->dlpi_addr); 
            ElfW(Addr) page_start = PAGE_START(
                    jmprel[j].r_offset + pInfo->dlpi_addr);

            mprotect((ElfW(Addr) *) page_start, PAGE_SIZE, PROT_WRITE | PROT_READ);
 //使用mprotect解除内存访问权限 https://man7.org/linux/man-pages/man2/mprotect.2.html
            
            *((ElfW(Addr) *) i2) = (ElfW(Addr)) myStrstr + jmprel[i].r_addend;
            //修改got表地址
            
            
            mprotect((ElfW(Addr) *) page_start, PAGE_SIZE, PROT_READ);
            //还原内存访问权限
            
            break;
        }
 }

​ 宏定义如下

#define PAGE_SIZE 4096
#define PAGE_START(a) ((a) & ~(PAGE_SIZE-1))
#if defined(__LP64__)
#define ELFW(what) ELF64_ ## what

#define Elf_Dyn Elf64_Dyn
#define Elf_Sym Elf64_Sym
#define Elf_Rela Elf64_Rela
#else
#define ELFW(what) ELF32_ ## what
#define Elf_Dyn Elf32_Dyn
#define Elf_Sym Elf32_Sym
#define Elf_Rela Elf32_Rela

#endif

#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)

5.大功告成

我们每次调用strstr都会打印出两个的参数

extern "C" JNIEXPORT jstring JNICALL
Java_com_example1_gothook_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    dl_iterate_phdr(&callback, nullptr);
    strstr("test1", "test2"); //调用strstr
    return env->NewStringUTF(hello.c_str());
}


hook_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    dl_iterate_phdr(&callback, nullptr);
    strstr("test1", "test2"); //调用strstr
    return env->NewStringUTF(hello.c_str());
}

在这里插入图片描述

关注我的技术公众号
不定期分析各种技术文章
在这里插入图片描述