【Android 逆向】函数拦截实例 ( ③ 刷新 CPU 高速缓存 | ④ 处理拦截函数 | ⑤ 返回特定结果 )
文章目錄
- 前言
- 一、刷新 CPU 高速緩存
- 二、處理攔截函數(shù)
- 1、樁函數(shù)
- 2、處理攔截函數(shù)
- 三、返回特定結(jié)果
- 四、相關(guān)完整代碼
前言
【Android 逆向】函數(shù)攔截實例 ( 函數(shù)攔截流程 | ① 定位動態(tài)庫及函數(shù)位置 ) 博客中簡單介紹了 hook 函數(shù) ( 函數(shù)攔截 ) 的流程 , 本系列博客介紹函數(shù)攔截實例 ;
攔截 clock_gettime 函數(shù) ;
#include <time.h> int clock_gettime(clockid_t clk_id,struct timespec *tp);【Android 逆向】函數(shù)攔截實例 ( ② 插樁操作 | 保存實際函數(shù)入口 6 字節(jié)數(shù)據(jù) | 在插樁的函數(shù)入口寫入跳轉(zhuǎn)指令 | 構(gòu)造拼接樁函數(shù) ) 博客中進行了插樁操作 ,
一、刷新 CPU 高速緩存
執(zhí)行 cache_flush 系統(tǒng)調(diào)用函數(shù) 刷新 CPU 的高速緩存 ; 該步驟 只在 ARM 架構(gòu)的 CPU 中執(zhí)行 , x86 架構(gòu)的 CPU 不需要刷新緩存 ;
x86 不需要執(zhí)行刷新緩存操作 , 但也可以執(zhí)行系統(tǒng)調(diào)用操作 syscall 來刷新緩存 ;
刷新 CPU 高速緩存 代碼示例 : pApi 是實際調(diào)用的函數(shù)指針 , size 是 6 字節(jié) , 也就是說刷新 (int)pApi 地址到 (int)pApi + size 之間 666 字節(jié)對應的 CPU 高速緩存即可 ;
/* 清空 CPU 高速緩存 */ #if !defined(__i386__)/* 在 arm 架構(gòu)中必須刷新 CPU 高速緩存 , x86 不需要執(zhí)行 */cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE); #else/* x86 下可以執(zhí)行該系統(tǒng)調(diào)用 */syscall(0xF002, (int)pApi,(int)pApi + sizeE);二、處理攔截函數(shù)
1、樁函數(shù)
在 【Android 逆向】函數(shù)攔截實例 ( ② 插樁操作 | 保存實際函數(shù)入口 6 字節(jié)數(shù)據(jù) | 在插樁的函數(shù)入口寫入跳轉(zhuǎn)指令 | 構(gòu)造拼接樁函數(shù) ) 三、在插樁的函數(shù)入口寫入跳轉(zhuǎn)指令 | 構(gòu)造拼接樁函數(shù) 博客章節(jié) , 介紹了拼接裝函數(shù) do_clock_gettime 函數(shù) , 實現(xiàn)了調(diào)用 do_clock_gettime 函數(shù) 與調(diào)用 clock_gettime 函數(shù)相同的效果 ;
構(gòu)造拼接樁函數(shù) : 前 6 字節(jié)是保存下來的 clock_gettime 函數(shù)的前 6 字節(jié)指令 , 執(zhí)行到第 6 字節(jié)時 , 直接跳轉(zhuǎn)到 clock_gettime 函數(shù) 執(zhí)行 , 這樣執(zhí)行拼接的函數(shù) 等同于執(zhí)行 clock_gettime 函數(shù) ;
將 do_clock_gettime 函數(shù)構(gòu)造成 clock_gettime 函數(shù)流程 : 執(zhí)行 do_clock_gettime 方法的第 6 字節(jié)的指令時 , 跳轉(zhuǎn)到 clock_gettime 函數(shù)的第 6 字節(jié)指令位置 , do_clock_gettime 的 0 ~ 6 字節(jié)指令是 clock_gettime 實際函數(shù)的前 6 字節(jié) , 之所以這么定義 , 是因為 clock_gettime 的前 6 個字節(jié)被覆蓋為 跳轉(zhuǎn)指令了 ;
2、處理攔截函數(shù)
處理攔截函數(shù) :
當函數(shù)執(zhí)行到 clock_gettime 之后 , 就會執(zhí)行插入的跳轉(zhuǎn)指令 , 跳轉(zhuǎn)到 dn_clock_gettime 函數(shù)中 ;
在該函數(shù)中 , 可以調(diào)用 do_clock_gettime 函數(shù) , 執(zhí)行原有的指令 ;
do_clock_gettime 函數(shù)執(zhí)行前后 , 都可以插入自己的業(yè)務邏輯 , 監(jiān)控或修改都可以 ;
處理攔截函數(shù) 代碼示例 :
/* 攔截函數(shù) , 攔截 clock_gettime 函數(shù)后 , 跳轉(zhuǎn)到此處 */ int dn_clock_gettime(clockid_t id, struct timespec* ts) {/*if (ts == NULL) {return -1;}*//* 如果設備實現(xiàn)了系統(tǒng)調(diào)用 , 可以通過該代碼調(diào)用原有的 clock_gettime 函數(shù) *///int ret = syscall(__NR_clock_gettime, id, ts);/* 此處實際上調(diào)用的是原有的 clock_gettime 函數(shù) 如果設備上沒有實現(xiàn)系統(tǒng)調(diào)用 , 使用如下方法可以調(diào)用原有的 clock_gettime 函數(shù) */do_clock_gettime(id, ts);/* clock_gettime 函數(shù)執(zhí)行完后 , 繼續(xù)執(zhí)行一些自己實現(xiàn)的部分 *//* 上面的代碼是 hook 住的真實代碼 我們可以在真實代碼 前面 / 后面 執(zhí)行一些自定義內(nèi)容 */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; }三、返回特定結(jié)果
執(zhí)行上述 dn_clock_gettime 函數(shù)的返回值 , 就是最終的返回結(jié)果 ;
四、相關(guān)完整代碼
下面是相關(guān)代碼 , 只是逆向代碼中的函數(shù)攔截部分代碼 :
調(diào)用代碼 :
/* 這是 hook 標準庫中的 clock_gettime 函數(shù)的入口方法 , 跳轉(zhuǎn)到自定義的 dn_clock_gettime 方法中 */ hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6);函數(shù)攔截代碼 :
/* hook 函數(shù)的完整流程 , 跳轉(zhuǎn)指令 size 是 6 字節(jié)*/ /* 這是 hook 標準庫中的 clock_gettime 函數(shù)的入口方法 , 跳轉(zhuǎn)到自定義的 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 };/* 插樁前先保存函數(shù)的入口 6 字節(jié)數(shù)據(jù) , 因為之后插樁 , * 會使用跳轉(zhuǎn)代碼 0xE9,0,0,0,0 覆蓋函數(shù)入口內(nèi)存* 該函數(shù)最終還是要執(zhí)行 , 需要拷貝一下 , 供之后實際函數(shù)調(diào)用使用 */memcpy(code, pApi, size);/* 函數(shù)插樁 , pApi 是實際函數(shù) , pUser 是插樁后跳轉(zhuǎn)到的攔截函數(shù) */write_code(pApi, pUser);/* 執(zhí)行 size + pStub 位置的指令時 , 直接跳轉(zhuǎn)到 size + pApi 位置如 : 執(zhí)行 do_clock_gettime 方法的第 6 字節(jié)的指令時 , 跳轉(zhuǎn)到 clock_gettime 函數(shù)的第 6 字節(jié)指令位置 do_clock_gettime 的 0 ~ 6 字節(jié)指令是 clock_gettime 實際函數(shù)的前 6 字節(jié) , 之所以這么定義 , 是因為 clock_gettime 的前 6 個字節(jié)被覆蓋為 跳轉(zhuǎn)指令了調(diào)用 do_clock_gettime 方法 , 就相當于調(diào)用了*/write_code(size + pStub, size + pApi);/* 將復制的 6 字節(jié) 代碼存放到 pStub 函數(shù)中的 0 ~ 6 字節(jié)位置 */memcpy(pStub, code, size);/* 清空 CPU 高速緩存 */ #if !defined(__i386__)/* 在 arm 架構(gòu)中必須刷新 CPU 高速緩存 , x86 不需要執(zhí)行 */cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE); #else/* x86 下可以執(zhí)行該系統(tǒng)調(diào)用 */syscall(0xF002, (int)pApi,(int)pApi + sizeE); #endif }/** unsigned char* pFunc* unsigned char* pStub* 上述兩個參數(shù)分別是兩個函數(shù)指針* * 注意 : 寫完之后要刷新 CPU 高速緩存 , 調(diào)用 cache_flush 系統(tǒng)調(diào)用函數(shù)*/ int write_code(unsigned char* pFunc, unsigned char* pStub) {/* 獲取 pFunc 函數(shù)入口 , 先獲取該函數(shù)所在內(nèi)存頁地址 */void* pBase = (void*)(0xFFFFF000 & (int)pFunc);/* 修改整個內(nèi)存頁屬性 , 修改為 可讀 | 可寫 | 可執(zhí)行 , * 避免因為內(nèi)存訪問權(quán)限問題導致操作失敗* mprotect 函數(shù)只能對整個頁內(nèi)存的屬性進行修改 * 每個 內(nèi)存頁 大小都是 4KB */int ret = mprotect(pBase, 0x1000, PROT_WRITE | PROT_READ | PROT_EXEC);/* 修改內(nèi)存頁屬性失敗的情況 */if (ret == -1) {perror("mprotect:");return -1;} #if defined(__i386__) // arm 情況處理/* E9 是 JMP 無條件跳轉(zhuǎn)指令 , 后面 4 字節(jié)是跳轉(zhuǎn)的地址 */unsigned char code[] = { 0xE9,0,0,0,0 };/* 計算 pStub 函數(shù)跳轉(zhuǎn)地址 , 目標函數(shù) pStub 地址 - 當前函數(shù) pFunc 地址 - 5 * 跳轉(zhuǎn)指令 跳轉(zhuǎn)的是 偏移量 , 不是絕對地址值*/*(unsigned*)(code + 1) = pStub - pFunc - 5;/* 將跳轉(zhuǎn)代碼拷貝到 pFunc 地址處 , 這是 pFunc 函數(shù)的入口地址 */memcpy(pFunc, code, sizeof(code)); #else // arm 情況處理/* B 無條件跳轉(zhuǎn)指令 */unsigned char code[] = { 0x04,0xF0,0x1F,0xE5,0x00,0x00,0x00,0x00 };/* arm 的跳轉(zhuǎn)是絕對地址跳轉(zhuǎn) , 傳入 pStub 函數(shù)指針即可 */*(unsigned*)(code + 4) = (unsigned)pStub;/* 將機器碼復制到函數(shù)開始位置 */memcpy(pFunc, code, sizeof(code)); #endifreturn 0; }/* 攔截函數(shù) , 攔截 clock_gettime 函數(shù)后 , 跳轉(zhuǎn)到此處 */ int dn_clock_gettime(clockid_t id, struct timespec* ts) {/*if (ts == NULL) {return -1;}*//* 如果設備實現(xiàn)了系統(tǒng)調(diào)用 , 可以通過該代碼調(diào)用原有的 clock_gettime 函數(shù) *///int ret = syscall(__NR_clock_gettime, id, ts);/* 此處實際上調(diào)用的是原有的 clock_gettime 函數(shù) 如果設備上沒有實現(xiàn)系統(tǒng)調(diào)用 , 使用如下方法可以調(diào)用原有的 clock_gettime 函數(shù) */do_clock_gettime(id, ts);/* clock_gettime 函數(shù)執(zhí)行完后 , 繼續(xù)執(zhí)行一些自己實現(xiàn)的部分 *//* 上面的代碼是 hook 住的真實代碼 我們可以在真實代碼 前面 / 后面 執(zhí)行一些自定義內(nèi)容 */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) {//未使用,內(nèi)核中該函數(shù)實際的代碼,反匯編libc.so得到/*這些指令都不重要 都會被覆蓋調(diào) , 寫的這些指令主要是占坑用的 實際上調(diào)用的是 clock_gettime 函數(shù) 下面的匯編代碼都會被覆蓋為 跳轉(zhuǎn)代碼 , 跳轉(zhuǎn)到 clock_gettime 函數(shù) , 注意 , clock_gettime 函數(shù) 的前 6 字節(jié)的指令會被拷貝到函數(shù)入口 , 執(zhí)行第 6 字節(jié)位置時 , 跳轉(zhuǎn)到 clock_gettime 函數(shù) 的第 6 字節(jié)位置*///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"); }總結(jié)
以上是生活随笔為你收集整理的【Android 逆向】函数拦截实例 ( ③ 刷新 CPU 高速缓存 | ④ 处理拦截函数 | ⑤ 返回特定结果 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 逆向】函数拦截实例 (
- 下一篇: 【Android 逆向】Frida 框架