android下got表HOOK实现

简介: 通过基于got表进行实现android下的hook实现。

简介

对于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 LIB_PATH "/data/app-lib/com.xxxx/libxxxx.so"

int hookFopen(){

    // 获取目标pid进程中"/data/app-lib/com.xxxx/libxxxx.so"模块的加载地址
    void* base_addr = getModuleBase(getpid(), LIB_PATH);
    LOGD("[+] libxxxx.so address = %p \n", base_addr);

    // 保存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文件中节区表的每个单元信息结构体(描述每个节区的信息的结构体)
    Elf32_Shdr shdr;
    // elf32文件中定位到存放每个节区名称的字符串表的信息结构体位置.shstrtab
    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){
                        LOGD("[+] Found fopen in got.\n");
                        got_found = 1;
                        // 获取当前内存分页的大小
                        uint32_t page_size = getpagesize();
                        // 获取内存分页的起始地址(需要内存对齐)
                        uint32_t entry_page_start = (out_addr + j) & (~(page_size - 1));
                        LOGD("[+] entry_page_start = %lx, page size = %lx\n", entry_page_start, page_size);
                        // 修改内存属性为可读可写可执行
                        if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1){
                            LOGD("mprotect false.\n");
                            return -1;
                        }
                        LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "before hook function", got_item, new_fopen);

                        // Hook函数为我们自己定义的函数
                        got_item = new_fopen;
                        LOGD("[+] %s, old_fopen = %lx, new_fopen = %lx\n", "after hook function", got_item, new_fopen);
                        // 恢复内存属性为可读可执行
                        if(mprotect((uint32_t*)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1){
                            LOGD("mprotect false.\n");
                            return -1;
                        }
                        break;
                    // 此时,目标函数的调用地址已经被Hook了
                    }else if(got_item == new_fopen){
                        LOGD("[+] Already hooked.\n");
                        break;
                    }
                }
                // Hook目标函数成功,跳出循环
                if(got_found)
                    break;
            }
        }
    }
    free(string_table);
    close(fd);
}
相关文章
|
Android开发
flutter中实现仿Android端的onResume和onPause方法
flutter中实现仿Android端的onResume和onPause方法
|
8月前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
9月前
|
缓存 Android开发
Android插件化——高手必备的Hook技术,零基础开发android
Android插件化——高手必备的Hook技术,零基础开发android
|
XML 存储 JavaScript
Android打造专有Hook第三篇,实战全量代码规范检查
目前的规范检查,我分为了全量文件检查和增量文件检查,基本上和Git提交保持一致,在实际的业务中,开发者可以动态修改配置文件参数gitIncrement,来切换是增量还是全量,增量和全量有一些是共通的地方,接下来的代码中会陈述。
|
9月前
|
Android开发
安卓逆向 -- Hook多个dex文件
安卓逆向 -- Hook多个dex文件
78 1
|
9月前
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
136 0
|
9月前
|
Shell Android开发 数据安全/隐私保护
安卓逆向 -- Frida环境搭建(HOOK实例)
安卓逆向 -- Frida环境搭建(HOOK实例)
205 0
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
124 0
|
Shell Android开发 数据安全/隐私保护
安卓逆向 -- Frida环境搭建(HOOK实例)
安卓逆向 -- Frida环境搭建(HOOK实例)
390 0
|
JSON JavaScript 数据可视化
Android打造专有hook,让不规范的代码扼杀在萌芽之中
正所谓规范是一个项目的基石,也是衡量一个项目,是否健壮,稳定,可维护的标准,可谓是相当重要的。我相信,大部分的公司都有自己的一套规范标准,我也相信,很多可能就是一个摆设,毕竟人员的众多,无法做到一一的约束,如果采取人工的检查,无形当中就会投入大量的时间和人力成本,基于此,所谓的规范,也很难执行下去。
136 1

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 9
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 10
    Android学习自定义View(四)——继承控件(滑动时ListView的Item出现删除按钮)