【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 )
文章目錄
- 前言
- 一、函數攔截需要的幾個參數
- 二、插樁前先保存實際函數入口 6 字節數據
- 三、在插樁的函數入口寫入跳轉指令 | 構造拼接樁函數
前言
【Android 逆向】函數攔截實例 ( 函數攔截流程 | ① 定位動態庫及函數位置 ) 博客中簡單介紹了 hook 函數 ( 函數攔截 ) 的流程 , 本篇博客開始介紹函數攔截實例 ;
攔截 clock_gettime 函數 ;
#include <time.h> int clock_gettime(clockid_t clk_id,struct timespec *tp);一、函數攔截需要的幾個參數
定義 hook_func 函數 , 執行 C/C++ 函數的 hook 操作 ;
void hook_func(uint8_t* pApi, uint8_t* pUser, uint8_t* pStub, size_t size)上述函數的 444 個參數含義如下 :
- uint8_t* pApi 參數 : 要攔截的實際函數 , int clock_gettime(clockid_t clk_id,struct timespec *tp); 函數 ;
- uint8_t* pUser 參數 : 攔截函數后 , 跳轉到的 dn_clock_gettime 函數 ;
- uint8_t* pStub 參數 : 定義的 do_clock_gettime 樁代碼 , 將 pApi 函數的前 6 字節拷貝到該 pStub 函數入口 , 然后跳轉到 pApi 函數的第 666 字節開始執行 , 相當于調用了 uint8_t* pApi 參數對應的實際函數 , 即 int clock_gettime(clockid_t clk_id,struct timespec *tp); 函數 ;
- size_t size 參數 : 跳轉指令占 0xE9,0,0,0,0 555 字節 , 這里 將函數入口前 666 字節保存下來 ;
函數調用實例 :
/* 這是 hook 標準庫中的 clock_gettime 函數的入口方法 , 跳轉到自定義的 dn_clock_gettime 方法中 */ hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6);二、插樁前先保存實際函數入口 6 字節數據
插樁前先 保存函數的入口 6 字節數據 , 因為之后插樁 , 會使用跳轉代碼 0xE9,0,0,0,0 覆蓋函數入口內存 , 被破壞的實際函數 最終還是要執行 , 需要拷貝一下 , 供之后實際函數調用使用 ;
unsigned char code[64] = { 0 };/* 插樁前先保存函數的入口 6 字節數據 , 因為之后插樁 , * 會使用跳轉代碼 0xE9,0,0,0,0 覆蓋函數入口內存* 該函數最終還是要執行 , 需要拷貝一下 , 供之后實際函數調用使用 */memcpy(code, pApi, size);三、在插樁的函數入口寫入跳轉指令 | 構造拼接樁函數
這里執行了 222 次插樁操作 :
- 第一次是實際函數跳轉 : 函數插樁 , pApi 是實際函數 , pUser 是插樁后跳轉到的攔截函數 ; 該情況是在 clock_gettime 函數的入口處插入跳轉代碼 , 跳轉到 dn_clock_gettime 函數位置 ;
- 第二次是構造樁函數 ( 構造拼接樁函數 ) : 在自定義的 dn_clock_gettime 函數中 , 需要調用實際的 clock_gettime 函數 , 這里將 do_clock_gettime 函數構造成 clock_gettime 函數 ;
構造拼接樁函數 : 前 6 字節是保存下來的 clock_gettime 函數的前 6 字節指令 , 執行到第 6 字節時 , 直接跳轉到 clock_gettime 函數 執行 , 這樣執行拼接的函數 等同于執行 clock_gettime 函數 ;
將 do_clock_gettime 函數構造成 clock_gettime 函數流程 : 執行 do_clock_gettime 方法的第 6 字節的指令時 , 跳轉到 clock_gettime 函數的第 6 字節指令位置 , do_clock_gettime 的 0 ~ 6 字節指令是 clock_gettime 實際函數的前 6 字節 , 之所以這么定義 , 是因為 clock_gettime 的前 6 個字節被覆蓋為 跳轉指令了 ;
調用 do_clock_gettime 方法 , 就相當于調用了 clock_gettime 方法 ;
/* 函數插樁 , pApi 是實際函數 , pUser 是插樁后跳轉到的攔截函數 */write_code(pApi, pUser);/* 執行 size + pStub 位置的指令時 , 直接跳轉到 size + pApi 位置如 : 執行 do_clock_gettime 方法的第 6 字節的指令時 , 跳轉到 clock_gettime 函數的第 6 字節指令位置 do_clock_gettime 的 0 ~ 6 字節指令是 clock_gettime 實際函數的前 6 字節 , 之所以這么定義 , 是因為 clock_gettime 的前 6 個字節被覆蓋為 跳轉指令了調用 do_clock_gettime 方法 , 就相當于調用了 clock_gettime 方法 ; */write_code(size + pStub, size + pApi);/* 將復制的 6 字節 代碼存放到 pStub 函數中的 0 ~ 6 字節位置 */memcpy(pStub, code, size);函數插樁的具體細節在之前的
- 【Android 逆向】函數攔截 ( 修改內存頁屬性 | x86 架構插樁攔截 )
- 【Android 逆向】函數攔截 ( ARM 架構下的插樁攔截 | 完整代碼示例 )
博客中有詳細的說明 , 先修改內存頁屬性 , 然后直接修改內存 , 寫入跳轉匯編指令對應的二進制機器碼數據 ;
代碼示例 :
/** unsigned char* pFunc* unsigned char* pStub* 上述兩個參數分別是兩個函數指針* * 注意 : 寫完之后要刷新 CPU 高速緩存 , 調用 cache_flush 系統調用函數*/ int write_code(unsigned char* pFunc, unsigned char* pStub) {/* 獲取 pFunc 函數入口 , 先獲取該函數所在內存頁地址 */void* pBase = (void*)(0xFFFFF000 & (int)pFunc);/* 修改整個內存頁屬性 , 修改為 可讀 | 可寫 | 可執行 , * 避免因為內存訪問權限問題導致操作失敗* mprotect 函數只能對整個頁內存的屬性進行修改 * 每個 內存頁 大小都是 4KB */int ret = mprotect(pBase, 0x1000, PROT_WRITE | PROT_READ | PROT_EXEC);/* 修改內存頁屬性失敗的情況 */if (ret == -1) {perror("mprotect:");return -1;} #if defined(__i386__) // arm 情況處理/* E9 是 JMP 無條件跳轉指令 , 后面 4 字節是跳轉的地址 */unsigned char code[] = { 0xE9,0,0,0,0 };/* 計算 pStub 函數跳轉地址 , 目標函數 pStub 地址 - 當前函數 pFunc 地址 - 5 * 跳轉指令 跳轉的是 偏移量 , 不是絕對地址值*/*(unsigned*)(code + 1) = pStub - pFunc - 5;/* 將跳轉代碼拷貝到 pFunc 地址處 , 這是 pFunc 函數的入口地址 */memcpy(pFunc, code, sizeof(code)); #else // arm 情況處理/* B 無條件跳轉指令 */unsigned char code[] = { 0x04,0xF0,0x1F,0xE5,0x00,0x00,0x00,0x00 };/* arm 的跳轉是絕對地址跳轉 , 傳入 pStub 函數指針即可 */*(unsigned*)(code + 4) = (unsigned)pStub;/* 將機器碼復制到函數開始位置 */memcpy(pFunc, code, sizeof(code)); #endifreturn 0; }總結
以上是生活随笔為你收集整理的【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【错误记录】Visual Studio
- 下一篇: 【Android 逆向】函数拦截实例 (