代码实例分析android中inline hook

代码实例分析android中inline hook,第1张

概述以下内容通过1、实现目标注入程序,2、实现主程序,3、实现注入函数,4、thumb指令集实现等4个方面详细分析了android中inlinehook的用法,以下是全部内容:

以下内容通过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_INCLUDEDvoID target_foo();#endif // TARGET_H_INCLUDEDtarget.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 := armLOCAL_MODulE := targetLOCAL_CFLAGS += -pIE -fPIE -std=c11LOCAL_LDFLAGS += -pIE -fPIE -shared -llogAPP_ABI := armeabi-v7aLOCAL_SRC_fileS := target.cinclude $(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,"/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,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,#0x8]
     add   r14,sp,#0x10
     str   r14,#0x4]
     pop   {r0}
     push  {r0-r12}
     @ 此时寄存器被备份在栈中,将栈顶地址作为回调函数的参数(struct hook_reg)
     mov   r0,sp
     ldr   r3,_hook_func_addr_s
     blx   r3
     @ 恢复寄存器值
     ldr   r0,#0x3c]
     msr   cpsr,r0
     ldmfd sp!,{r0-r12}
     ldr   r14,#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,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,#-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,4);
     memcpy((voID*)hook_addr,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中inline hook全部内容,希望文章能够帮你解决代码实例分析android中inline hook所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1143120.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存