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.mkinclude $(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.mkLOCAL_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位置位)。

原文地址:https://www.cnblogs.com/mmmmar/p/8185549.html

时间: 2024-10-08 07:58:51

android inline hook的相关文章

Android inline hook手记[转载]

原网址:http://blog.dbgtech.net/blog/?p=51 作者:NetRoc Android inline hook手记 说到Inline hook,了解这个词的同志们都应该知道,无非是修改目标函数处的指令,跳转到自己的函数,并且提供调用原函数的stub,即可完成整个流程.但是在ARM下面情况和我们熟悉的x86有所不同.ARM芯片的运行状态分为arm和thumb两种模式,分别有不同的指令集,arm指令为定长32位,thumb指令为定长16位(thumb-2中进行了扩展,可以使

Android Art Hook 技术方案

Android Art Hook 技术方案 by 低端码农 at 2015.4.13 www.im-boy.net 0x1 开始 Anddroid上的ART从5.0之后变成默认的选择,可见ART的重要性,目前关于Dalvik Hook方面研究的文章很多,但我在网上却找不到关于ART Hook相关的文章,甚至连鼎鼎大名的XPosed和Cydia Substrate到目前为止也不支持ART的Hook.当然我相信,技术方案他们肯定是的,估计卡在机型适配上的了. 既然网上找不到相关的资料,于是我决定自己

android 5 HOOK 技术研究之 ADBI 项目

简介 adbi 是一个android平台的二进制注入框架,源码开放在github上 :  ADBI 项目 ,从hook技术的分类来说,其属于so注入+inline hook, 这种方式的套路是:基于linux系统的ptrace机制,attach一个目标进程,注入一个动态链接库进入目标进程的地址空间,然后用so里边的函数地址替换目标进程地址空间里原有的函数地址(老的函数地址一般也需要保存起来). 源码目录 hijack:  可执行程序,用于注入一个so到目标进程 libbase:  注入库,提供h

Cydia Substrate框架Android so hook分析

最近需要用到Android so hook,于是分析了一下比较流行的Cydia Substrate框架 CydiaSubstrate框架的核心函数是MSHOOKFunction,官方使用说明如下: 现在Android 默认编译出来的都是thumb指令集的,就分析一下这个模式下的HOOK吧. 在使用MSHOOKFunction HOOK前,先用IDA attach到进程先看下准备HOOK的函数,前面18个字节的二进制指令如下: 52ABF480 30 B5       PUSH          

Android Native Hook技术(一)

原理分析 ADBI是一个著名的安卓平台hook框架,基于 动态库注入 与 inline hook 技术实现.该框架主要由2个模块构成:1)hijack负责将so注入到目标进程空间,2)libbase是注入的so本身,提供了inline hook能力. 源码目录中的example则是一个使用ADBI进行hook epoll_wait的示例. hijack hijack实现动态库注入功能,通过在目标进程插入dlopen()调用序列,加载指定so文件.要实现这个功能,主要做两件事情: 获得目标进程中d

使用cydia substrate 来进行android native hook

? cydia不仅可以hook java代码,同样可以hook native代码,下面举一个例子来进行android native hook 我是在网上找到的supermathhook这个项目,在他基础上修改的,本来是为了仓促应对阿里的ctf 这个项目位置: 这个项目是用来hook jni 代码的,而我是用来hook dvmDexFileOpenPartial这个函数的,所以必须使用 Mshookfunction这个函数,这个函数在libsubstrate.so中,自己去官网下载就可以了. 接下

Inline Hook

本文讲 用inline hook的方式修改NtOpenKey函数的一个小例子, 自己的学习笔记 hook 计算机里面一般是指 挂钩某函数,也可以是替换掉原来的函数. inline hook 是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的. 这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改.一般来说 普通的hook比较稳定使用. inline hook 更加高级一点,一般也跟难以被发现.所以很多人比如病毒制作者都比较推崇

反病毒攻防研究第012篇:利用Inline HOOK实现主动防御

一.前言 之前文章中所讨论的恶意程序的应对方法,都是十分被动的,即只有当恶意程序被执行后,才考虑补救措施.这样,我们就会一直处于后手状态,而如果说病毒的危害性极大,那么即便我们完美地修复了诸如注册表项,服务项等敏感位置,并且删除了病毒本身,但是它依旧可能已经破坏了系统中非常重要的文件,造成了不可逆的损伤.因此这篇文章就来简单讨论一下利用Inline HOOK技术实现主动防御,在病毒执行前,就主动将危险函数劫持,如同一道防火墙,保护我们计算机的安全. 二.Inline HOOK原理 我们平时所使用

Ring3 下 API Inline Hook 优化方案探索与实现

??本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/51302024 ?? 以前写过两篇"[Win32] API Hook(1)在32/64位系统上的实现"博客,介绍并给出了 API inline hook 代码,如下: ????blog.csdn.net/zuishikonghuan/article/details/47