【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 )

简介: 【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 )

文章目录

前言

一、函数拦截需要的几个参数

二、插桩前先保存实际函数入口 6 字节数据

三、在插桩的函数入口写入跳转指令 | 构造拼接桩函数

前言

【Android 逆向】函数拦截实例 ( 函数拦截流程 | ① 定位动态库及函数位置 ) 博客中简单介绍了 hook 函数 ( 函数拦截 ) 的流程 , 本篇博客开始介绍函数拦截实例 ;


拦截 clock_gettime 函数 ;


#include <time.h>
int clock_gettime(clockid_t clk_id,struct timespec *tp);





一、函数拦截需要的几个参数


定义 hook_func 函数 , 执行 C/C++ 函数的 hook 操作 ;


void hook_func(uint8_t* pApi, uint8_t* pUser, uint8_t* pStub, size_t size)


上述函数的 4 44 个参数含义如下 :


uint8_t* pApi 参数 : 要拦截的实际函数 , int clock_gettime(clockid_t clk_id,struct timespec *tp); 函数 ;

uint8_t* pUser 参数 : 拦截函数后 , 跳转到的 dn_clock_gettime 函数 ;

uint8_t* pStub 参数 : 定义的 do_clock_gettime 桩代码 , 将 pApi 函数的前 6 字节拷贝到该 pStub 函数入口 , 然后跳转到 pApi 函数的第 6 66 字节开始执行 , 相当于调用了 uint8_t* pApi 参数对应的实际函数 , 即 int clock_gettime(clockid_t clk_id,struct timespec *tp); 函数 ;

size_t size 参数 : 跳转指令占 0xE9,0,0,0,0 5 55 字节 , 这里 将函数入口前 6 66 字节保存下来 ;


函数调用实例 :


/* 这是 hook 标准库中的 clock_gettime 函数的入口方法 , 跳转到自定义的 dn_clock_gettime 方法中 */
hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6);





二、插桩前先保存实际函数入口 6 字节数据


插桩前先 保存函数的入口 6 字节数据 , 因为之后插桩 , 会使用跳转代码 0xE9,0,0,0,0 覆盖函数入口内存 , 被破坏的实际函数 最终还是要执行 , 需要拷贝一下 , 供之后实际函数调用使用 ;


unsigned char code[64] = { 0 };
  /* 插桩前先保存函数的入口 6 字节数据 , 因为之后插桩 , 
  * 会使用跳转代码 0xE9,0,0,0,0 覆盖函数入口内存
  * 该函数最终还是要执行 , 需要拷贝一下 , 供之后实际函数调用使用 
  */
  memcpy(code, pApi, size);






三、在插桩的函数入口写入跳转指令 | 构造拼接桩函数


这里执行了 2 22 次插桩操作 :


第一次是实际函数跳转 : 函数插桩 , pApi 是实际函数 , pUser 是插桩后跳转到的拦截函数 ; 该情况是在 clock_gettime 函数的入口处插入跳转代码 , 跳转到 dn_clock_gettime 函数位置 ;

第二次是构造桩函数 ( 构造拼接桩函数 ) : 在自定义的 dn_clock_gettime 函数中 , 需要调用实际的 clock_gettime 函数 , 这里将 do_clock_gettime 函数构造成 clock_gettime 函数 ;


构造拼接桩函数 : 前 6 字节是保存下来的 clock_gettime 函数的前 6 字节指令 , 执行到第 6 字节时 , 直接跳转到 clock_gettime 函数 执行 , 这样执行拼接的函数 等同于执行 clock_gettime 函数 ;



将 do_clock_gettime 函数构造成 clock_gettime 函数流程 : 执行 do_clock_gettime 方法的第 6 字节的指令时 , 跳转到 clock_gettime 函数的第 6 字节指令位置 , do_clock_gettime 的 0 ~ 6 字节指令是 clock_gettime 实际函数的前 6 字节 , 之所以这么定义 , 是因为 clock_gettime 的前 6 个字节被覆盖为 跳转指令了 ;


调用 do_clock_gettime 方法 , 就相当于调用了 clock_gettime 方法 ;


/* 函数插桩 , pApi 是实际函数 , pUser 是插桩后跳转到的拦截函数 */
  write_code(pApi, pUser);
  /* 
    执行 size + pStub 位置的指令时 , 直接跳转到 size + pApi 位置
    如 : 执行 do_clock_gettime 方法的第 6 字节的指令时 , 跳转到 clock_gettime 函数的第 6 字节指令位置 
    do_clock_gettime 的 0 ~ 6 字节指令是 clock_gettime 实际函数的前 6 字节 , 
    之所以这么定义 , 是因为 clock_gettime 的前 6 个字节被覆盖为 跳转指令了
    调用 do_clock_gettime 方法 , 就相当于调用了 clock_gettime 方法 ; 
  */
  write_code(size + pStub, size + pApi);
  /* 将复制的 6 字节 代码存放到 pStub 函数中的 0 ~ 6 字节位置 */
  memcpy(pStub, code, size);


函数插桩的具体细节在之前的


【Android 逆向】函数拦截 ( 修改内存页属性 | x86 架构插桩拦截 )

【Android 逆向】函数拦截 ( ARM 架构下的插桩拦截 | 完整代码示例 )

博客中有详细的说明 , 先修改内存页属性 , 然后直接修改内存 , 写入跳转汇编指令对应的二进制机器码数据 ;



代码示例 :


/*
 * unsigned char* pFunc
 * unsigned char* pStub
 * 上述两个参数分别是两个函数指针
 * 
 * 注意 : 写完之后要刷新 CPU 高速缓存 , 调用 cache_flush 系统调用函数
 */
int write_code(unsigned char* pFunc, unsigned char* pStub) {
  /* 获取 pFunc 函数入口 , 先获取该函数所在内存页地址 */
  void* pBase = (void*)(0xFFFFF000 & (int)pFunc);
  /* 修改整个内存页属性 , 修改为 可读 | 可写 | 可执行 , 
  * 避免因为内存访问权限问题导致操作失败
  * mprotect 函数只能对整个页内存的属性进行修改 
  * 每个 内存页 大小都是 4KB 
  */
  int ret = mprotect(pBase, 0x1000, PROT_WRITE | PROT_READ | PROT_EXEC);
  /* 修改内存页属性失败的情况 */
  if (ret == -1) {
  perror("mprotect:");
  return -1;
  }
#if defined(__i386__) // arm 情况处理
  /* E9 是 JMP 无条件跳转指令 , 后面 4 字节是跳转的地址 */
  unsigned char code[] = { 0xE9,0,0,0,0 };
  /* 计算 pStub 函数跳转地址 , 目标函数 pStub 地址 - 当前函数 pFunc 地址 - 5 
  * 跳转指令 跳转的是 偏移量 , 不是绝对地址值
  */
  *(unsigned*)(code + 1) = pStub - pFunc - 5;
  /* 将跳转代码拷贝到 pFunc 地址处 , 这是 pFunc 函数的入口地址 */
  memcpy(pFunc, code, sizeof(code));
#else // arm 情况处理
  /* B 无条件跳转指令 */
  unsigned char code[] = { 0x04,0xF0,0x1F,0xE5,0x00,0x00,0x00,0x00 };
  /* arm 的跳转是绝对地址跳转 , 传入 pStub 函数指针即可 */
  *(unsigned*)(code + 4) = (unsigned)pStub;
  /* 将机器码复制到函数开始位置 */
  memcpy(pFunc, code, sizeof(code));
#endif
  return 0;
}


目录
打赏
0
0
0
0
39
分享
相关文章
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
106 2
Android中如何跳转到Wi-Fi开关设置页
本文介绍如何在Android应用开发中使用隐式Intent引导用户至特定系统设置页面,如Wi-Fi设置页,并提供Kotlin代码示例。通过设置Intent的Action属性并检查设备兼容性,可轻松实现跳转功能,提升用户体验。此外,还列举了其他常用设置页面的Intent Action及注意事项。
159 15
Android经典实战之Kotlin的delay函数和Java中的Thread.sleep有什么不同?
本文介绍了 Kotlin 中的 `delay` 函数与 Java 中 `Thread.sleep` 方法的区别。两者均可暂停代码执行,但 `delay` 适用于协程,非阻塞且高效;`Thread.sleep` 则阻塞当前线程。理解这些差异有助于提高程序效率与可读性。
123 1
Android经典实战之跳转到系统设置页面或其他系统应用页面大全
本文首发于公众号“AntDream”,关注获取更多技巧。文章总结了Android开发中跳转至系统设置页面的方法,包括设备信息、Wi-Fi、显示与声音设置等,并涉及应用详情与电池优化页面。通过简单的Intent动作即可实现,需注意权限与版本兼容性。每日进步,尽在“AntDream”。
814 2
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
104 8
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
156 6
Android项目架构设计问题之构造一个Android中的线程池如何解决
Android项目架构设计问题之构造一个Android中的线程池如何解决
52 0
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
83 0
Android 监听Notification 被清除实例代码
Android 监听Notification 被清除实例代码
|
10月前
|
Android kernel 操作gpio
Android kernel 操作gpio
130 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等