1. 程式人生 > >android 內聯 hook

android 內聯 hook

bit != fff andro html -a 速度 git 其它

先回顧下 x86 下的內聯 hook.
1.原理是找到你要 hook 的地址。
2.保存這個地址原來的數據。(這裏要保存至少 5 個字節的數據因為一個 call指令為 5 個字節
3.把這個地址修改成 call 0Xxxxx(5 個字節 ) 也就是對應 opcode 為 E9 0Xxxxx 後面四個字節為一個函數地址
4.填充0Xxxxx 公式為 自己的函數地址-當前地址-5
5.把原來的那個 5 個字節的 opcode 還原重新從 hook點開始執行
arm 下
第一點有 倆種模式Arm模式與Thumb模式
在Arm版本7及以上的體系中,其指令集分為Arm指令集和Thumb指令集。Arm指令為4字節對齊,每條指令長度均為32位;Thumb指令為2字節對齊,又分為Thumb16、Thumb32,其中Thumb16指令長度為16位,Thumb32指令長度為32位。
第二點函數調用指令為:
B系列指令:B、BL、BX、BLX
PC寄存器 相當於 eip
第三點不這倆種模式流水線級數不同。arm一般為 3 級 。也就是說在 arm模式下 pc 指向下一條指令的向一條。因為 hook是 pc 要回退 4 個字節(4字節對齊)
因此對應:

LDR PC, [PC, #-4]
addr

而Thumb(32位)不用回退

LDR.W PC, [PC, #0]
addr

因為也就相當於 LDR.W PC, [PC, #0]/LDR PC, [PC, #-4] 對應x86 中的 call 對應 opcode
call : E9
LDR.W PC, [PC, #0]:0x00F0DFF8 (LDR.W強制以 4 字節)

LDR PC, [PC, #-4] :0xE51FF004
後面就與 x86 方式一樣了修改地址:

x86 
call 0Xxxxx
arm
LDR PC, [PC, #-4]
Thumb(32位)
addr
LDR.W PC, [PC, #0]
addr

最後要註意一個非常容易忽略的點就是地址一般為 4 個字節,也就是說Thumb(32位)的每條指令是 2 個字節對齊的因此只能 2 個字節的寫。而 arm 每條指令為 4 字節對齊。因此應該考慮有時候只有 2 字節,也就必須後倆個字節用 nop填充。因為指令沒有 4 字節對齊為非法指令。
也就是寫法為:

//開始 hook
//arm 方式就一種
//thumb 方式有倆種  一種是轉成arm 再 hook  一種是直接 hook
static void doInlineHook(struct inlineHookItem *item)
{
    //修改頁屬性
    mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_WRITE | PROT_EXEC);

    if (item->proto_addr != NULL) {
        *(item->proto_addr) = TEST_BIT0(item->target_addr) ? (uint32_t *) SET_BIT0((uint32_t) item->trampoline_instructions) : item->trampoline_instructions;
    }
    //Arm指令為4字節對齊,每條指令長度均為32位;Thumb指令為2字節對齊,
    // 又分為Thumb16、Thumb32,其中Thumb16指令長度為16位,Thumb32指令長度為32位。
    //這裏要註意的是:
    //Arm處理器采用3級流水線來增加處理器指令流的速度,也就是說程序計數器R15(PC)總是指向“正在取指”的指令,
    // 而不是指向“正在執行”的,即PC總是指向當前正在執行的指令地址再加2條指令的地址。比如當前指令地址是0×8000,
    // 那麽當前pc的值,在thumb下面是0×8000 + 2*2, 在arm下面是0×8000 + 4*2。
    //對於Arm指令集,跳轉指令為:
    //
    //LDR PC, [PC, #-4]  要回退一條
    //addr
    //
    //LDR PC, [PC, #-4]對應的機器碼為:0xE51FF004,addr為要跳轉的地址。
    //該跳轉指令範圍為32位,對於32位系統來說即為全地址跳轉。
    //對於Thumb32指令集,跳轉指令為:
    //
    //LDR.W PC, [PC, #0]  不要回退剛好指向下一條
    //addr
    //
    //LDR.W PC, [PC, #0]對應的機器碼為:0x00F0DFF8,addr為要跳轉的地址。同樣支持任意地址跳轉。
    //thumb 模式(Thumb32指令集)為倆字節對齊
    if (TEST_BIT0(item->target_addr)) {
        int i;
        i = 0;
        //判斷是否為4 字節對齊 用NOP填充
        if (CLEAR_BIT0(item->target_addr) % 4 != 0) {
            ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00;  // NOP
        }
        //LDR偽指令 LDR.W 強制32位
        //倆個字節為一組
        //LDR.W PC, [PC, #0]對應的機器碼為:0x00F0DFF8  每次一個四字節要分為倆個2 字節
        ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF;
        ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF000; // LDR.W PC, [PC]
        //一個四字節地址  同樣分為倆個2 字節
        //取低四位
        ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr & 0xFFFF;
        //取高四位
        ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr >> 16;
    }
    //arm 模式  指令為4字節對齊
    else {
        //四個字節為一組
        ((uint32_t *) (item->target_addr))[0] = 0xe51ff004; // LDR PC, [PC, #-4]
        //下一條4字節地址
        ((uint32_t *) (item->target_addr))[1] = item->new_addr;
    }

    mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_EXEC);

    item->status = HOOKED;

    cacheflush(CLEAR_BIT0(item->target_addr), CLEAR_BIT0(item->target_addr) + item->length, 0);
}

源碼:
https://github.com/ele7enxxh/Android-Inline-Hook
其它博客地址:
http://ele7enxxh.com/Android-Arm-Inline-Hook.html
https://www.cnblogs.com/mmmmar/p/8185549.html

android 內聯 hook