以下内容通过1、实现目标注入程序,2、实现主程序,3、实现注入函数,4、thumb指令集实现等4个方面详细分析了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

#include

#include

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

#include

#include

#include

#include

#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

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点的跳转指令替换掉的两条指令,最后跳回原程序。

ellcode.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(-1, 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(16);

memcpy(stubcode, (void*)hook_addr, 8);

//ldr pc, [pc, #-4]

//[address]

//手动填充stubcode

char jump_ins[8] = {0x04, 0xF0, 0x1F, 0xE5};

uint32_t jmp_address = hook_addr + 8;

memcpy(jump_ins + 4, &jmp_address, 4);

memcpy(stubcode + 8, jump_ins, 8);

//添加执行属性

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 + 4, &jmp_address, 4);

memcpy((void*)hook_addr, jump_ins, 8);

change_addr_writable(hook_addr, false);

//刷新cache

cacheflush(hook_addr, 8, 0);

}

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

4、thumb指令集实现

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

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

android hook 实例,代码实例分析android中inline hook相关推荐

  1. android加载efi分区,高通Android UEFI XBL 代码流程分析

    高通Android UEFI XBL 代码流程分析 背景 之前学习的lk阶段点亮LCD的流程算是比较经典,但是高通已经推出了很多种基于UEFI方案的启动架构. 所以需要对这块比较新的技术进行学习.在学 ...

  2. SSDT Hook的妙用-对抗ring0 inline hook

    ******************************************************* *标题:[原创]SSDT Hook的妙用-对抗ring0 inline hook  * ...

  3. 全志 android 编译,全志A20启动代码流程分析 ——Android

    现在的CPU都固化了内部 ROM,内部 ROM中有一般都有一段程序,一般有如下几个功能: 1,初始化,部分外设,如USB,SDCARD 2,初始化DDR(内存)和NandFlash 3,加载boot( ...

  4. 全志android 编译,全志A20启动代码流程分析 ——Android

    现在的CPU都固化了内部 ROM,内部 ROM中有一般都有一段程序,一般有如下几个功能: 1,初始化,部分外设,如USB,SDCARD 2,初始化DDR(内存)和NandFlash 3,加载boot( ...

  5. android tombstone发生过程,如何调试分析Android中发生的tombstone

    如何调试分析Android中发生的tombstone Android中较容易出现以下三类问题:Force close / ANR / Tombstone 前两者主要是查看当前的进程或者系统框架层的状态 ...

  6. [转]Android 项目的代码混淆,Android proguard 使用说明

    简介 Java代码是非常容易反编译的.为了很好的保护Java源代码,我们往往会对编译好的class文件进行混淆处理. ProGuard是一个混淆代码的开源项目.它的主要作用就是混淆,当然它还能对字节码 ...

  7. android安全分析师,乐固分析-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com...

    简介: 调试手机是Android 6.0的32位的手机.样本是自己写的一个demo小程序.加固时间为今年的12月中旬左右. Java层分析 壳的入口是MyWrapperProxyApplication ...

  8. android 休眠唤醒驱动流程分析,Android 电源管理——gotosleep和userActivity关注

    一.Android power management应用层分析 Android提供了android.os.PowerManager类,该类用于控制设备的电源状态的切换. 该类对外有三个接口函数: 1. ...

  9. android漫画app代码,漫画书Android客户端 – ComicApp

    漫画书 1.平台:Android客户端(后期完善IOS端) 2.开发框架:React Native react-redux react-thunk 3.开发工具:Vs Code 1.8 1.项目架构 ...

最新文章

  1. CountDownTimer的简单使用
  2. source insight 里编辑的时候,每次粘贴后,光标停留在粘贴内容的左面
  3. 魔改ResNet反超Transformer再掀架构之争!作者说“没一处是创新”,这些优化trick值得学...
  4. Web Service 概念
  5. 微信小程序实现支付功能
  6. HQL语句使用row_number() over(partition by),分组排序取topN
  7. HttpServletBean 、 FrameworkServlet 和 DispatcherServlet 关系
  8. linux开机黑屏时间长,Linux 开机进入紧急模式,出现黑屏 grub 的参考解决方案
  9. 月薪3k和30k的程序员,差距就在这道坎...
  10. android系统安全测试,Android 安全测试初探 (二)
  11. axure操作回复_Axure8.0基础教程(21-30)新手必须掌握的基础操作
  12. ubuntu 16.04 系统安装保留原home分区
  13. 深度linux iso镜像,深度 Deepin 15 正式版 ISO 镜像下载 - 精美易用适合国人学习的国产 Linux 发行版......
  14. 高分辨率卫星影像建筑物变化检测
  15. 关于谢尔宾斯基三角(Sierpinski)的讲解
  16. 存储器容量的扩充以及DRAM
  17. 设置允许从网络访问计算机的用户账户(加入guest组),网络共享
  18. VHDL出现综合错误:“ERROR:Xst:827 - file_name Line xx: Signal xx cannot be synthesized, bad synchronous desc
  19. 【ReactJS】一、手把手搭建ReactJS开发环境(Sublime)
  20. 让thinkpad更安静——控制风扇转速,解决tpfancontrol导致的关机、重启、黑屏

热门文章

  1. mysql——decimal类型与decimal长度
  2. 计算机仿真在电力领域的应用,仿真技术在电力系统中的应用实例
  3. 自动摘要php,phpcms修改手动摘要255字符、自动摘要200字符及取消自动摘要
  4. c++将小写转换为大写函数_必须掌握的基础函数组合应用技巧,提高效率,准时下班...
  5. Java学习指导————如何做到基础扎实
  6. mysql 查询语句 过滤_MySQL全面瓦解7:查询的过滤条件
  7. vue内检测是否有swiper_vue.js怎么用swiper
  8. .network 中文文档_以太坊链下支付网络Raiden API中文文档
  9. api idea 开发rest_部分介绍使用IDEA的rest client
  10. 利用next_permutation解答全排列问题