【Android 逆向】函数拦截实例 ( ③ 刷新 CPU 高速缓存 | ④ 处理拦截函数 | ⑤ 返回特定结果 )
文章目錄
- 前言
- 一、刷新 CPU 高速緩存
- 二、處理攔截函數
- 1、樁函數
- 2、處理攔截函數
- 三、返回特定結果
- 四、相關完整代碼
前言
【Android 逆向】函數攔截實例 ( 函數攔截流程 | ① 定位動態庫及函數位置 ) 博客中簡單介紹了 hook 函數 ( 函數攔截 ) 的流程 , 本系列博客介紹函數攔截實例 ;
攔截 clock_gettime 函數 ;
#include <time.h> int clock_gettime(clockid_t clk_id,struct timespec *tp);【Android 逆向】函數攔截實例 ( ② 插樁操作 | 保存實際函數入口 6 字節數據 | 在插樁的函數入口寫入跳轉指令 | 構造拼接樁函數 ) 博客中進行了插樁操作 ,
一、刷新 CPU 高速緩存
執行 cache_flush 系統調用函數 刷新 CPU 的高速緩存 ; 該步驟 只在 ARM 架構的 CPU 中執行 , x86 架構的 CPU 不需要刷新緩存 ;
x86 不需要執行刷新緩存操作 , 但也可以執行系統調用操作 syscall 來刷新緩存 ;
刷新 CPU 高速緩存 代碼示例 : pApi 是實際調用的函數指針 , size 是 6 字節 , 也就是說刷新 (int)pApi 地址到 (int)pApi + size 之間 666 字節對應的 CPU 高速緩存即可 ;
/* 清空 CPU 高速緩存 */ #if !defined(__i386__)/* 在 arm 架構中必須刷新 CPU 高速緩存 , x86 不需要執行 */cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE); #else/* x86 下可以執行該系統調用 */syscall(0xF002, (int)pApi,(int)pApi + sizeE);二、處理攔截函數
1、樁函數
在 【Android 逆向】函數攔截實例 ( ② 插樁操作 | 保存實際函數入口 6 字節數據 | 在插樁的函數入口寫入跳轉指令 | 構造拼接樁函數 ) 三、在插樁的函數入口寫入跳轉指令 | 構造拼接樁函數 博客章節 , 介紹了拼接裝函數 do_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 個字節被覆蓋為 跳轉指令了 ;
2、處理攔截函數
處理攔截函數 :
當函數執行到 clock_gettime 之后 , 就會執行插入的跳轉指令 , 跳轉到 dn_clock_gettime 函數中 ;
在該函數中 , 可以調用 do_clock_gettime 函數 , 執行原有的指令 ;
do_clock_gettime 函數執行前后 , 都可以插入自己的業務邏輯 , 監控或修改都可以 ;
處理攔截函數 代碼示例 :
/* 攔截函數 , 攔截 clock_gettime 函數后 , 跳轉到此處 */ int dn_clock_gettime(clockid_t id, struct timespec* ts) {/*if (ts == NULL) {return -1;}*//* 如果設備實現了系統調用 , 可以通過該代碼調用原有的 clock_gettime 函數 *///int ret = syscall(__NR_clock_gettime, id, ts);/* 此處實際上調用的是原有的 clock_gettime 函數 如果設備上沒有實現系統調用 , 使用如下方法可以調用原有的 clock_gettime 函數 */do_clock_gettime(id, ts);/* clock_gettime 函數執行完后 , 繼續執行一些自己實現的部分 *//* 上面的代碼是 hook 住的真實代碼 我們可以在真實代碼 前面 / 后面 執行一些自定義內容 */if (id > CLOCK_MONOTONIC)return 0;if (clock_base[id] == 0.0) {clock_base[id] = ts->tv_sec * 1000000000.0 + ts->tv_nsec;clock_new[id] = clock_base[id];}else {//mutex.lock();double tick = ts->tv_sec * 1000000000.0 + ts->tv_nsec;//printf("tick : %f base: %f delta: %f\n", tick, clock_base[id], tick - clock_base[id]);if (tick > clock_base[id]) {clock_new[id] += (tick - clock_base[id]) * time_scale;ts->tv_sec = (time_t)(clock_new[id] / 1000000000.0);ts->tv_nsec = (long)(fmod(clock_new[id], 1000000000.0));clock_base[id] = tick;}//mutex.unlock();}return 0; }三、返回特定結果
執行上述 dn_clock_gettime 函數的返回值 , 就是最終的返回結果 ;
四、相關完整代碼
下面是相關代碼 , 只是逆向代碼中的函數攔截部分代碼 :
調用代碼 :
/* 這是 hook 標準庫中的 clock_gettime 函數的入口方法 , 跳轉到自定義的 dn_clock_gettime 方法中 */ hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6);函數攔截代碼 :
/* hook 函數的完整流程 , 跳轉指令 size 是 6 字節*/ /* 這是 hook 標準庫中的 clock_gettime 函數的入口方法 , 跳轉到自定義的 dn_clock_gettime 方法中 */ /* hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6); */ void hook_func(uint8_t* pApi, uint8_t* pUser, uint8_t* pStub, size_t size) {unsigned char code[64] = { 0 };/* 插樁前先保存函數的入口 6 字節數據 , 因為之后插樁 , * 會使用跳轉代碼 0xE9,0,0,0,0 覆蓋函數入口內存* 該函數最終還是要執行 , 需要拷貝一下 , 供之后實際函數調用使用 */memcpy(code, pApi, size);/* 函數插樁 , 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 方法 , 就相當于調用了*/write_code(size + pStub, size + pApi);/* 將復制的 6 字節 代碼存放到 pStub 函數中的 0 ~ 6 字節位置 */memcpy(pStub, code, size);/* 清空 CPU 高速緩存 */ #if !defined(__i386__)/* 在 arm 架構中必須刷新 CPU 高速緩存 , x86 不需要執行 */cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE); #else/* x86 下可以執行該系統調用 */syscall(0xF002, (int)pApi,(int)pApi + sizeE); #endif }/** 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; }/* 攔截函數 , 攔截 clock_gettime 函數后 , 跳轉到此處 */ int dn_clock_gettime(clockid_t id, struct timespec* ts) {/*if (ts == NULL) {return -1;}*//* 如果設備實現了系統調用 , 可以通過該代碼調用原有的 clock_gettime 函數 *///int ret = syscall(__NR_clock_gettime, id, ts);/* 此處實際上調用的是原有的 clock_gettime 函數 如果設備上沒有實現系統調用 , 使用如下方法可以調用原有的 clock_gettime 函數 */do_clock_gettime(id, ts);/* clock_gettime 函數執行完后 , 繼續執行一些自己實現的部分 *//* 上面的代碼是 hook 住的真實代碼 我們可以在真實代碼 前面 / 后面 執行一些自定義內容 */if (id > CLOCK_MONOTONIC)return 0;if (clock_base[id] == 0.0) {clock_base[id] = ts->tv_sec * 1000000000.0 + ts->tv_nsec;clock_new[id] = clock_base[id];}else {//mutex.lock();double tick = ts->tv_sec * 1000000000.0 + ts->tv_nsec;//printf("tick : %f base: %f delta: %f\n", tick, clock_base[id], tick - clock_base[id]);if (tick > clock_base[id]) {clock_new[id] += (tick - clock_base[id]) * time_scale;ts->tv_sec = (time_t)(clock_new[id] / 1000000000.0);ts->tv_nsec = (long)(fmod(clock_new[id], 1000000000.0));clock_base[id] = tick;}//mutex.unlock();}return 0; }int do_clock_gettime(clockid_t which_clock, struct timespec* tp) {//未使用,內核中該函數實際的代碼,反匯編libc.so得到/*這些指令都不重要 都會被覆蓋調 , 寫的這些指令主要是占坑用的 實際上調用的是 clock_gettime 函數 下面的匯編代碼都會被覆蓋為 跳轉代碼 , 跳轉到 clock_gettime 函數 , 注意 , clock_gettime 函數 的前 6 字節的指令會被拷貝到函數入口 , 執行第 6 字節位置時 , 跳轉到 clock_gettime 函數 的第 6 字節位置*///return syscall(__NR_clock_gettime, which_clock, tp);__asm__ __volatile__("push %%ebx"::: "ebx");__asm__ __volatile__("push %%ecx\n"::: "ecx");__asm__ __volatile__("mov 12(%%esp),%%ebx"::: "ebx");__asm__ __volatile__("mov 16(%%esp),%%ecx"::: "ecx");__asm__ __volatile__("mov $0x109,%%eax"::: "eax");__asm__ __volatile__("int $0x80");__asm__ __volatile__("pop %%ecx":::"ecx");__asm__ __volatile__("pop %%ebx":::"ebx");__asm__ __volatile__("retn"); }總結
以上是生活随笔為你收集整理的【Android 逆向】函数拦截实例 ( ③ 刷新 CPU 高速缓存 | ④ 处理拦截函数 | ⑤ 返回特定结果 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 逆向】函数拦截实例 (
- 下一篇: 【Android 逆向】Frida 框架