android got Table HOOK Implementation Code

  • 2021-12-13 16:57:42
  • OfStack

Overview

hook of so file of android is divided into Got table hook, Sym table hook, inline hook and so on according to ELF file characteristics.
Global symbol table (GOT table) hook, it is through the analysis of SO file, the hook function in the got table address is replaced by its own function entry address, so that every time the target process calls the hook function, it actually executes our own function.

Androd so injection and function Hook (based on got table) steps:

1. ptrace Attach Target pid Process;
2. In the target pid process, find the memory space (for storing the path of the injected so file and the name of the called function in so or shellcode);
3. Calling dlopen, dlsym and other functions in the target pid process to load so file to realize ES40so injection and Hook of the function;
4. Release the attached target pid process and unload the injected so file.

Specific code implementation

The following is an example of got hook using the fopen function.


// Realization of getting module address function 
void* getModuleBase(pid_t pid, const char* module_name){
    FILE* fp;
    long address = 0;
    char* pch;
    char filename[32];
    char line[1024];

    //  Format the string to get  "/proc/pid/maps"
    if(pid < 0){
        snprintf(filename, sizeof(filename), "/proc/self/maps");
    }else{
        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
    }

    //  Open a file /proc/pid/maps Gets the specified pid Memory module information loaded by the process 
    fp = fopen(filename, "r");
    if(fp != NULL){
        //  Every time 1 Line, read file  /proc/pid/maps Content in 
        while(fgets(line, sizeof(line), fp)){
            //  Find the specified so Module 
            if(strstr(line, module_name)){
                //  Split string 
                pch = strtok(line, "-");
                //  String length shaping 
                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);
}

Related articles: