自己來動手實現(xiàn) Native Hook

Native Hook 是我們性能優(yōu)化中最常見的手段之一鸭叙,推薦大家用開源的方案像 xhookbhook 等等,會用這肯定是最基礎的拣宏,其次我們一直都追求知道原理并且要自己能寫沈贝。今天這里我們自己來實現(xiàn)一套簡單的 Native Hook ,我們只寫關鍵代碼勋乾。為了確保大家都能看懂宋下,我們可能需要以下基礎知識:

  • 跨 so 的方法調(diào)用流程
  • elf 文件格式
  • 基本的 NDK 開發(fā)知識

有了以上基礎知識,我們實現(xiàn)起來就會變得簡單了市俊,雖然過程中可能會遇到一些問題杨凑,但前期我們只需要確保流程和方案沒問題就行。其實主要就是兩步:首先摆昧,讀取 /proc/self/maps 文件內(nèi)容撩满,找到目標 so 文件的基地址;最后绅你,解析 elf 找到要 hook 的函數(shù)的地址伺帘,替換成我們自己指定的函數(shù)地址。

1. 讀取 /proc/self/maps 文件內(nèi)容忌锯,找到目標 so 文件的基地址

讓我們先寫代碼來看看 /proc/self/maps 的內(nèi)容

    // 1. 讀取 /proc/self/maps 內(nèi)容伪嫁,找到 so 文件路徑和基地址
    FILE* maps_fp = fopen("/proc/self/maps", "r");
    if (maps_fp == NULL) {
        LOGE("open /proc/self/maps error");
    }
    char  line[512];
    while (fgets(line, sizeof(line), maps_fp)) {
        LOGE("%s", line);
    }
...... 省了很多行打印
7b86a57000-7b87a57000 rw-p 00000000 00:00 0                              [anon:dalvik-region space live bitmap]
7b87a57000-7b87b57000 rw-p 00000000 00:00 0                              [anon:dalvik-allocspace zygote / non moving space mark-bitmap 0]
7b87b57000-7b87c57000 rw-p 00000000 00:00 0                              [anon:dalvik-allocspace zygote / non moving space live-bitmap 0]
7b87c57000-7b87cc8000 r--p 00000000 fd:05 30703605                       /system/framework/hwperf.jar
7b87cc8000-7b87f92000 r--p 00000000 fd:05 30540624                       /system/framework/hwframework.jar
7b87f92000-7b87ff3000 r--p 00000000 fd:05 30704758                       /system/framework/hwPartDeviceVirtualization.jar
7b87ff3000-7b88150000 r--p 00000000 fd:05 30717667                       /system/framework/hwPartSecurity.jar
7b88150000-7b88249000 r--p 00000000 fd:05 30699746                       /system/framework/hwPartTelephonyOpt.jar
7b88249000-7b88598000 r--p 00000000 fd:05 30676807                       /system/framework/hwEmui.jar
7b88598000-7b8869b000 r--p 00000000 fd:05 36412139                       /system/framework/featurelayer-widget.jar
7b8869b000-7b889d3000 r--p 00000000 fd:05 32955722                       /system/framework/telephony-common.jar
7b889d3000-7b88ac1000 r--p 00000000 fd:05 30419804                       /system/framework/ext.jar
7b88ac1000-7b88cee000 r--p 01c80000 fd:05 38214656                       /system/framework/framework.jar
7b88cee000-7b896ff000 r--p 01270000 fd:05 38214656                       /system/framework/framework.jar
7b896ff000-7b8a024000 r--p 0094c000 fd:05 38214656                       /system/framework/framework.jar
7b8a024000-7b8a971000 r--p 00000000 fd:05 38214656                       /system/framework/framework.jar
7b8a971000-7b8aa99000 r--p 00000000 07:07 40                             /apex/com.android.runtime/javalib/apache-xml.jar
7b8aa99000-7b8abef000 r--p 00000000 07:07 41                             /apex/com.android.runtime/javalib/bouncycastle.jar
7b8abef000-7b8af17000 r--p 00004000 07:07 42                             /apex/com.android.runtime/javalib/core-libart.jar
7b8af17000-7b8b3d0000 r--p 00000000 07:07 43                             /apex/com.android.runtime/javalib/core-oj.jar
7b8b3d0000-7b8b50e000 r--p 00000000 07:07 111                            /apex/com.android.runtime/lib64/libart.so
7b8b50e000-7b8b9c1000 --xp 0013e000 07:07 111                            /apex/com.android.runtime/lib64/libart.so
7b8b9c1000-7b8b9c4000 rw-p 005f1000 07:07 111                            /apex/com.android.runtime/lib64/libart.so
7b8b9c4000-7b8b9d5000 r--p 005f4000 07:07 111                            /apex/com.android.runtime/lib64/libart.so
7b8b9d5000-7b8b9d9000 rw-p 00000000 00:00 0                              [anon:.bss]
7b8b9dc000-7b8ba00000 r--s 00000000 fd:05 78402939                       /system/usr/hyphen-data/hyph-nb.hyb
...... 省了很多行打印

通過上面的打印,so 的路徑地址我們一眼肯定能看出來偶垮,但是哪個是基地址呢张咳?這就得去研究一下了帝洪,或者查查資料啥的:

起始地址 - 結束地址      屬性  偏移    設備號 inode號    映射的文件名(詳細的描述見下表)
7b8abef000-7b8af17000 r--p 00004000 07:07 42         /apex/com.android.runtime/javalib/core-libart.jar
7b8af17000-7b8b3d0000 r--p 00000000 07:07 43         /apex/com.android.runtime/javalib/core-oj.jar
7b8b3d0000-7b8b50e000 r--p 00000000 07:07 111        /apex/com.android.runtime/lib64/libart.so
7b8b50e000-7b8b9c1000 --xp 0013e000 07:07 111        /apex/com.android.runtime/lib64/libart.so
7b8b9c1000-7b8b9c4000 rw-p 005f1000 07:07 111        /apex/com.android.runtime/lib64/libart.so
7b8b9c4000-7b8b9d5000 r--p 005f4000 07:07 111        /apex/com.android.runtime/lib64/libart.so
void core_dhook(const char *pathname, uintptr_t base_addr, const char *symbol, void *new_func){
    LOGE("2. 解析 elf 找到要 hook 的函數(shù)的地址,替換成我們自己指定的函數(shù)地址");
}

void dhook(const char *pathname_regex_str, const char *symbol, void *new_func)
{
    // 1. 讀取 /proc/self/maps 內(nèi)容脚猾,找到 so 文件路徑和基地址
    FILE* maps_fp = fopen("/proc/self/maps", "r");
    if (maps_fp == NULL) {
        LOGE("open /proc/self/maps error");
    }
    char                     line[512];
    char                     permission[5];
    uintptr_t                base_addr;
    unsigned long            offset;
    int                      pathname_pos;
    char                    *pathname;
    size_t                   pathname_len;
    regex_t                  path_name_regex;

    regcomp(&path_name_regex, pathname_regex_str, REG_NOSUB);

    while (fgets(line, sizeof(line), maps_fp)) {
        if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, permission, &offset, &pathname_pos) != 3) continue;

        // 異常情況的判斷
        if(permission[0] != 'r') continue;
        if(permission[3] != 'p') continue;
        if(0 != offset) continue;

        // 空格去掉
        while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
            pathname_pos += 1;
        if(pathname_pos >= (int)(sizeof(line) - 1)) continue;

        // 獲取成這樣 /system/framework/arm64/boot-core-libart.art\n
        pathname = line + pathname_pos;
        pathname_len = strlen(pathname);
        if(0 == pathname_len) continue;
        // 獲取成這樣 /system/framework/arm64/boot-core-libart.art
        if(pathname[pathname_len - 1] == '\n')
        {
            pathname[pathname_len - 1] = '\0';
            pathname_len -= 1;
        }
        // 異常情況判斷
        if(0 == pathname_len) continue;
        if('[' == pathname[0]) continue;

        // 正則匹配
        if(0 == regexec(&path_name_regex, pathname, 0, NULL, 0))
        {
            core_dhook(pathname, base_addr, symbol, new_func);
        }
    }
}

2. 解析 elf 找到要 hook 的函數(shù)的地址葱峡,替換成指定的函數(shù)地址

void core_dhook(uintptr_t base_addr, const char *symbol, void *new_func){
    // 2.1. 獲取計算 program_header_table 的地址
    ElfW(Ehdr) *elf_header = base_addr;
    ElfW(Phdr) *elf_program_header_table = base_addr + elf_header->e_phoff;
    // 2.2. 遍歷 elf_program_header_table
    int program_header_table_length = elf_header->e_phnum;
    uintptr_t dyn_address = 0;
    unsigned int dyn_memory_size = 0;
    for (int i = 0; i < program_header_table_length; ++i) {
        if(elf_program_header_table[i].p_type == PT_DYNAMIC){
            dyn_address = elf_program_header_table[i].p_vaddr + base_addr;
            dyn_memory_size = elf_program_header_table[i].p_memsz;
            break;
        }
    }
    // 2.3 找出 rel.plt 表的位置
    ElfW(Dyn) *elf_dynamic_table = dyn_address;
    int dynamic_count = dyn_memory_size / sizeof(ElfW(Dyn));
    ElfW(Dyn) *elf_dynamic_table_end = elf_dynamic_table + dynamic_count;
    uintptr_t     elf_rel_address;
    uintptr_t     sys_tab_address;
    uintptr_t     str_tab_address;
    unsigned long  elf_rel_size;

    for (; elf_dynamic_table < elf_dynamic_table_end; elf_dynamic_table++) {
        switch (elf_dynamic_table->d_tag) {
            case DT_NULL:
                elf_dynamic_table == elf_dynamic_table_end;
                break;
            case DT_JMPREL:
                elf_rel_address = elf_dynamic_table->d_un.d_ptr;
                break;
            case DT_PLTRELSZ:
                elf_rel_size = elf_dynamic_table->d_un.d_val / sizeof(ElfW(Rela));
                break;
            case DT_SYMTAB:
                sys_tab_address = elf_dynamic_table->d_un.d_ptr;
                break;
            case DT_STRTAB:
                str_tab_address = elf_dynamic_table->d_un.d_ptr;
                break;
        }
    }
    // 2.4 遍歷 rel.plt 表,匹配 symbol 只能通過 sys_table -> str_tab
    ElfW(Rela) *elf_rel_table = (elf_rel_address + base_addr);

    for (int i = 0; i < elf_rel_size; ++i) {
        int sys_tab_index = ELF_R_SYM(elf_rel_table[i].r_info);
        ElfW(Sym) *sys_table = (sys_tab_index * sizeof(ElfW(Sym)) + sys_tab_address + base_addr);
        char* fun_name = (char *) (sys_table->st_name + str_tab_address + base_addr);
        if (memcmp(fun_name, symbol, strlen(symbol)) == 0) {
            uintptr_t mem_page_start = elf_rel_table[i].r_offset + base_addr;
            // 調(diào)整目標內(nèi)存區(qū)域的權限
            mprotect(PAGE_START(mem_page_start), getpagesize(), PROT_READ | PROT_WRITE);
            // 2.5 替換掉地址
            *(void **) (elf_rel_table[i].r_offset + base_addr) = new_func;
            // 清除指令緩存
            __builtin___clear_cache((void *) PAGE_START(mem_page_start), (void *) PAGE_END(mem_page_start));
        }
    }
}

視頻鏈接:https://pan.baidu.com/s/1O5M7oumrpTVBnda8X88VlQ
視頻密碼:kdtd

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末龙助,一起剝皮案震驚了整個濱河市砰奕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌提鸟,老刑警劉巖军援,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異称勋,居然都是意外死亡胸哥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門赡鲜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烘嘱,“玉大人,你說我怎么就攤上這事蝗蛙。” “怎么了醉鳖?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵捡硅,是天一觀的道長。 經(jīng)常有香客問我盗棵,道長壮韭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任纹因,我火速辦了婚禮喷屋,結果婚禮上,老公的妹妹穿的比我還像新娘瞭恰。我一直安慰自己屯曹,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布惊畏。 她就那樣靜靜地躺著恶耽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颜启。 梳的紋絲不亂的頭發(fā)上偷俭,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音缰盏,去河邊找鬼涌萤。 笑死淹遵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的负溪。 我是一名探鬼主播透揣,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笙以!你這毒婦竟也來了淌实?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤猖腕,失蹤者是張志新(化名)和其女友劉穎拆祈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倘感,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡放坏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了老玛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淤年。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜡豹,靈堂內(nèi)的尸體忽然破棺而出麸粮,到底是詐尸還是另有隱情,我是刑警寧澤镜廉,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布弄诲,位于F島的核電站,受9級特大地震影響娇唯,放射性物質(zhì)發(fā)生泄漏齐遵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一塔插、第九天 我趴在偏房一處隱蔽的房頂上張望梗摇。 院中可真熱鬧,春花似錦想许、人聲如沸伶授。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谎砾。三九已至,卻和暖如春捧颅,著一層夾襖步出監(jiān)牢的瞬間景图,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工碉哑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挚币,地道東北人亮蒋。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像妆毕,于是被迫代替她去往敵國和親慎玖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容