GVKun编程网logo

8_InlineHook

16

本文将为您提供关于8_InlineHook的详细介绍,同时,我们还将为您提供关于64位系统InlineHook、64位下的InlineHook、AndroidInlineHook、Androidinl

本文将为您提供关于8_InlineHook的详细介绍,同时,我们还将为您提供关于64 位系统 InlineHook、64位下的InlineHook、Android Inline Hook、Android inline hook 手记的实用信息。

本文目录一览:

8_InlineHook

8_InlineHook

1 shellcode低2Gb警告、应使用高2GB 稳定 : 内核挂钩子

由于每个进程的低2gb 的数据是不同的;所以 在内核挂钩子 因该把 代码 放在 高 2gb。

方法1(申请): 比如 使用前面的 ExAllocatePool(0,size);分配非分页内存,然后把shellcode 拷贝到 非分页内存。

方法2(偷内存): 注意: 偷的时候 看一下 页属性 是否可读可写可执行

在内核固定 没有使用到的地方 放上我们的 shellcode;;

如:gdtr 中的 空向量。

1570272839541

起始 下面这些都没有使用到,都可以占用:

1570272974943

裸函数注意: 在裸函数中没有自动的 ebp ,esp 开栈 平栈的操作;所以忌讳使用局部变量;当然可以自己管理栈,使用好局部变量。

Hook kifastcallentry 重点结论:注意 当 目标地址 < 当前地址 ; jmp 的偏移 应该是 直接 目标 - 当前 -5;

目标地址 > 当前地址 ; jmp 的偏移应该 是 目标 - 当前 -5;

是一样的; jmp 偏移的基址 是当前指令结束位置

jmp 寄存器 和call寄存器一样 也 不需要计算什么偏移;寄存器是什么就跳到什么。

push 0xAddr;ret 也不需要 计算偏移;但是需要 6个字节;计算偏移的需要5个字节

计算偏移 是最重要的;

注意 步骤:

计算到目标code 的偏移 (或者直接使用寄存器)

将当前位置的 opcode 修改 为跳转 指令 char * [] = {0xe9,0x.....0x..}

然后在目标地址那个做好处理后 最好将 执行流 交换原来的执行流;所以再计算偏移(或使用寄存器)跳回去;

f7 单步调试触发 的是 idt 里面的 1号处理 使用 pchunter 查看idt 里面的序号为 1 的向量的地址;然后 使用ida 调试 查看;

hook;

大致如下:

程序1 : 挂钩gdt内存地址到 目标函数

1570289557113

程序2 :hook 逻辑操作 代码:

1570289111758

64 位系统 InlineHook

64 位系统 InlineHook

APIHook64Class.h

 1 #ifndef APIHOOK64CLASS_H_
 2 #define APIHOOK64CLASS_H_
 3 #include <Windows.h>
 4 
 5 class APIHook64
 6 {
 7 private:
 8     unsigned char code[12];
 9     unsigned char oldcode[12];
10     FARPROC addr;
11 
12 public:
13     APIHook64();
14     BOOL Hook(char *dllName,char *apiName,long long callfunc,BOOL bHook=TRUE);
15 };
16 
17 #endif

 

APIHook64Class.cpp

 1 #include "APIHook64Class.h"
 2 
 3 APIHook64::APIHook64()
 4 {
 5     /*
 6         mov eax,0x12345678
 7         push eax
 8         ret
 9     */
10     unsigned char c[12] = { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xC3 };
11     RtlMoveMemory(APIHook64::code, c, 12);
12     memset(APIHook64::oldcode, 0, 12);
13     addr = NULL;
14 }
15 
16 BOOL APIHook64::Hook(char *dllName, char *apiName, long long callfunc, BOOL bHook)
17 {
18     BOOL bOk = FALSE;
19     DWORD dwOldProtect = 0;
20     long long api = callfunc;
21     HANDLE hPro = GetCurrentProcess();
22 
23     if (!APIHook64::oldcode[0])
24     {
25         addr = GetProcAddress(LoadLibrary(dllName), apiName);
26         RtlMoveMemory(APIHook64::code+2, &api, 8);
27         if (VirtualProtectEx(hPro, addr, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect))
28         {
29             RtlMoveMemory(APIHook64::oldcode, addr, 12);
30         }
31     }
32     if (bHook)
33     {
34         bOk = WriteProcessMemory(hPro, addr, APIHook64::code, 12, NULL);
35     }
36     else {
37         bOk = WriteProcessMemory(hPro, addr, APIHook64::oldcode, 12, NULL);
38     }
39     VirtualProtectEx(hPro, addr, 12, dwOldProtect, &dwOldProtect);
40     CloseHandle(hPro);
41     return bOk;
42 }

 

64位下的InlineHook

64位下的InlineHook

[toc]

x64下手工HOOK的方法

关于64位程序.网上HOOK方法一大堆.这里也记录一下. 了解跨平台HOOK的真相与本质.

一丶HOOK的几种方法之远跳

1. 远跳 不影响寄存器 + 15字节方法

在64位下 HOOK有几种方法. 一种是影响寄存器的值.另一种是不影响寄存器的值.各有优劣.

第一种: 不影响寄存器的值 硬编码占用大小为15个字节.

原理: 利用push + ret的原理. 让HOOK的位置跳转为我们的地址.

push 函数低地址(8个字节)
mov qword ptr ss:[rsp + 4],函数高地址(8个字节,不过高4个字节一般都是0所以可以不用给)
ret

硬编码:

68 XX XX XX XX							push LowAddress
48 C7 44 24 04 XX XX XX XX              mov qword ptr ss:[rsp + 4],HighAddress
C3	                                    ret

其中XX的地方可以换成我们的地址.

2.远跳 影响寄存器 + 12字节方法

这一种方法则会影响寄存器的值.

原理: 利用 rax + jmp的方式进行跳转.

mov rax,Address
Jmp rax

硬编码

48 B8 XX XX XX XX XX XX XX XX FF E0 

截图:

这里需要注意的是地址的大小尾模式. 填写一定不要写错.

3.影响寄存器,恢复寄存器 进行跳转.

这种方法其实是第二种方法的演变. 第二种我们会直接修改rax为我们的地址.其实我们可以利用堆栈进行保存.

原理: rax + jmp + push 进行恢复还原


push reg
mov reg,address
jmp reg
pop reg

reg代表的就是任意寄存器. 如果使用这个方法可以有效地保存寄存器进行HOOK.跳转回来的时候进行还原即可.

这里的硬编码不确定.因为 push reg 与 pop reg 跟你使用的寄存器有关.

4. 常用 jmp + rip方式跳转 大小6个字节

在64位程序中. 可以使用rip寄存器了. 而32位不可以.32位下想要改变 eip的值. 无非就是 jmp + call才可以改变.64位可以使用

原理: jmp + rip 进行寻址. 进行跳转

jmp qword ptr ds:[rip]
数据地址

这种方法就是 对rip + (指令长度)这个寄存器取内容 把里面的数据当做地址进行跳转. 所以当使用这个方式的时候.我们的下方跟着八个字节数据即可. 这个数据就是你要跳转的地址.

如下:

它会把下面八个字节数据当做地址进行跳转.

这时候有人说了.你这不是6字节跳转呀.下面要跟着8个字节. 所以这里说一下. 使用这种方法. 在rip的下面不一定要跟着就是数据. 我们可以修改偏移进行跳转. 什么意思. 现在我们是 jmp qword ptr ds:[rip] ,代表了我们要在当前rip的下方取8个字节当做地址跳转. 那么我们也可以写成 jmp qword ptr ds:[rip + x]; x就是任意数. 把这个偏移下的8个字节去内容当做地址去跳转.

关于偏移 计算公式其实还是一样. 目的地址 - 源地址 - 指令长度.

如下:

比如我们从第一条指令 进行偏移跳转. 把图中红框内容当做8个字节数据进行跳转.

那么可以写成如下:

目的地址(红框地址,0x7FFF12A51228) - 源地址(7FFF12A511Dd) - 指令长度(6)

指令长度为什么是6.因为我们第一行指令是 jmp qword ptr ds:[rip] 只不过我们现在还没写. 我们得出的偏移放到第一行中的偏移..

现在得出的偏移为 0x45 这个45我们就可以填写到偏移中.

现在 0x7FFF12A51228 红框地址是我们的数据.只需要在这里写入8个字节地址即可.

如下图:

如上图可以看到,第一个红框 FF 25 45 00 00 00 这个45就是我们算的偏移. 意思就是在 rip + 45位置,读取8个字节数据当做地址进行跳转. 然后 228地址我们写入了8个字节数据. 数据的地址 后缀为1EC的地址. 所以看上图,我们的RIP已经跳转过去了.

注意,因为 rip的寻址偏移只能是上下2GB 也就是你的偏移不要超过2G位置即可.

二丶Call的几种方式.

1. CALL PUSH + RET 方式

这种方式跟上方的push + ret原理类似.

call Next
Next:
push lowAddress;
mov [rsp + 4],highAddress
ret

这种方式就是取得下一行指令, 下一行指令入栈. 然后继续push + ret 进行跳转.

2.正常call

这种call 没有试过. 原理跟上面一样. 硬编码 E8 偏移 的方式调用


call youAddress

硬编码为: E8 XX XX XX XX xx代表偏移. 偏移计算公式还是 目的 - 源 - 5(指令长度)

但是是上下2GB

其他待整理

Android Inline Hook

Android Inline Hook

最近终于沉下心来对着书把 hook 跟注入方面的代码敲了一遍,打算写几个博客把它们记录下来。

第一次介绍一下我感觉难度最大的 inline hook,实现代码参考了腾讯 GAD 的游戏安全入门。

inline hook 的大致流程如下:

首先将目标指令替换为跳转指令,跳转地址为一段我们自己编写的汇编代码,这段汇编代码先是执行用户指定的代码,如修改寄存器的值,然后执行被替换掉的原指令 2,最后再跳转回原指令 3 处,恢复程序的正常运行。

为了避开注入过程,我们通过 hook 自己进程加载的动态连接库进行演示。

1、实现目标注入程序

我们将这个程序编译为动态连接库,然后在主程序中加载,作为 hook 的目标。

target.h
#ifndef TARGET_H_INCLUDED
#define TARGET_H_INCLUDED void target_foo(); #endif // TARGET_H_INCLUDED
target.c #include "target.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> void target_foo() { int a = 3; int b = 2; while(a--) { sleep(2); b = a * b; printf("[INFO] b is %d\n", b); } b = b + 2; b = b - 1; printf("[INFO] finally, b is %d\n", b); }
Android.mk
include $(CLEAR_VARS) LOCAL_ARM_MODE := arm LOCAL_MODULE :
= target LOCAL_CFLAGS += -pie -fPIE -std=c11 LOCAL_LDFLAGS += -pie -fPIE -shared -llog APP_ABI := armeabi-v7a LOCAL_SRC_FILES := target.c include $(BUILD_SHARED_LIBRARY)

注意 Android.mk LOCAL_ARM_MODE := arm 代表编译时使用 4 字节的 arm 指令集,而不是 2 字节的 thumb 指令集。

2、实现主程序

在主程序中我们首先加载之前编写的动态链接库,进行 hook 之后再对其中的函数 target_foo 进行调用。

main.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <stdbool.h>
#include "hook_inline.h"

typedef void (*target_foo)(void);
void my_func(struct hook_reg *reg) { puts("here we go!"); } void main() { void *handler = dlopen("/data/local/tmp/libtarget.so", RTLD_NOW); target_foo foo = (target_foo)dlsym(handler, "target_foo"); hook_inline_make("/data/local/tmp/libtarget.so", 0xde2, my_func, true); foo(); }
hook_inline.h #ifndef HOOK_INLINE_H_INCLUDED
#define HOOK_INLINE_H_INCLUDED #include <stdbool.h> struct hook_reg { long ARM_r0; long ARM_r1; long ARM_r2; long ARM_r3; long ARM_r4; long ARM_r5; long ARM_r6; long ARM_r7; long ARM_r8; long ARM_r9; long ARM_r10;long ARM_r11; long ARM_r12;long ARM_sp; long ARM_lr; long ARM_cpsr; }; typedef void (*hook_func)(struct hook_reg *reg); bool hook_inline_make(const char *library, long address, hook_func func, bool isArm); #endif // HOOK_INLINE_H_INCLUDED

这里我们 hook 功能的实现函数为 hook_inline_make,4 个参数分别为动态库路径,目标地址,用户函数,目标地址处指令集。

当程序执行到目标地址处时会回调我们传入的用户函数,可通过参数 hook_reg 来更改寄存器的值(不包括寄存器 pc)。因为之前在动态链接库的 Android.mk 文件指定了使用 arm 指令集进行编译,所以此处指定最后一个参数为 true

3、实现注入函数

现在到了最为关键的地方,为了实现这个功能还需要了解几个知识。

(1)、获取内存中动态链接库的基址

Linux 系统中各个进程的内存加载信息可以在 /proc/pid/maps 文件中到,通过它我们可以获取到动态链接库在内存中的加载基址。

long get_module_addr(pid_t pid, const char *module_name)
{
    char file_path[256];
    char file_line[512];
    if (pid < 0) {
        snprintf(file_path, sizeof(file_path), "/proc/self/maps");
    } else {
        snprintf(file_path, sizeof(file_path), "/proc/%d/maps", pid);
    }
    FILE *fp = fopen(file_path, "r");
    if (fp == NULL) {
        return -1;
    }
    long addr_start = -1, addr_end = 0;
    while (fgets(file_line, sizeof(file_line), fp)) {
        if (strstr(file_line, module_name)) {
            if (2 == sscanf(file_line, "%8lx-%8lx", &addr_start, &addr_end)) {
                break;
            }
        }
    }
    fclose(fp);
    printf("library :%s %lx-%lx, pid : %d\n", module_name, addr_start, addr_end, pid);
    return addr_start;
}

(2)、更改内存中的二进制代码

现在的计算机系统中一般对内存进行分段式管理,不同的段有不同的读、写、执行的属性。一般来讲代码段只有读和执行的属性,不允许对代码段进行写操作。Linux 系统中通过函数 mprotect 对内存的属性进行更改,需要注意的一点是需要以内存页的大小进行对齐。

bool change_addr_writable(long address, bool writable) {
    long page_size = sysconf(_SC_PAGESIZE);
    //align address by page size
    long page_start = (address) & (~(page_size - 1));
    //change memory attribute
    if (writable == true) {
        return mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != -1;
    } else {
        return mprotect((void*)page_start, page_size, PROT_READ | PROT_EXEC) != -1;
    }
}

接下来就可以着手实现功能了,inline hook 跟指令集密切相关,此处我们先演示 arm 指令集的情况,之后对 thumb 指令集进行讨论。这里实现的功能是用户可在自己注册的回调函数中对 hook 点寄存器的值进行修改。

 

为了实现 32 位地址空间的长跳转,我们需要两条指令的长度(8 个字节)来实现。一般手机上的 arm 处理器为 3 级流水,所以 pc 寄存器的值总是指向当前执行指令后的第二条指令,因而使用 ldr pc, [pc, #-4] 来加载该指令之后的跳转地址。当程序跳转到 shellcode 后,首先对寄存器组进行备份,然后调用用户注册的回调函数,用户可在回调函数中修改备份中各个寄存器(pc 寄存器除外)的值,然后从备份中恢复寄存器组再跳转到 stubcode,stubcode 的功能是执行被 hook 点的跳转指令替换掉的两条指令,最后跳回原程序。

shellcode.S
1
.global _shellcode_start_s 2 .global _shellcode_end_s 3 .global _hook_func_addr_s 4 .global _stub_func_addr_s 5 .data 6 _shellcode_start_s: 7 @ 备份各个寄存器 8 push {r0, r1, r2, r3} 9 mrs r0, cpsr 10 str r0, [sp, #0xc] 11 str r14, [sp, #0x8] 12 add r14, sp, #0x10 13 str r14, [sp, #0x4] 14 pop {r0} 15 push {r0-r12} 16 @ 此时寄存器被备份在栈中,将栈顶地址作为回调函数的参数(struct hook_reg 17 mov r0, sp 18 ldr r3, _hook_func_addr_s 19 blx r3 20 @ 恢复寄存器值 21 ldr r0, [sp, #0x3c] 22 msr cpsr, r0 23 ldmfd sp!, {r0-r12} 24 ldr r14, [sp, #0x4] 25 ldr sp, [r13] 26 ldr pc, _stub_func_addr_s 27 _hook_func_addr_s: 28 .word 0x0 29 _stub_func_addr_s: 30 .word 0x0 31 _shellcode_end_s: 32 .end

shellcode 使用汇编实现,在使用时需要对里边的两个地址进行修复,用户回调函数地址(_hook_func_addr_s)跟 stubcode 地址(_stub_func_addr_s)。

接下来我们可以看一下函数 hook_inline_make 的具体实现了

 1 void hook_inline_make(const char *library, long address, hook_func func)
 2 {
 3     //获取hook点在内存中的地址
 4     long base_addr = get_module_addr(-1, library);
 5     long hook_addr = base_addr + address;
 6     //获取shellcode中的符号地址
 7     extern long _shellcode_start_s;
 8     extern long _shellcode_end_s;
 9     extern long _hook_func_addr_s;
10     extern long _stub_func_addr_s;
11     void *p_shellcode_start = &_shellcode_start_s;
12     void *p_shellcdoe_end = &_shellcode_end_s;
13     void *p_hook_func = &_hook_func_addr_s;
14     void *p_stub_func = &_stub_func_addr_s;
15     //计算shellcode大小
16     int shellcode_size = (int)(p_shellcdoe_end - p_shellcode_start);
17     //新建shellcode
18     void *shellcode = malloc(shellcode_size);
19     memcpy(shellcode, p_shellcode_start, shellcode_size);
20     //添加执行属性
21     change_addr_writable((long)shellcode, true);
22     //在32bit的arm指令集中,stubcode中的4条指令占用16个字节的空间
23     //前两条指令为hook点被替换的两条指令
24     //后两条指令跳转回原程序
25     void *stubcode = malloc(16);
26     memcpy(stubcode, (void*)hook_addr, 8);
27     //ldr pc, [pc, #-4]
28     //[address]
29     //手动填充stubcode
30     char jump_ins[8] = {0x04, 0xF0, 0x1F, 0xE5};
31     uint32_t jmp_address = hook_addr + 8;
32     memcpy(jump_ins + 4, &jmp_address, 4);
33     memcpy(stubcode + 8, jump_ins, 8);
34     //添加执行属性
35     change_addr_writable((long)stubcode, true);
36     //修复shellcode中的两个地址值
37     uint32_t *shell_hook = shellcode + (p_hook_func - p_shellcode_start);
38     *shell_hook = (uint32_t)func;
39     uint32_t *shell_stub = shellcode + (p_stub_func - p_shellcode_start);
40     *shell_stub = (uint32_t)stubcode;
41     //为hook点添加写属性
42     change_addr_writable(hook_addr, true);
43     //替换hook点指令为跳转指令,跳转至shellcode
44     jmp_address = (uint32_t)shellcode;
45     memcpy(jump_ins + 4, &jmp_address, 4);
46     memcpy((void*)hook_addr, jump_ins, 8);
47     change_addr_writable(hook_addr, false);
48     //刷新cache
49     cacheflush(hook_addr, 8, 0);
50 }

注意这里的 change_addr_writable 函数无论传入 false 还是 true 对应地址都会添加上执行属性。由于处理器采用流水线跟多级缓存,在更改代码后我们需要手动刷新 cache,即函数 cacheflush(第三个参数无意义)。

 

4、thumb 指令集实现

由于 thumb 指令集的功能受到限制,虽然思路上跟 arm 指令集一致,但在实现上需要用更多条指令,下面是我自己想的一种实现方式,欢迎交流。

需要注意的是由于每条 thumb 指令为 16bit,所以 32 位的跳转地址需要占用两条指令的空间,而且跳转时会污染 r0 寄存器所以要对其进行保护。我在实现程序时将 shellcode 编译为了 arm 指令集,所以在原程序、shellcode、stubcode 之间相互跳转时需要使用 bx 指令进行处理器状态切换(需要跳转的地址代码为 thumb 指令集时,需要将地址的第 1 个 bit 位置位)。

 

Android inline hook 手记

Android inline hook 手记

许久没搞安全方面的东西了。最近有些时间看了看 android 下面的开发,看去看来总是想往内深入,结果又往 Native 和内核挖过去了……

研究了一下 ARM 架构下面的 inline 实现,网上搜了搜似乎没有找到多少资料。想了想还是写出来吧,当作笔记。由于时间不多,也不是为工作而看, 仅仅是做了粗略的实现和研究,并没有深入下去。这篇手记的目的不在于详细描述整个 HOOK 实现的过程,而仅仅对一些关键点作一些描述。

说到 Inline hook,了解这个词的同志们都应该知道,无非是修改目标函数处的指令,跳转到自己的函数,并且提供调用原函数的 stub,即可完成整个流程。但是在 ARM 下面情况和我们熟悉的 x86 有所不同。ARM 芯片的运行状态分为 arm 和 thumb 两种模式,分别有不同的指令集,arm 指令为定长 32 位,thumb 指令为定长 16 位 (thumb-2 中进行了扩展,可以使用 32 位 thumb 指令)。同一段代码中可以混用两套指令集,通过一些带有 interworking 功能的跳转或者 load 指令可以在两种模式间切换。做 ARM 下的 inline,首先遇到的就是指令模式的问题。另外,ARM 架构 下,CPU 也具有分开的指令缓存和数据缓存,类似 x86 下的 DTLB 和 ITLB。但是在实现过程中发现,arm 的缓存作用非常明显,而且刷新机制不太确 定,因此自修改代码需要经常主动控制缓存的刷新。这一点,可以通过 NDK 的 API cacheflush 实现。

下面简单说说一些主要问题:

  • 关于页保护

    这一点对于熟悉 Linux 编程的同志们应该不是问题,mprotect 修改为 PROT_READ | PROT_WRITE | PROT_EXEC 即可。页面大小可以通过包含 ndk 下面 <asm/page.h> 文件,里面定义的一系列宏用于获得页面大小和进行对齐运 算。

  • 关于模式转换和跳转

    Arm 下主要的分支指令如 BX,BLX 等,都可以切换指令模式。详见 arm 的用户手册。这里主要讨论模式的选择和切换时机。只有一个问题需要注 意,arm 处理器执行时,由于流水线的关系,会预取两条指令,因此当前指令取到的 pc 值,始终是之后第三条指令的地址。比如当前指令地址是 0×8000, 那么当前 pc 的值,在 thumb 下面是 0×8000 + 2 * 2, 在 arm 下面是 0×8000 + 4 * 2。

    由于运行时我没有找到简便的办法能够确切知道被 hook 的目标函数指令集,所以这个问题留给了 hook 的使用者来决定。Hook 之前应通过逆向工具获知所有目标函数是 arm 还是 thumb 指令。

    如果要根据目标函数指令集的不同而对 hook 函数采用不同的 编译选项,显然是一件麻烦的事情。而 arm 模式的指令由于单条指令包含的语义更多,是我们的首选。因此可以考虑主仅使用 arm 指令编译 hook 函数,而在跳转的同时切换到 arm 模式。

    关于跳转插入的指令方面,由于 arm 指令带立即数的跳转范围只有 4M,thumb 的跳转范围只有 256 字节。所以首选 ldr pc,xxxx 指令来实现。对于 arm 指令的目标,这个指令很容易选择。如下:

    ldr pc, [pc,#-4]

    32 位跳转绝对地址

    指令为单个 32 位数字:0xE51FF004。

    但是 thumb 模式下的 16 位 ldr 指令没有办法向 pc 中 load,选择就很成问题。如果单纯使用 16 位 thumb 指令的话,跳转部分需要占用大量 字节数,而因为 arm 下面编译器常常使用 pc 的值作为基址来计算地址,被搬动过的指令中就极有可能存在这种指令。搬动过后的代码中就必须对这部分指令进行 修正,而又由于 thumb 所能够支持的立即数很小,跳转范围也很小,这种修正往往非常麻烦,需要用几条同等指令来替换一条指令。经过考虑,还是决定放弃对 ARMv5 的支持,直接使用 ARMv6T2 之后支持的 thumb-2 指令集。thumb-2 支持 32 位 thumb 指令,也支持 ldr 以 pc 为目标寄存器:

    ldr.w pc,[pc,#0]

    32 位跳转绝对地址

    指令为单个 32 位数字:0x00F0DFF8

    所有需要跳转的地址,需要注意的是 bit0 的处理。如果 bit0 为 1,跳转后会切换到 thumb 指令模式,如果 bit0 为 0,会切换至 arm 模式。 当目标为 arm 的时候,我们不需要特殊处理,编译器会处理地址的计算。但是当目标为 thumb 的时候,从 hook 指令跳转到 hook 函数,以及调用原始函 数的时候,都需要注意地址 bit0 的处理。

  • 关于搬出来的原始指令

    按照 win32 下 Detours 库的实现方式,被 HOOK 函数的前面几条指令,会搬到一个 trampoline 中,并在这些指令后添加跳转至原代码 后续部分的指令。在搬动过程中,需要对被移动的指令进行地址修正。在处理 ARM 平台 inline 的过程中也需要作这样的工作。但是实际上在处理的时候会发 现,要做到这一点是非常困难的。ARM 下常常会生成下面这种将 pc 作为地址参照的指令块

    而由于 arm 平台寻址范围较小,编译器通常选择将数据和指令在内存中的存放混杂在一起。thumb 模式下,由于指令中能包含的立即数非常小,这种问 题会表现得异常突出,修正的时候也常常一条指令被拉长为数条。因此代码修正会有非常大的工作量。这部分问题由于太消耗时间,我也仅仅是对 arm 下的 inline 进行研究性实现,也就没有管这个问题了。实际项目如果要用到 hook,这个部分花费的时间应该比单纯 hook 跳转的实现要大得多。在不考虑并 发和效率的情况下,当 hook 函数中要调用原函数时,可以考虑临时恢复 hook,并在调用完成后再次 hook 来解决。但是始终是相当不优雅的实现。

  • 关于线程处理

    修改 hook 目标的指令时,和 x86 平台下一样,也需要注意有可能某些线程刚好执行到被修改的指令中的问题。Win32 下可以枚举线程并修改 context 到被搬迁的指令中去。但是 Linux 内核系统下很难进行线程的控制。估计可以采用接管信号处理,并向进程内所有线程调用 pthread_kill 来实现。

今天关于8_InlineHook的分享就到这里,希望大家有所收获,若想了解更多关于64 位系统 InlineHook、64位下的InlineHook、Android Inline Hook、Android inline hook 手记等相关知识,可以在本站进行查询。

本文标签: