最近终于沉下心来对着书把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 = ;
int b = ;
while(a--) {
sleep();
b = a * b;
printf("[INFO] b is %d\n", b);
}
b = b + ;
b = b - ;
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.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[];
char file_line[];
if (pid < ) {
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 -;
}
long addr_start = -, addr_end = ;
while (fgets(file_line, sizeof(file_line), fp)) {
if (strstr(file_line, module_name)) {
if ( == 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 - ));
//change memory attribute
if (writable == true) {
return mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != -;
} else {
return mprotect((void*)page_start, page_size, PROT_READ | PROT_EXEC) != -;
}
}

接下来就可以着手实现功能了,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
.global _shellcode_end_s
.global _hook_func_addr_s
.global _stub_func_addr_s
.data
_shellcode_start_s:
@ 备份各个寄存器
push {r0, r1, r2, r3}
mrs r0, cpsr
str r0, [sp, #0xc]
str r14, [sp, #0x8]
add r14, sp, #0x10
str r14, [sp, #0x4]
pop {r0}
push {r0-r12}
@ 此时寄存器被备份在栈中,将栈顶地址作为回调函数的参数(struct hook_reg
mov r0, sp
ldr r3, _hook_func_addr_s
blx r3
@ 恢复寄存器值
ldr r0, [sp, #0x3c]
msr cpsr, r0
ldmfd sp!, {r0-r12}
ldr r14, [sp, #0x4]
ldr sp, [r13]
ldr pc, _stub_func_addr_s
_hook_func_addr_s:
.word 0x0
_stub_func_addr_s:
.word 0x0
_shellcode_end_s:
.end

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

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

 void hook_inline_make(const char *library, long address, hook_func func)
{
//获取hook点在内存中的地址
long base_addr = get_module_addr(-, library);
long hook_addr = base_addr + address;
//获取shellcode中的符号地址
extern long _shellcode_start_s;
extern long _shellcode_end_s;
extern long _hook_func_addr_s;
extern long _stub_func_addr_s;
void *p_shellcode_start = &_shellcode_start_s;
void *p_shellcdoe_end = &_shellcode_end_s;
void *p_hook_func = &_hook_func_addr_s;
void *p_stub_func = &_stub_func_addr_s;
//计算shellcode大小
int shellcode_size = (int)(p_shellcdoe_end - p_shellcode_start);
//新建shellcode
void *shellcode = malloc(shellcode_size);
memcpy(shellcode, p_shellcode_start, shellcode_size);
//添加执行属性
change_addr_writable((long)shellcode, true);
//在32bit的arm指令集中,stubcode中的4条指令占用16个字节的空间
//前两条指令为hook点被替换的两条指令
//后两条指令跳转回原程序
void *stubcode = malloc();
memcpy(stubcode, (void*)hook_addr, );
//ldr pc, [pc, #-4]
//[address]
//手动填充stubcode
char jump_ins[] = {0x04, 0xF0, 0x1F, 0xE5};
uint32_t jmp_address = hook_addr + ;
memcpy(jump_ins + , &jmp_address, );
memcpy(stubcode + , jump_ins, );
//添加执行属性
change_addr_writable((long)stubcode, true);
//修复shellcode中的两个地址值
uint32_t *shell_hook = shellcode + (p_hook_func - p_shellcode_start);
*shell_hook = (uint32_t)func;
uint32_t *shell_stub = shellcode + (p_stub_func - p_shellcode_start);
*shell_stub = (uint32_t)stubcode;
//为hook点添加写属性
change_addr_writable(hook_addr, true);
//替换hook点指令为跳转指令,跳转至shellcode
jmp_address = (uint32_t)shellcode;
memcpy(jump_ins + , &jmp_address, );
memcpy((void*)hook_addr, jump_ins, );
change_addr_writable(hook_addr, false);
//刷新cache
cacheflush(hook_addr, , );
}

注意这里的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的更多相关文章

  1. Android Exception Hook

    承接上一篇文章Android Inline Hook,接下来我们看一下android系统中基于异常的hook方式,这种方式与inline hook相比实现较为简单,但执行效率是它的短板. except ...

  2. Android Hook框架adbi的分析(2)--- inline Hook的实现

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/74452308 一. Android Hook框架adbi源码中inline Hoo ...

  3. Android Hook框架adbi的分析(3)---编译和inline Hook实践

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75200800 一.序言 在前面的博客中,已经分析过了Android Hook框架a ...

  4. Android Native Hook技术(一)

    原理分析 ADBI是一个著名的安卓平台hook框架,基于 动态库注入 与 inline hook 技术实现.该框架主要由2个模块构成:1)hijack负责将so注入到目标进程空间,2)libbase是 ...

  5. android的hook方面知识点

    android hook分为另种: native层hook---理解ELF文件 java层---虚拟机特性和Java上的反射的作用 注入代码: 存放在哪? 用mmap函数分配临时内存来完成代码存放,对 ...

  6. 对付ring0 inline hook

    对付ring0 inline hook的基本思路是这样的,自己写一个替换的内核函数,以NtOpenProcess为例,就是MyNtOpenProcess.然后修改SSDT表,让系统服务进入自己的函数M ...

  7. Inline Hook NtQueryDirectoryFile

    Inline Hook NtQueryDirectoryFile 首先声明这个是菜鸟—我的学习日记,不是什么高深文章,高手们慎看. 都总是发一些已经过时的文章真不好意思,几个月以来沉迷于游戏也是时候反 ...

  8. 在已有软件加壳保护 下实现 Inline hook

    如写的不好请见谅,本人水平有限. 个人简历及水平:. http://www.cnblogs.com/hackdragon/p/3662599.html 正常情况: 接到一个项目实现对屏幕输出内容的获取 ...

  9. windows 32位以及64位的inline hook

    Tips : 这篇文章的主题是x86及x64 windows系统下的inline hook实现部分. 32位inline hook 对于系统API的hook,windows 系统为了达成hotpatc ...

随机推荐

  1. 微信JS-SDK选择相册或拍照并上传PHP实现

    理解:微信上传接口是拍照,或者选择本地照片,上传到微信的服务器,获取到一个id,通过token与这个id获取到图片,保存到服务器即可. 效果 通过微信js接口,调用底层程序. 需要引入js文件,并进行 ...

  2. git学习资料包

    1.廖雪峰老师的git教程:https://www.liaoxuefeng.com    -----点击“GIT教程”开始学习 2.菜鸟教程git学习:http://www.runoob.com/gi ...

  3. RobotFramework自动化测试框架-移动手机自动化测试Element Attribute Should Match关键字的使用

    Element Attribute Should Match 关键字用来判断元素的属性值是否和预期值匹配,该关键字接收四个参数[ locator | attr_name | match_pattern ...

  4. HDU 1908 Double Queue(set)

    Problem Description The new founded Balkan Investment Group Bank (BIG-Bank) opened a new office in B ...

  5. 【LeetCode-面试算法经典-Java实现】【053-Maximum Subarray(最大子数组和)】

    [053-Maximum Subarray(最大子数组和)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Find the contiguous subarray w ...

  6. 【ODPS】TableTunnel单线程简单下载事例

    ODPS Tunnel是ODPS的数据通道,用户能够通过Tunnel向ODPS中上传或者下载数据.眼下Tunnel仅支持表(不包含视图View)数据的上传下载. 主要接口 描写叙述 TableTunn ...

  7. 星云測试- Android应用深度体检专业平台

    星云測试-给你的Android应用做个深度体检   星云測试- Android应用深度体检专业平台 星云在线云測试(简称星云測试www.teststars.cc)是全球第一个公布并商用的数字化精准软件 ...

  8. 自学WPF之XAML(二)控件

    摘自<深入浅出WPF>. 在WPF中是数据驱动UI,数据是核心,是主动的,UI从属于数据,并表达数据,是被动的.UI是展示给用户操作的.响应UI操作的元素是控件(control).下面是我 ...

  9. SGD

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  10. LBSN中的用户行为模式分析

    LBSN中的用户行为模式分析 zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouw  2015-12-23   声明: 1)该LBSN的 ...