之前介绍了Android平台上3种常见的hook方法,而hook的前提是进程注入,通过进程注入我们可以将模块或代码注入到目标进程中以便对其空间内的数据进行操作,本篇文章介绍基于ptrace函数的注入技术。

ptrace函数不熟悉的朋友可以参考我之前写的linux ptrace I和linux ptrace II,跟hook相比,在熟悉了ptrace函数的使用方式后注入过程并不复杂,但在细节的处理上要多加留意,稍有不慎就会造成目标进程发生崩溃。

注入流程如下:

  1. 附加目标进程
  2. 保存寄存器环境
  3. 远程调用mmap函数分配空间
  4. 远程调用dlopen函数注入模块
  5. 远程调用注入模块中的函数
  6. 远程调用munmap函数释放空间
  7. 恢复寄存器环境
  8. 脱离目标进程

整个注入过程都是围绕ptrace函数进行,所以我们需要对ptrace函数进行封装以便实现特定的功能。

1、进程附加

 1 #define CODE_CHECK(code) do {             \
 2     if ((code) != 0) {                    \
 3         return -1;                        \
 4     }                                     \
 5 } while(0)
 6
 7 int ptrace_attach(pid_t pid)
 8 {
 9     int status = 0;
10     CODE_CHECK(ptrace(PTRACE_ATTACH, pid, NULL, NULL));
11     while(waitpid(pid, &status, WUNTRACED) == -1) {
12         if (errno == EINTR) {
13             continue;
14         } else {
15             return -1;
16         }
17     }
18     return 0;
19 }

2、脱离进程

1 int ptrace_detach(pid_t pid)
2 {
3     CODE_CHECK(ptrace(PTRACE_DETACH, pid, NULL, NULL));
4     return 0;
5 }

3、恢复进程运行状态

1 int ptrace_continue(pid_t pid)
2 {
3     CODE_CHECK(ptrace(PTRACE_CONT, pid, NULL, NULL));
4     return 0;
5 }

4、获取寄存器信息

1 int ptrace_getregs(pid_t pid, struct pt_regs *regs)
2 {
3     CODE_CHECK(ptrace(PTRACE_GETREGS, pid, NULL, regs));
4     return 0;
5 }

5、设置寄存器信息

1 int ptrace_setregs(pid_t pid, const struct pt_regs *regs)
2 {
3     CODE_CHECK(ptrace(PTRACE_SETREGS, pid, NULL, regs));
4     return 0;
5 }

6、向目标进程写入数据

int ptrace_writedata(pid_t pid, const void *addr, const void *data, int size)
{int write_count = size / sizeof(long);int remain_size = size % sizeof(long);long write_buffer;for (int i = 0; i < write_count; ++i) {memcpy(&write_buffer, data, sizeof(long));CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer));data = ((long*)data) + 1;addr = ((long*)addr) + 1;}if (remain_size > 0) {write_buffer = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);memcpy(&write_buffer, data, remain_size);CODE_CHECK(ptrace(PTRACE_POKETEXT, pid, addr, write_buffer));}return 0;
}

7、调用目标进程函数

 1 int ptrace_call(pid_t pid, const void* addr, const long *parameters, int num, struct pt_regs *regs)
 2 {
 3     int i;
 4     //根据函数调用约定,前4个参数分别放入r0、r1、r3、r4寄存器,其余放入栈中。
 5     //如果需要传入字符串等信息需要提前将数据写入目标进程。
 6     for (i = 0; i < num && i < 4; ++i) {
 7         regs->uregs[i] = parameters[i];
 8     }
 9     if (i < num) {
10         LOG_INFO("write %d parameters to stack", num - i);
11         regs->ARM_sp -= (num - i) * sizeof(long);
12         CODE_CHECK(ptrace_writedata(pid, (void*)regs->ARM_sp,
13             &parameters[i], (num - i) * sizeof(long)));
14     }
15     //设置pc寄存器
16     regs->ARM_pc = (long)addr;
17     //根据pc寄存器的第0bit位判断目标地址指令集
18     if (regs->ARM_pc & 1) {
19         //for thumb
20         regs->ARM_pc &= (~1u);
21         regs->ARM_cpsr |= CPSR_T_MASK;
22     } else {
23         regs->ARM_cpsr &= ~CPSR_T_MASK;
24     }
25     //设置lr寄存器值为0,当函数返回时进程会接收到异常信号而停止运行。
26     regs->ARM_lr = 0;
27     //设置寄存器信息
28     CODE_CHECK(ptrace_setregs(pid, regs));
29     //恢复目标进行运行
30     CODE_CHECK(ptrace_continue(pid));
31     LOG_INFO("wait for stopping...");
32     int stat = 0;
33     while(waitpid(pid, &stat, WUNTRACED) == -1) {
34         if (errno == EINTR) {
35             continue;
36         } else {
37             return -1;
38         }
39     }
40     if(!WIFSTOPPED(stat)) {
41         LOG_INFO("status is invalid: %d", stat);
42         return -1;
43     }
44     CODE_CHECK(ptrace_getregs(pid, regs));
45     //平衡因传递参数而使用的栈
46     regs->ARM_sp += (num - i) * sizeof(long);
47     return 0;
48 }

8、获取目标进程函数地址

 1 void* get_module_addr(pid_t pid, const char *module_name)
 2 {
 3     FILE *fp;
 4     char file_path[MAX_PATH];
 5     char file_line[MAX_LINE];
 6     if (pid < 0) {
 7         snprintf(file_path, sizeof(file_path), "/proc/self/maps");
 8     } else {
 9         snprintf(file_path, sizeof(file_path), "/proc/%d/maps", pid);
10     }
11     fp = fopen(file_path, "r");
12     if (fp == NULL) {
13         return NULL;
14     }
15     unsigned long addr_start = 0, addr_end = 0;
16     while (fgets(file_line, sizeof(file_line), fp)) {
17         if (strstr(file_line, module_name)) {
18             if (2 == sscanf(file_line, "%8lx-%8lx", &addr_start, &addr_end)) {
19                 break;
20             }
21         }
22     }
23     fclose(fp);
24     LOG_INFO("library :%s %lx-%lx, pid : %d", module_name, addr_start, addr_end, pid);
25     return (void*)addr_start;
26 }
27
28 void* get_remote_func_addr(pid_t pid, const char *module_name, const void *func_local_addr)
29 {
30     void *module_local_addr, *module_remote_addr, *func_remote_addr;
31     module_remote_addr = get_module_addr(pid, module_name);
32     module_local_addr = get_module_addr(-1, module_name);
33     if (module_remote_addr == NULL || module_local_addr == NULL) {
34         return NULL;
35     }
36     return (void*)((unsigned long)func_local_addr - (unsigned long)module_local_addr + (unsigned long)module_remote_addr);
37 }

有了这些封装后的函数,我们便可以着手对注入流程进行实现了。

首先附加目标进程,并获取一些必要函数在目标进程中的地址。

//附加目标进程pid_t pid = 目标进程号;
CODE_CHECK(ptrace_attach(pid));
//备份目标进程寄存器
struct pt_regs current_regs, origin_regs;
CODE_CHECK(ptrace_getregs(pid, &origin_regs));
memcpy(&current_regs, &origin_regs, sizeof(struct pt_regs));
//获取远程函数地址
void *addr_mmap, *addr_munmap, *addr_dlopen, *addr_dlsym;
addr_mmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)mmap);
addr_munmap = get_remote_func_addr(pid, "/system/lib/libc.so", (void*)munmap);
addr_dlopen = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlopen);
ddr_dlsym = get_remote_func_addr(pid, "/system/bin/linker", (void *)dlsym);

接着远程调用目标进程mmap函数分配空间,因为我们在调用目标进程的dlopen等函数时,需要传递字符串信息,所以需要开辟一块空间写入字符串。

long remote_addr, remote_handle, remote_func, remote_ret;
//void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);long remote_params[6];
remote_params[0] = 0;
remote_params[1] = MMAP_SIZE;
remote_params[2] = PROT_READ | PROT_WRITE | PROT_EXEC;
remote_params[3] = MAP_ANONYMOUS | MAP_PRIVATE;
remote_params[4] = 0;
remote_params[5] = 0;
CODE_CHECK(ptrace_call(pid, addr_mmap, remote_params, 6, &current_regs));
//函数返回值在r0寄存器
remote_addr = current_regs.ARM_r0;

之后我们需要远程调用dlopen函数使目标进程加载我们的模块,但在调用之前需要先将模块路径先写入目标进程。

//将模块路径写入目标进程
char *lib_path = "模块路径";
CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, lib_path, strlen(lib_path) + 1));
//void *dlopen(const char *filename, int flag);
remote_params[0] = remote_addr;
remote_params[1] = RTLD_NOW| RTLD_GLOBAL;
LOG_INFO("call remote dlopen");
CODE_CHECK(ptrace_call(pid, addr_dlopen, remote_params, 2, &current_regs));
remote_handle = current_regs.ARM_r0; 

此时已成功将模块注入目标进程,需要远程调用dlsym函数获取模块在加载到目标进程后其中的函数地址,同之前一样需要先将函数名写入目标进程空间。

char *func_name = "函数名";
CODE_CHECK(ptrace_writedata(pid, (void*)remote_addr, func_name, strlen(func_name) + 1));
//void *dlsym(void *handle, const char *symbol);
remote_params[0] = remote_handle;
remote_params[1] = remote_addr;
CODE_CHECK(ptrace_call(pid, addr_dlsym, remote_params, 2, &current_regs));
remote_func = current_regs.ARM_r0;

在获取函数地址后,直接对其进行远程调用,这里假设该函数不需要参数。

//int func();
CODE_CHECK(ptrace_call(pid, (void*)remote_func, NULL, 0, &current_regs));
remote_ret = current_regs.ARM_r0;

这时我们注入的代码已成功执行,可以恢复目标进程的运行状态了。

//释放之前在目标进程分配的空间
//int munmap(void *start, size_t length);
remote_params[0] = remote_addr;
remote_params[1] = MMAP_SIZE;
CODE_CHECK(ptrace_call(pid, addr_munmap, remote_params, 2, &current_regs));
//恢复寄存器信息
CODE_CHECK(ptrace_setregs(pid, &origin_regs));
//脱离目标进程
CODE_CHECK(ptrace_detach(pid));

一些需要包含的头文件:

#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <asm/ptrace.h>
#include <asm/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

转载于:https://www.cnblogs.com/mmmmar/p/8253537.html

Android Ptrace Inject相关推荐

  1. Android SO Inject

    原文链接: http://bbs.byr.cn/#!article/MobileTerminalAT/4328 以前用过LBE,一直觉得十分的彪悍,所以终于忍不住去尝试逆向了.首先工具上面很原始,小弟 ...

  2. android ptrace 检测,Android ptrace函数的实现

    首先看sys/ptrace.h /bionic/libc/include/sys/ptrace.h 我们在调用的时候使用的是PTRACE_的导出符号,glibc也导出了PT_开头的符号.PTRACE_ ...

  3. Android ButterKnife示例

    In this tutorial we're going to discuss the Android ButterKnife tool and look into it's usages. 在本教程 ...

  4. Android Dagger-Hilt 依赖注入

    Author: aa86799@163.com date: 2020-09-09 00:10 文章目录 文档地址 依赖配置 @HiltAndroidApp 将依赖项注入 Android 类 @Inje ...

  5. linux下gem卸载,gem 安装卸载pod

    Linux打包压缩.md Linux下打包压缩命令 下面学习一下压缩和打包的相关命令,首先得先明确两个概念,即:压缩和打包 .我们实际使用中一般是打包和压缩结合的使用,为了学习下面简要的介绍一下压缩文 ...

  6. Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...

  7. 【Android 逆向】ptrace 函数 ( C 标准库 ptrace 函数简介 | ptrace 函数真实作用 )

    文章目录 一.C 标准库 ptrace 函数简介 二.ptrace 函数真实作用 一.C 标准库 ptrace 函数简介 ptrace 函数 : 在 C 标准库 中有一个 ptrace 函数 , 该函 ...

  8. Android so注入( inject)和Hook(挂钩)的实现思路讨论

    本文博客:http://blog.csdn.net/qq1084283172/article/details/54095995 前面的博客中分析一些Android的so注入和Hook目标函数的代码,它 ...

  9. 【Android 逆向】代码调试器开发 ( ptrace 函数 | 读寄存器 | 写寄存器 )

    文章目录 一.读寄存器 二.写寄存器 一.读寄存器 调用 ptrace(PTRACE_GETREGS, m_nPid, NULL, regs) 读取进程运行时的寄存器 ; 读取寄存器时 , 进程必须处 ...

  10. 【Android 逆向】代码调试器开发 ( ptrace 函数 | 向进程内存写出数据 )

    文章目录 一.向进程内存写出数据 二.写出流程 三.完整代码 一.向进程内存写出数据 向内存写出数据 : 每次最多能写出 4 字节 ; ptrace(PTRACE_POKETEXT, m_nPid, ...

最新文章

  1. 05.DOM 查询练习-节点练习
  2. 004_JDK的String类对Comparable接口的实现
  3. exfat最佳单元大小_047|仓储物流自动化系统中的物料单元
  4. 【ArcGIS风暴】ArcGIS中等高线高程标注/注记(打断/消隐)方法案例汇总
  5. arm处理器的历史及现状
  6. 【纪中集训】2019.08.10【省选组】模拟TJ
  7. Qt4_实现其他菜单
  8. js调用数科阅读器_使用 Vue 和 epub.js 制作电子书阅读器
  9. PPT 设置幻灯片母版
  10. 华为大数据生态适配地图
  11. 帆软日期格式转换_自定义函数把阳历转换成阴历
  12. 彩虹商城知识付费程序-优质站,易支付可自定义(货源对接)
  13. 华硕路由器官方固件开机自动运行脚本方法
  14. 抖音运营干货:3个月4抖音号狂吸400W+粉丝
  15. 解决Python打包exe控制台无法粘贴问题
  16. matlab如何新建mat文件_matlab中mat文件的生成和读取
  17. java单例实例对象在springboot中实例化了2次,原因竟然是热部署的锅(记一次神奇的bug)
  18. 梦想天空分外蓝,实习结束总结
  19. Linux--解决上cf慢的问题(通过修改hosts)
  20. ecshop 实现qq,新浪,淘宝登陆接口

热门文章

  1. 高并发下如何保证数据库和缓存双写一致性?
  2. 15张图带你彻底明白spring循环依赖,再也不用怕了
  3. lambda表达式学习使用实例
  4. Ubuntu下多个gcc版本之间的切换
  5. python爬虫学习整理——爬虫入门(1)
  6. github 新建远程仓库 及 删除远程仓库
  7. 《软件工程(第4版?修订版)》—第2章2.7节本章对单个开发人员的意义
  8. 基于 Arduino 的 RFID 识别实验
  9. 【实用】CSS Border使用小分享——盒模型
  10. Iframe的基础应用——关于Iframe刷页问题的两种方法