-
-
[原创]ptrace注入代码在不同平台的区别(ARM64、x86-64、MIPS64)
-
发表于: 2026-4-5 00:19 4890
-
⚠️ 免责声明:本文仅供学习和研究目的,介绍 ptrace 在 Linux 系统编程中的技术细节。ptrace 是 Linux 提供的标准系统调用,广泛用于调试器(如 gdb)开发。使用 ptrace 需要 root 权限,且应遵守相关法律法规。请勿将本文技术用于非法目的。
之前学过 ARM64(aarch64) 的 ptrace 注入,最近尝试使用 ptrace 在 x86-64 上注入时,发现两者实现上有些区别,在ARM64上能成功注入的代码,修改相关寄存器和调用约定之后在x86-64上却一直注入失败 (dlopen返回一个很小的值,比如0x8e,正常情况下dlopen应该返回一个堆地址)。本文用来说明两个平台之间的ptrace注入代码区别,方便后面参考。2026.4.20 补充MIPS64的ptrace注入
ptrace 是 Linux 提供的一个系统调用,允许一个进程(tracer)观察和控制另一个进程(tracee)的执行。它是调试器(如 gdb)和进程注入技术的基础。如果想要操作其它进程,需要有root权限。
ptrace 的核心能力包括:
基本的函数原型:
ptrace 注入的核心思路是:附加到目标进程,让目标进程调用 dlopen 来加载我们指定的 so 库,从而在目标进程中执行我们的代码。
通用的注入流程如下:
附加目标进程:调用 ptrace(PTRACE_ATTACH, pid, ...) 附加到目标进程,目标进程会收到 SIGSTOP 信号暂停执行。
保存寄存器现场:调用 ptrace(PTRACE_GETREGS, pid, ...) 保存目标进程当前的寄存器状态,以便注入完成后恢复。
获取目标进程中的关键函数地址:在目标进程的内存空间中找到 dlopen、dlsym、dlclose 等函数的地址。由于 ASLR 的存在,同一个库在不同进程中的加载地址是不同的,但同一个库内部函数的偏移是固定的。因此可以通过以下公式计算:
具体步骤:
这个方法的前提是注入工具和目标进程加载了同一版本的库,这在同一台机器上通常是成立的。
向目标进程内存写入参数:将 so 库路径字符串等参数写入目标进程的内存中(通常写入栈上或 mmap 的内存区域)。
设置寄存器,调用 dlopen:按照目标平台的调用约定设置寄存器和栈,使目标进程执行 dlopen 调用。
触发执行并等待返回:让目标进程继续执行,等待调用完成。
获取返回值:读取寄存器获取 dlopen 的返回值(so 库的句柄/基址)。
恢复寄存器现场并分离:将之前保存的寄存器状态恢复,调用 ptrace(PTRACE_DETACH, pid, ...) 分离目标进程,目标进程继续正常执行。
虽然整体流程在两个平台上是一致的,但第 5 步和第 6 步的具体实现因平台差异而有所不同,这也是本文的重点。
ARM64 调用约定:
ARM64 使用 struct user_pt_regs 或通过 iovec 配合 PTRACE_GETREGSET 来获取寄存器:
在 ARM64 上调用 dlopen 的设置比较直观:
ARM64 的关键点:
下面是完整的 ARM64 (Android) 上的 ptrace 注入代码(如果要注入Android APP,需要将so放在app对应的lib目录下,因为有selinux的限制)。
上面的调用约定和寄存器设置看起来很简单,但在 Android 手机上实际测试时发现,不能直接把 SO 路径写入目标进程的栈空间。
最初的实现:
运行结果:dlopen 的返回值恰好等于我们传入的 path_addr,说明 x0 根本没有被修改——dlopen 没有真正执行。
原因:在 Android 上,dlopen 内部的 linker 实现对内存区域有要求。直接写在栈上的路径字符串,可能因为 linker 内部的内存访问检查等原因导致执行异常。
解决方法:先在目标进程中调用 mmap 分配一块独立的内存,再将 SO 路径写入这块内存:
x86-64 的 ptrace 注入流程和 ARM64 基本一致,但在调用约定和返回地址处理上有重要差异。下面按照相同的结构介绍,并说明两个平台的关键区别。
x86-64 调用约定:
x86-64 使用 struct user_regs_struct,通过 PTRACE_GETREGS 获取:
按照 ARM64 的思路,直接设置寄存器调用 dlopen:
完整代码如下:
使用上述代码调用 dlopen,会返回异常值:
handle = 0xdb 明显错误——正常情况下 dlopen 应该返回一个较大的堆地址。直接设置寄存器的方式在 x86-64 上失败了。
为什么 mmap 可以成功,dlopen 不行?
这个问题研究了很久也没有找到原因,下面的内容是问的AI,有大佬知道的话可以指点一下。
在 ARM64 实现中,我们通过设置 lr = 0 让函数返回时触发 SIGSEGV。x86-64 没有 lr 寄存器,但可以通过手动压栈来设置返回地址:
这种方式对 mmap 有效,但对 dlopen 失败了。原因在于两者的复杂度不同:
mmap 是内核直接实现的系统调用,内部逻辑简单,即使栈状态不完美也能正常工作。
dlopen 则完全不同:
这些操作都要求严格的栈 16 字节对齐,而手动操作 rsp 很难保证这一点。即使对齐正确,dlopen 内部调用的其他函数也可能因为栈状态异常而崩溃,导致返回错误值(如 0xdb)。
失败原因总结:
解决方案:在目标进程内存中写入 trampoline 代码,让目标进程通过trampoline调用dlopen。
优势:
完整代码如下:
之前尝试了x86-64和ARM64下的注入,这里补充一下MIPS64下的注入。
这次测试环境不是实体 MIPS64 机器,而是用 Buildroot + QEMU system mode 搭出来的。其中,Buildroot用来编译出MIPS64的内核以及文件系统,QEMU用来模拟MIPS64的指令。
我使用的是:
直接使用 Buildroot 自带的 QEMU MIPS64 默认配置:
这里选的是 MIPS64 big-endian 的 malta 板级模型。执行完成后会生成 .config。
Buildroot 默认的 SSH 服务需要在配置文件中手动开启:
如果宿主机环境变量有问题,先清理 PATH:
然后编译:
编译完成后,产物在:
我这边最终得到的核心文件是:
QEMU 在 user-mode networking 下,通过ifconfig查看的网段一般是:
这个 10.0.2.x 网段是 QEMU 自带的虚拟 NAT 网段,宿主机物理网卡上不会看到这个网段,因此需要将虚拟机的端口通过命令转发出来。
Buildroot 已经生成了启动脚本,但是没有添加网络配置,不通过脚本启动而是直接使用下面的命令运行(这里将宿主机的2222端口映射到QEMU虚拟机中的22端口,用于SSH服务):
启动后进入串口终端,登录提示类似:
Buildroot 默认 root 无密码,可以直接登录。
SSH登录需要设置密码,并修改SSH配置文件允许以root用户登录。
配置完成之后,在宿主机上使用ssh -p2222 root@localhost 即可通过SSH访问QEMU虚拟机。使用scp -P2222 file root@localhost:/root/ 即可拷贝文件到QEMU虚拟机。
一开始我用宿主机系统自带的 mips64-linux-gnuabi64-gcc,结果编出来的程序在 Buildroot 系统里运行时报:
原因是:交叉编译器的运行时库和 Buildroot 根文件系统不一致。
最终改成直接使用 Buildroot 产出的交叉工具链:output/host/bin/mips64-buildroot-linux-gnu-gcc,这样编出来的程序才能和目标系统的 libc、动态链接器保持一致。
MIPS64 使用 N64 ABI,调用约定:
MIPS64 使用 struct pt_regs(定义在 <asm/ptrace.h> 中),通过 PTRACE_GETREGS / PTRACE_SETREGS 直接读写,不需要像 ARM64 那样走 iovec + REGSET:
在 MIPS64 上发起远程调用的关键设置:
MIPS64 上的关键点:
这次在 MIPS64(buildroot + qemu)环境下做 ptrace 注入,最终结论是:远程调用框架本身可以跑通,但 mmap 不能直接照搬其它架构的 libc 调用方式,最稳妥的方案是“直接 syscall mmap + 远程 dlopen”。
MIPS64 上最终跑通的流程是:
也就是说:
在 MIPS 上远程调用 libc 函数时,仅设置 cp0_epc 不够,还需要:
原因是 MIPS 的 PIC(位置无关代码)通常依赖 $t9 来访问 GOT / 做函数内跳转。
代码示例如下:
函数被调用时 t9 寄存器保存着该函数的运行时地址(通过 jalr t9 调用约定)。GP(用于定位GOT表的位置,通常为GOT表加上一定偏移的位置GOT + 0x7ff0)的计算依赖链接器提供的一个固定偏移量 _gp_disp:
为什么需要这样做:
如果只改 pc,不改 $t9,很容易出现:
在目标进程里直接本地调用mmap是成功的:
但是通过 ptrace 远程调用 libc 的 mmap() 时,虽然:
返回值却始终是:
继续读取远程 errno 后发现:
这说明:
既然远程 libc mmap() wrapper 不可靠,最终改成了:
MIPS64 Linux 上 mmap 的 syscall 号是:
做法是:
切到 syscall 之后,mmap 立即成功,后续:
这次 MIPS64 ptrace 注入的几个关键经验可以总结为:
最终可工作的组合是:
这套组合在当前的 buildroot qemu mips64 环境下已经验证可用,完整代码如下:
总结三个平台在 ptrace 注入上的关键区别:
核心区别主要体现在四点:
ptrace 注入的核心流程在不同架构上是一致的,但真正决定是否稳定成功的,是各自 ABI、返回地址机制和运行时实现细节:
| 对比项 | mmap | dlopen |
|---|---|---|
| 实现复杂度 | 简单,直接 syscall | 复杂,涉及动态链接器 |
| 内部调用链 | 几乎无 | 多层(_dl_open → 锁操作 → malloc → 构造函数) |
| 栈对齐要求 | 宽松 | 严格(16字节对齐) |
| 栈状态依赖 | 低 | 高 |
| 对比项 | ARM64 | x86-64 | MIPS64 (N64) |
|---|---|---|---|
| 整数参数传递 | x0 - x7 | rdi, rsi, rdx, rcx, r8, r9 | a0−a7 (regs[4]-regs[11]) |
| 返回值寄存器 | x0 | rax | $v0 (regs[2]) |
| 程序计数器 | pc | rip | cp0_epc |
| 栈顶指针 | sp | rsp | $sp (regs[29]) |
| 额外关键寄存器 | lr (x30) | 无 | ra(regs[31])、∗∗t9 (regs[25])** |
| 返回地址机制 | 链接寄存器 lr | 栈(call 压栈 / ret 弹栈) | 链接寄存器 $ra |
| 远程函数返回判定 | 常用 lr = 0,返回后触发 SIGSEGV | 推荐 trampoline + int3 | 常用 $ra = 0,返回后触发 SIGSEGV |
| 寄存器获取方式 | PTRACE_GETREGSET + iovec | PTRACE_GETREGS | PTRACE_GETREGS |
| mmap 分配方式 | 远程 libc 调用可行 | 远程 libc 调用通常可行 | libc wrapper 不可靠,需直接 syscall |
| dlopen 调用方式 | 直接设置寄存器即可 | 推荐 trampoline | 直接设置寄存器(注意 $t9) |
| 最容易踩坑 | Android linker/SELinux 限制 | 栈对齐与返回地址处理 | 忘记设置 $t9;libc mmap wrapper 返回 EBADF |
| 推荐实现方式 | 直接设置寄存器 | 使用 trampoline 代码 | $t9 + syscall mmap + 远程 dlopen |
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
远程函数地址 = 本进程函数地址 - 本进程模块基址 + 目标进程模块基址
struct user_pt_regs {
__u64 regs[31]; // x0 - x30
__u64 sp;
__u64 pc;
__u64 pstate;
};
// 获取寄存器
struct iovec iov;
struct user_pt_regs regs;
iov.iov_base = ®s;
iov.iov_len = sizeof(regs);
ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov);
// 设置参数
regs.regs[0] = so_path_addr; // x0 = 第一个参数:so库路径地址
regs.regs[1] = RTLD_NOW; // x1 = 第二个参数:dlopen flags
regs.pc = dlopen_addr; // pc = dlopen 函数地址
regs.regs[30] = 0; // lr = 0,使 dlopen 返回后触发 SIGSEGV
ptrace(PTRACE_SETREGSET, pid, (void*)NT_PRSTATUS, &iov);
ptrace(PTRACE_CONT, pid, NULL, NULL);
// 等待目标进程触发 SIGSEGV(因为 lr = 0,dlopen 返回后会跳转到地址 0)
waitpid(pid, &status, 0);
// injector_arm64.c - ARM64 ptrace 注入工具
// 用法: ./injector_arm64 <pid> <so_absolute_path>
// 需要 root 权限
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <sys/mman.h>
#include <linux/elf.h>
#include <dlfcn.h>
#include <asm/ptrace.h>
// 获取寄存器(ARM64 使用 PTRACE_GETREGSET + iovec)
static int get_regs(pid_t pid, struct user_pt_regs *regs) {
struct iovec iov;
iov.iov_base = regs;
iov.iov_len = sizeof(*regs);
if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) < 0) {
perror("PTRACE_GETREGSET");
return -1;
}
return 0;
}
// 设置寄存器
static int set_regs(pid_t pid, struct user_pt_regs *regs) {
struct iovec iov;
iov.iov_base = regs;
iov.iov_len = sizeof(*regs);
if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) < 0) {
perror("PTRACE_SETREGSET");
return -1;
}
return 0;
}
// 从 /proc/pid/maps 中查找指定库的基地址
static long get_module_base(pid_t pid, const char *module_name) {
char path[256];
char line[512];
long base = 0;
if (pid == 0)
snprintf(path, sizeof(path), "/proc/self/maps");
else
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
FILE *fp = fopen(path, "r");
if (!fp) { perror("fopen maps"); return 0; }
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
base = strtol(line, NULL, 16);
break;
}
}
fclose(fp);
return base;
}
// 通过地址查找其所在的模块名和模块基址
static int find_module_by_addr(void *addr, char *module_name, size_t name_len, long *base) {
char line[512];
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return -1;
unsigned long target = (unsigned long)addr;
char found_module[256] = {0};
while (fgets(line, sizeof(line), fp)) {
unsigned long start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
if (target >= start && target < end) {
char *path = strrchr(line, '/');
if (path) {
char *nl = strchr(path, '\n');
if (nl) *nl = '\0';
strncpy(found_module, path, sizeof(found_module) - 1);
}
break;
}
}
fclose(fp);
if (found_module[0] == '\0') return -1;
*base = get_module_base(0, found_module);
strncpy(module_name, found_module, name_len - 1);
module_name[name_len - 1] = '\0';
return 0;
}
// 计算目标进程中某函数的地址(自动检测所在模块)
static long get_remote_func_addr(pid_t pid, void *local_func) {
char module_name[256];
long local_base;
if (find_module_by_addr(local_func, module_name, sizeof(module_name), &local_base) < 0) {
fprintf(stderr, "Failed to find module for addr %p\n", local_func);
return 0;
}
long remote_base = get_module_base(pid, module_name);
if (!local_base || !remote_base) {
fprintf(stderr, "Failed to get module base for %s\n", module_name);
return 0;
}
return remote_base + ((long)local_func - local_base);
}
// 在远程进程中调用一个函数,最多支持 6 个参数
// 原理: 设置 x0-x5 为参数, pc 为函数地址, lr = 0
// PTRACE_CONT 后等待 SIGSEGV (因为 lr=0, ret 跳转到地址 0)
// 读取 x0 获取返回值
static long call_remote_func(pid_t pid, struct user_pt_regs *orig_regs,
long func_addr,
long arg0, long arg1, long arg2,
long arg3, long arg4, long arg5) {
// 基于原始寄存器来设置,保证 sp 等关键寄存器正确
struct user_pt_regs regs;
memcpy(®s, orig_regs, sizeof(regs));
regs.regs[0] = arg0; // x0 - x5: 参数
regs.regs[1] = arg1;
regs.regs[2] = arg2;
regs.regs[3] = arg3;
regs.regs[4] = arg4;
regs.regs[5] = arg5;
regs.pc = func_addr; // pc = 目标函数地址
regs.regs[30] = 0; // lr = 0,函数返回时触发 SIGSEGV
if (set_regs(pid, ®s) < 0) return -1;
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
return -1;
}
int status;
waitpid(pid, &status, WUNTRACED);
// 读取返回值 (ARM64 返回值在 x0)
if (get_regs(pid, ®s) < 0) return -1;
return (long)regs.regs[0];
}
// 向目标进程写入数据(以 long 为单位,按需补齐)
static int ptrace_write_data(pid_t pid, unsigned long addr, const void *data, size_t len) {
const unsigned char *src = (const unsigned char *)data;
size_t i = 0;
for (i = 0; i + sizeof(long) <= len; i += sizeof(long)) {
long val;
memcpy(&val, src + i, sizeof(long));
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA");
return -1;
}
}
if (i < len) {
long val = ptrace(PTRACE_PEEKDATA, pid, (void *)(addr + i), NULL);
memcpy(&val, src + i, len - i);
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA tail");
return -1;
}
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <pid> <so_absolute_path>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
const char *so_path = argv[2];
int status;
// 1. 附加目标进程
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("PTRACE_ATTACH");
return 1;
}
waitpid(pid, &status, WUNTRACED);
// 2. 保存原始寄存器
struct user_pt_regs orig_regs;
if (get_regs(pid, &orig_regs) < 0) {
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
// 3. 在远程进程中调用 mmap 分配内存
long remote_mmap = get_remote_func_addr(pid, (void *)mmap);
long mmap_result = call_remote_func(pid, &orig_regs, remote_mmap,
0, 0x1000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mmap_result == -1 || mmap_result == 0) {
fprintf(stderr, "[-] 远程 mmap 失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 mmap 成功, 地址: 0x%lx\n", mmap_result);
// 4. 将 so 路径写入 mmap 分配的内存
ptrace_write_data(pid, (unsigned long)mmap_result, so_path, strlen(so_path) + 1);
// 5. 获取远程 dlopen 地址并调用
void *local_dlopen = dlsym(RTLD_DEFAULT, "dlopen");
long remote_dlopen = get_remote_func_addr(pid, local_dlopen);
long dlopen_ret = call_remote_func(pid, &orig_regs, remote_dlopen,
mmap_result, RTLD_NOW, 0, 0, 0, 0);
printf("[+] dlopen 返回值: 0x%lx\n", dlopen_ret);
if (dlopen_ret == 0)
printf("[-] dlopen 失败!\n");
else
printf("[+] 注入成功!handle = 0x%lx\n", dlopen_ret);
// 6. 恢复寄存器现场并分离
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 0;
}
// ❌ 错误做法:直接写到栈上
unsigned long path_addr = (regs.sp - path_len) & ~0xF;
regs.sp = path_addr - 128;
ptrace_write_data(pid, path_addr, so_path, path_len);
regs.regs[0] = path_addr; // x0 = so 路径
regs.regs[1] = RTLD_NOW; // x1 = flags
regs.pc = dlopen_addr;
regs.regs[30] = 0; // lr = 0
// ✅ 正确做法:先 mmap 分配内存,再写入路径
// 1. 远程调用 mmap
long mmap_result = call_remote_func(pid, &orig_regs, remote_mmap,
0, 0x1000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// 2. 将路径写入 mmap 分配的内存
ptrace_write_data(pid, mmap_result, so_path, path_len);
// 3. 用 mmap 的地址作为 dlopen 的参数
long dlopen_ret = call_remote_func(pid, &orig_regs, remote_dlopen,
mmap_result, RTLD_NOW, 0, 0, 0, 0);
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
// 设置参数
regs.rdi = path_addr; // rdi = so 路径
regs.rsi = RTLD_NOW; // rsi = flags
regs.rip = dlopen_addr; // rip = dlopen 函数地址
// 模拟 call 指令:将返回地址压栈
regs.rsp -= 8;
ptrace(PTRACE_POKEDATA, pid, (void *)regs.rsp, (void *)0); // 返回地址设为 0
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
ptrace(PTRACE_CONT, pid, NULL, NULL);
// 等待 SIGSEGV(因为返回地址为 0)
waitpid(pid, &status, 0);
// x86-64 ptrace 注入工具
// 用法: ./injector_x86_64 <pid> <so_absolute_path>
// 需要 root 权限
// 参考 ARM64 实现:先 mmap 分配内存,再写入 SO 路径,最后调用 dlopen
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <dlfcn.h>
// 获取寄存器
static int get_regs(pid_t pid, struct user_regs_struct *regs) {
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_GETREGS");
return -1;
}
return 0;
}
// 设置寄存器
static int set_regs(pid_t pid, struct user_regs_struct *regs) {
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_SETREGS");
return -1;
}
return 0;
}
// 从 /proc/pid/maps 中查找指定库的基地址
static long get_module_base(pid_t pid, const char *module_name) {
char path[256];
char line[512];
long base = 0;
if (pid == 0)
snprintf(path, sizeof(path), "/proc/self/maps");
else
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
FILE *fp = fopen(path, "r");
if (!fp) { perror("fopen maps"); return 0; }
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
base = strtol(line, NULL, 16);
break;
}
}
fclose(fp);
return base;
}
// 通过地址查找其所在的模块名和模块基址
static int find_module_by_addr(void *addr, char *module_name, size_t name_len, long *base) {
char line[512];
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return -1;
unsigned long target = (unsigned long)addr;
char found_module[256] = {0};
while (fgets(line, sizeof(line), fp)) {
unsigned long start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
if (target >= start && target < end) {
char *path = strrchr(line, '/');
if (path) {
char *nl = strchr(path, '\n');
if (nl) *nl = '\0';
strncpy(found_module, path, sizeof(found_module) - 1);
}
break;
}
}
fclose(fp);
if (found_module[0] == '\0') return -1;
*base = get_module_base(0, found_module);
strncpy(module_name, found_module, name_len - 1);
module_name[name_len - 1] = '\0';
return 0;
}
// 计算目标进程中某函数的地址(自动检测所在模块)
static long get_remote_func_addr(pid_t pid, void *local_func) {
char module_name[256];
long local_base;
if (find_module_by_addr(local_func, module_name, sizeof(module_name), &local_base) < 0) {
fprintf(stderr, "Failed to find module for addr %p\n", local_func);
return 0;
}
long remote_base = get_module_base(pid, module_name);
if (!local_base || !remote_base) {
fprintf(stderr, "Failed to get module base for %s\n", module_name);
return 0;
}
printf("[*] %s: local_base=0x%lx, remote_base=0x%lx\n",
module_name, local_base, remote_base);
return remote_base + ((long)local_func - local_base);
}
// 在远程进程中调用一个函数,最多支持 6 个参数
// 原理: 设置 rdi/rsi/rdx/rcx/r8/r9 为参数, rip 为函数地址
// 手动将返回地址 0 压栈 (模拟 call 指令)
// PTRACE_CONT 后等待 SIGSEGV
// 读取 rax 获取返回值
static long call_remote_func(pid_t pid, struct user_regs_struct *orig_regs,
long func_addr,
long arg0, long arg1, long arg2,
long arg3, long arg4, long arg5) {
// 基于原始寄存器来设置,保证 rsp 等关键寄存器正确
struct user_regs_struct regs;
memcpy(®s, orig_regs, sizeof(regs));
// x86-64 调用约定: 前6个整数参数依次通过 rdi, rsi, rdx, rcx, r8, r9 传递
regs.rdi = arg0;
regs.rsi = arg1;
regs.rdx = arg2;
regs.rcx = arg3;
regs.r8 = arg4;
regs.r9 = arg5;
// 设置 rip 为目标函数地址
regs.rip = func_addr;
// ★ x86-64 关键步骤: 模拟 call 指令,将返回地址压栈
// x86-64 没有链接寄存器,返回地址保存在栈上
// call 指令等价于: push 返回地址; jmp 函数地址
regs.rsp -= 8;
if (ptrace(PTRACE_POKEDATA, pid, (void *)regs.rsp, (void *)0) < 0) {
perror("PTRACE_POKEDATA (push return addr)");
return -1;
}
if (set_regs(pid, ®s) < 0) return -1;
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
return -1;
}
int status;
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
// 预期的 SIGSEGV,函数已执行完毕
} else if (WIFSTOPPED(status)) {
printf("[!] 收到非预期信号: %d\n", WSTOPSIG(status));
}
// 读取返回值 (x86-64 返回值在 rax)
if (get_regs(pid, ®s) < 0) return -1;
return (long)regs.rax;
}
// 向目标进程写入数据(以 long 为单位,按需补齐)
static int ptrace_write_data(pid_t pid, unsigned long addr, const void *data, size_t len) {
const unsigned char *src = (const unsigned char *)data;
size_t i = 0;
for (i = 0; i + sizeof(long) <= len; i += sizeof(long)) {
long val;
memcpy(&val, src + i, sizeof(long));
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA");
return -1;
}
}
if (i < len) {
long val = ptrace(PTRACE_PEEKDATA, pid, (void *)(addr + i), NULL);
memcpy(&val, src + i, len - i);
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA tail");
return -1;
}
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <pid> <so_absolute_path>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
const char *so_path = argv[2];
int status;
printf("[*] 目标进程: %d\n", pid);
printf("[*] 注入 so: %s\n", so_path);
// 1. 附加目标进程
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("PTRACE_ATTACH");
return 1;
}
waitpid(pid, &status, WUNTRACED);
printf("[+] 已附加到目标进程\n");
// 2. 保存原始寄存器
struct user_regs_struct orig_regs;
if (get_regs(pid, &orig_regs) < 0) {
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 已保存寄存器现场\n");
// 3. 在远程进程中调用 mmap 分配内存
long remote_mmap = get_remote_func_addr(pid, (void *)mmap);
if (!remote_mmap) {
fprintf(stderr, "[-] 获取远程 mmap 地址失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 mmap 地址: 0x%lx\n", remote_mmap);
long mmap_result = call_remote_func(pid, &orig_regs, remote_mmap,
0, // addr = NULL
0x1000, // length = 4096
PROT_READ | PROT_WRITE, // prot
MAP_PRIVATE | MAP_ANONYMOUS, // flags
-1, // fd
0); // offset
if (mmap_result == -1 || mmap_result == 0) {
fprintf(stderr, "[-] 远程 mmap 失败, 返回: 0x%lx\n", mmap_result);
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 mmap 成功, 地址: 0x%lx\n", mmap_result);
// 4. 将 so 路径写入 mmap 分配的内存
size_t path_len = strlen(so_path) + 1;
if (ptrace_write_data(pid, (unsigned long)mmap_result, so_path, path_len) < 0) {
fprintf(stderr, "[-] 写入 so 路径失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] so 路径已写入远程内存 @ 0x%lx\n", mmap_result);
// 5. 获取远程 dlopen 地址并调用
void *local_dlopen = dlsym(RTLD_DEFAULT, "dlopen");
if (!local_dlopen) {
fprintf(stderr, "[-] 无法获取 dlopen 地址\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[*] 本地 dlopen 地址: %p\n", local_dlopen);
long remote_dlopen = get_remote_func_addr(pid, local_dlopen);
if (!remote_dlopen) {
fprintf(stderr, "[-] 获取远程 dlopen 地址失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 dlopen 地址: 0x%lx\n", remote_dlopen);
long dlopen_ret = call_remote_func(pid, &orig_regs, remote_dlopen,
(long)mmap_result, // rdi = so 路径地址 (mmap 分配的)
RTLD_NOW, // rsi = flags
0, 0, 0, 0);
printf("[+] dlopen 返回值: 0x%lx\n", dlopen_ret);
if (dlopen_ret == 0) {
printf("[-] dlopen 失败!\n");
} else {
printf("[+] 注入成功!handle = 0x%lx\n", dlopen_ret);
}
// 6. 恢复寄存器现场并分离
if (set_regs(pid, &orig_regs) < 0) {
perror("set_regs restore");
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
printf("[+] 已恢复现场并分离目标进程\n");
return 0;
}
[*] 目标进程: 1114
[*] 注入 so: /path/to/inject.so
[+] 已附加到目标进程
[+] 已保存寄存器现场
[*] /libc.so.6: local_base=0x7bb2c7800000, remote_base=0x75a5dc400000
[+] 远程 mmap 地址: 0x75a5dc51ea90
[+] 远程 mmap 成功, 地址: 0x75a5dc7a5000
[+] so 路径已写入远程内存 @ 0x75a5dc7a5000
[*] 本地 dlopen 地址: 0x7bb2c7890680
[*] /libc.so.6: local_base=0x7bb2c7800000, remote_base=0x75a5dc400000
[+] 远程 dlopen 地址: 0x75a5dc490680
[+] dlopen 返回值: 0xdb
[+] 注入成功!handle = 0xdb
[+] 已恢复现场并分离目标进程
// 手动设置返回地址为 0
regs.rsp -= 8;
ptrace(PTRACE_POKEDATA, pid, (void *)regs.rsp, (void *)0);
// 使用 trampoline 调用dlopen(成功)
// 1. mmap 分配 RWX 内存
// 2. 前半部分存 SO 路径
// 3. 后半部分存 trampoline: call rax; int3
// trampoline 只有 3 字节:
unsigned char trampoline[3] = {
0xFF, 0xD0, // call rax (调用 dlopen,自动压栈返回地址)
0xCC // int3 (dlopen 返回后触发 SIGTRAP)
};
// 执行流程:
// 1. call rax 自动将返回地址(即 int3 的地址)压入栈,跳转到 dlopen
// 2. dlopen 执行完毕 ret,弹出返回地址,回到 int3
// 3. int3 触发 SIGTRAP,注入器读取返回值
// x86-64 ptrace 注入工具 (v2)
// 用法: ./injector_x86_64_v2 <pid> <so_absolute_path>
// 需要 root 权限
// 思路:
// 1. mmap 分配一块 RWX 内存
// - 前半部分存 SO 路径字符串
// - 后半部分存 trampoline: call dlopen; int3
// 2. 设置 regs.rdi=路径, regs.rsi=flags, regs.rax=dlopen, regs.rip=trampoline
// 3. 跳转到 trampoline 执行,dlopen 返回后执行 int3 触发 SIGTRAP
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <dlfcn.h>
// 获取寄存器
static int get_regs(pid_t pid, struct user_regs_struct *regs) {
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_GETREGS");
return -1;
}
return 0;
}
// 设置寄存器
static int set_regs(pid_t pid, struct user_regs_struct *regs) {
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_SETREGS");
return -1;
}
return 0;
}
// 从 /proc/pid/maps 中查找指定库的基地址
static long get_module_base(pid_t pid, const char *module_name) {
char path[256];
char line[512];
long base = 0;
if (pid == 0) {
snprintf(path, sizeof(path), "/proc/self/maps");
} else {
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
}
FILE *fp = fopen(path, "r");
if (!fp) {
perror("fopen maps");
return 0;
}
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
base = strtol(line, NULL, 16);
break;
}
}
fclose(fp);
return base;
}
// 通过地址查找其所在的模块名和模块基址
// 扫描 /proc/self/maps,找到包含 addr 的映射区间,提取模块路径
static int find_module_by_addr(void *addr, char *module_name, size_t name_len, long *base) {
char line[512];
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return -1;
unsigned long target = (unsigned long)addr;
char found_module[256] = {0};
while (fgets(line, sizeof(line), fp)) {
unsigned long start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
if (target >= start && target < end) {
// 提取模块路径 (maps 格式: addr-addr perms offset dev inode pathname)
char *path = strrchr(line, '/');
if (path) {
// 去掉换行
char *nl = strchr(path, '\n');
if (nl) *nl = '\0';
strncpy(found_module, path, sizeof(found_module) - 1);
}
break;
}
}
fclose(fp);
if (found_module[0] == '\0') return -1;
// 找到模块后,获取该模块的基地址(第一个映射的起始地址)
*base = get_module_base(0, found_module);
strncpy(module_name, found_module, name_len - 1);
module_name[name_len - 1] = '\0';
return 0;
}
// 计算目标进程中某函数的地址
// 自动检测函数所在模块,避免模块名猜错导致地址计算错误
static long get_remote_func_addr(pid_t pid, void *local_func, const char **out_module) {
char module_name[256];
long local_base;
if (find_module_by_addr(local_func, module_name, sizeof(module_name), &local_base) < 0) {
fprintf(stderr, "Failed to find module for addr %p\n", local_func);
return 0;
}
long remote_base = get_module_base(pid, module_name);
if (!local_base || !remote_base) {
fprintf(stderr, "Failed to get module base for %s (local: 0x%lx, remote: 0x%lx)\n",
module_name, local_base, remote_base);
return 0;
}
if (out_module) *out_module = strdup(module_name);
long offset = (long)local_func - local_base;
printf("[*] %s: local_base=0x%lx, remote_base=0x%lx, offset=0x%lx\n",
module_name, local_base, remote_base, offset);
return remote_base + offset;
}
// 向目标进程写入数据(以 long 为单位,按需补齐)
static int ptrace_write_data(pid_t pid, unsigned long addr, const void *data, size_t len) {
const unsigned char *src = (const unsigned char *)data;
size_t i = 0;
for (i = 0; i + sizeof(long) <= len; i += sizeof(long)) {
long val;
memcpy(&val, src + i, sizeof(long));
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA");
return -1;
}
}
if (i < len) {
long val = ptrace(PTRACE_PEEKDATA, pid, (void *)(addr + i), NULL);
memcpy(&val, src + i, len - i);
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA tail");
return -1;
}
}
return 0;
}
// 在远程进程中调用 mmap(addr=NULL, size, prot, flags, fd=-1, offset=0)
// 原理: 设置 rdi, rsi, rdx, rcx, r8, r9 为参数, rip 为函数地址, rsp 指向的返回地址设为 0
// PTRACE_CONT 后等待 SIGSEGV (因为 ret 跳转到地址 0)
// 读取 rax 获取返回值
static long remote_mmap(pid_t pid, struct user_regs_struct *orig_regs, long size, int prot, int flags) {
// 获取远程 mmap 地址
long remote_mmap_addr = get_remote_func_addr(pid, (void *)mmap, NULL);
if (!remote_mmap_addr) return -1;
// 基于原始寄存器来设置,保证 rsp 等关键寄存器正确
struct user_regs_struct regs;
memcpy(®s, orig_regs, sizeof(regs));
// 设置参数 (x86-64 System V ABI: rdi, rsi, rdx, rcx, r8, r9 传参)
// mmap(NULL, size, prot, flags, fd=-1, offset=0)
regs.rdi = 0; // addr = NULL
regs.rsi = size; // length
regs.rdx = prot; // prot
regs.rcx = flags; // flags
regs.r8 = -1; // fd
regs.r9 = 0; // offset
// 设置 rip 为 mmap 地址
regs.rip = remote_mmap_addr;
// 关键步骤: 分配一个栈空间,将返回地址设为 0
// 函数执行完 ret 后会跳转到地址 0,触发 SIGSEGV
regs.rsp = ((orig_regs->rsp - 256) & ~0xF) - 8;
// 在栈顶写入 0 作为返回地址
long ret_addr = 0;
if (ptrace(PTRACE_POKEDATA, pid, (void *)regs.rsp, (void *)ret_addr) < 0) {
perror("PTRACE_POKEDATA (ret_addr)");
return -1;
}
if (set_regs(pid, ®s) < 0) return -1;
// 继续执行
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
return -1;
}
int status;
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
// 预期的 SIGSEGV,函数已执行完毕
} else if (WIFSTOPPED(status)) {
printf("[!] mmap: 收到非预期信号: %d\n", WSTOPSIG(status));
}
// 读取返回值 (x86-64 返回值在 rax)
if (get_regs(pid, ®s) < 0) return -1;
return regs.rax;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <pid> <so_absolute_path>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
const char *so_path = argv[2];
int status;
printf("[*] 目标进程: %d\n", pid);
printf("[*] 注入 so: %s\n", so_path);
// 1. 附加目标进程
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("PTRACE_ATTACH");
return 1;
}
waitpid(pid, &status, WUNTRACED);
printf("[+] 已附加到目标进程\n");
// 2. 保存原始寄存器
struct user_regs_struct orig_regs;
if (get_regs(pid, &orig_regs) < 0) {
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 已保存寄存器现场\n");
// 3. 在远程进程中调用 mmap 分配内存
// mmap(NULL, 0x2000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
// 分配 8KB: 前半部分存路径,后半部分存 trampoline
long mmap_result = remote_mmap(pid, &orig_regs,
0x2000, // length = 8192
PROT_READ | PROT_WRITE | PROT_EXEC, // prot = RWX
MAP_PRIVATE | MAP_ANONYMOUS); // flags
if (mmap_result == -1 || mmap_result == 0) {
fprintf(stderr, "[-] 远程 mmap 失败, 返回: 0x%lx\n", mmap_result);
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 mmap 成功, 地址: 0x%lx\n", mmap_result);
long path_addr = mmap_result; // [0x0000] 路径
long tramp_addr = mmap_result + 0x1000; // [0x1000] trampoline
// 5. 将 so 路径写入 mmap 分配的内存
size_t path_len = strlen(so_path) + 1;
if (ptrace_write_data(pid, (unsigned long)path_addr, so_path, path_len) < 0) {
fprintf(stderr, "[-] 写入 so 路径失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] so 路径已写入远程内存 @ 0x%lx\n", path_addr);
// 6. 写入 trampoline: call rax; int3
// call rax (0xFF 0xD0) 会调用 rax 指向的函数
// int3 (0xCC) 触发 SIGTRAP
unsigned char trampoline[3] = {
0xFF, 0xD0, // call rax
0xCC // int3 (触发 SIGTRAP)
};
if (ptrace_write_data(pid, (unsigned long)tramp_addr, trampoline, sizeof(trampoline)) < 0) {
fprintf(stderr, "[-] 写入 trampoline 失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] trampoline 已写入 @ 0x%lx (call rax; int3)\n", tramp_addr);
// 7. 获取远程 dlopen 地址 (自动检测所在模块)
void *local_dlopen = dlsym(RTLD_DEFAULT, "dlopen");
if (!local_dlopen) {
fprintf(stderr, "[-] 无法获取 dlopen 地址\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[*] 本地 dlopen 地址: %p\n", local_dlopen);
const char *dlopen_module = NULL;
long remote_dlopen = get_remote_func_addr(pid, local_dlopen, &dlopen_module);
if (!remote_dlopen) {
fprintf(stderr, "[-] 获取远程 dlopen 地址失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 dlopen 地址: 0x%lx (模块: %s)\n", remote_dlopen,
dlopen_module ? dlopen_module : "unknown");
// 8. 使用 trampoline 方式调用 dlopen
// 设置 rax = dlopen 地址 (call rax 用)
// 设置 rdi = so 路径地址
// 设置 rsi = RTLD_NOW
// 设置 rip = trampoline 地址
struct user_regs_struct regs;
memcpy(®s, &orig_regs, sizeof(regs));
regs.rax = remote_dlopen; // rax = dlopen 地址 (call rax 用)
regs.rdi = path_addr; // rdi = so 路径
regs.rsi = RTLD_NOW; // rsi = flags
regs.rip = tramp_addr; // rip = trampoline
if (set_regs(pid, ®s) < 0) {
fprintf(stderr, "[-] 设置寄存器失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status)) {
int sig = WSTOPSIG(status);
printf("[*] 收到信号: %d (%s)\n", sig,
sig == SIGTRAP ? "SIGTRAP" :
sig == SIGSEGV ? "SIGSEGV" : "other");
}
// 9. 读取 dlopen 返回值
if (get_regs(pid, ®s) < 0) {
fprintf(stderr, "[-] 读取寄存器失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
long dlopen_ret = regs.rax;
printf("[+] dlopen 返回值: 0x%lx\n", dlopen_ret);
if (dlopen_ret == 0) {
printf("[-] dlopen 失败!\n");
} else {
printf("[+] 注入成功!handle = 0x%lx\n", dlopen_ret);
}
// 10. 恢复寄存器现场并分离
if (set_regs(pid, &orig_regs) < 0) {
perror("set_regs restore");
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
printf("[+] 已恢复现场并分离目标进程\n");
return 0;
}
wget a8dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3W2D9k6s2u0G2L8%4c8Q4x3X3g2G2M7X3N6Q4x3V1k6V1L8%4N6F1L8r3!0S2k6s2y4Q4x3V1k6T1N6h3W2D9k6s2u0G2L8%4c8Q4x3X3b7J5x3o6t1@1i4K6u0W2x3o6u0Q4x3X3f1I4i4K6u0W2N6r3q4J5i4K6u0W2k6%4Z5`.
tar xzf buildroot-2024.02.1.tar.gz
cd buildroot-2024.02.1
make qemu_mips64_malta_defconfig
# 开启 openssh
cat >> .config << 'EOF'
BR2_PACKAGE_OPENSSH=y
BR2_PACKAGE_OPENSSH_SERVER=y
BR2_PACKAGE_OPENSSH_CLIENT=y
EOF
# 同步配置,会弹出一些选项让你选一路回车保持默认即可
make oldconfig
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
make -j$(nproc)
output/images/
output/images/vmlinux //内核
output/images/rootfs.ext2 //文件系统
output/images/start-qemu.sh //qemu模拟脚本
eth0 inet addr:10.0.2.15
cd output/images
qemu-system-mips64 -M malta -kernel vmlinux -drive file=rootfs.ext2,format=raw -append "rootwait root=/dev/sda" -netdev user,id=net0,hostfwd=tcp::2222-:22 -device pcnet,netdev=net0 -nographic
buildroot login: root
#
# 设置 root 密码
passwd
# 输入新密码,如 123456
# 允许 root 登录 SSH
sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
# 重新启动 sshd
killall sshd && /usr/sbin/sshd
error while loading shared libraries: libc.so.6: cannot open shared object file
struct pt_regs {
unsigned long regs[32]; // $0 - $31
unsigned long cp0_status;
unsigned long lo;
unsigned long hi;
unsigned long cp0_badvaddr;
unsigned long cp0_cause;
unsigned long cp0_epc; // 用户态 PC
/* ... */
};
struct pt_regs regs;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
regs.regs[4] = arg0; // $a0
regs.regs[5] = arg1; // $a1
/* ... a2~a7 同理 ... */
regs.regs[25] = func_addr; // $t9 = 目标函数地址(PIC 必须)
regs.cp0_epc = func_addr; // pc = 目标函数地址
regs.regs[31] = 0; // $ra = 0,函数返回后跳到地址 0 触发 SIGSEGV
regs.regs[REG_T9] = func_addr;
regs.cp0_epc = func_addr;
af0: 67bdffe0 daddiu sp,sp,-32
af4: ffbf0018 sd ra,24(sp)
af8: ffbe0010 sd s8,16(sp)
afc: ffbc0008 sd gp,8(sp)
b00: 03a0f025 move s8,sp
b04: 3c1c0002 lui gp,0x2
b08: 0399e02d daddu gp,gp,t9
b0c: 679c7520 daddiu gp,gp,29984
b10: 24050001 li a1,1
b14: 24041388 li a0,5000
b18: df828070 ld v0,-32656(gp)
b1c: 0040c825 move t9,v0
b20: 0320f809 jalr t9
b24: 00000000 nop
GP = t9 + _gp_disp
其中 _gp_disp = _gp符号值 - 函数起始地址,这个值在链接时已经确定。
b04: lui gp, 0x2 ; gp = 0x0002_0000 = 0x20000 (高16位)
b08: daddu gp, gp, t9 ; gp = 0x20000 + t9
b0c: daddiu gp, gp, 29984 ; gp = 0x20000 + t9 + 0x7520
三步合并后:
GP = t9 + 0x20000 + 0x7520
GP = t9 + 0x27520
偏移量的拆分
_gp_disp = 0x27520,被拆分为两部分:
┌───────────────────────┬─────────────────────────────────┬────────────────────────┐
│ 指令 │ 作用 │ 值 │
├───────────────────────┼─────────────────────────────────┼────────────────────────┤
│ lui gp, 0x2 │ 加载高 16 位 (_gp_disp 的 hi16) │ 0x0002 << 16 = 0x20000 │
├───────────────────────┼─────────────────────────────────┼────────────────────────┤
│ daddiu gp, gp, 0x7520 │ 加载低 16 位 (_gp_disp 的 lo16) │ 0x7520 = 29984 │
└───────────────────────┴─────────────────────────────────┴────────────────────────
b30: ld v0, -32656(gp) ; 从 GOT 加载函数指针
b34: move t9, v0
b38: jalr t9 ; 调用目标函数
mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
MAP_FAILED == -1
errno = 9 (EBADF / Bad file descriptor)
#define __NR_MMAP_MIPS64 5009
// injector_mips64.c - MIPS64 ptrace 注入工具
// 用法: ./injector_mips64 <pid> <so_absolute_path>
// 需要 root 权限
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <asm/ptrace.h>
#define mips64_regs pt_regs
// 寄存器别名定义
#define REG_V0 2 // 返回值
#define REG_A0 4 // 参数0
#define REG_A1 5 // 参数1
#define REG_A2 6 // 参数2
#define REG_A3 7 // 参数3
#define REG_A4 8 // 参数4 (MIPS64 N64 ABI)
#define REG_A5 9 // 参数5 (MIPS64 N64 ABI)
#define REG_SP 29 // 栈指针
#define REG_RA 31 // 返回地址
#define REG_T9 25 // 跳转目标 (PIC 代码需要)
#define DUMMY_RETURN_ADDRESS 0x0
static void dump_regs(const char *tag, const struct mips64_regs *regs) {
printf("[*] %s pc=0x%lx sp=0x%lx ra=0x%lx t9=0x%lx v0=0x%lx\n",
tag,
(unsigned long long) regs->cp0_epc,
(unsigned long long) regs->regs[REG_SP],
(unsigned long long) regs->regs[REG_RA],
(unsigned long long) regs->regs[REG_T9],
(unsigned long long) regs->regs[REG_V0]);
printf("[*] %s a0=0x%llx a1=0x%llx a2=0x%llx a3=0x%llx a4=0x%llx a5=0x%llx\n",
tag,
(unsigned long long) regs->regs[REG_A0],
(unsigned long long) regs->regs[REG_A1],
(unsigned long long) regs->regs[REG_A2],
(unsigned long long) regs->regs[REG_A3],
(unsigned long long) regs->regs[REG_A4],
(unsigned long long) regs->regs[REG_A5]);
}
// 获取寄存器
static int get_regs(pid_t pid, struct mips64_regs *regs) {
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_GETREGS");
return -1;
}
return 0;
}
// 设置寄存器
static int set_regs(pid_t pid, struct mips64_regs *regs) {
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {
perror("PTRACE_SETREGS");
return -1;
}
return 0;
}
static long get_module_base(pid_t pid, const char *module_name) {
char path[256];
char line[512];
long base = 0;
if (pid == 0) {
snprintf(path, sizeof(path), "/proc/self/maps");
} else {
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
}
FILE *fp = fopen(path, "r");
if (!fp) {
perror("fopen maps");
return 0;
}
while (fgets(line, sizeof(line), fp)) {
// 查找行中的路径部分(空格后的部分)
char *path_start = strchr(line, '/');
if (path_start) {
// 检查是否以 module_name 结尾
size_t path_len = strlen(path_start);
size_t name_len = strlen(module_name);
// 移除换行符
if (path_start[path_len-1] == '\n') {
path_start[path_len-1] = '\0';
path_len--;
}
// 精确匹配:路径以 module_name 结尾
if (path_len >= name_len && strcmp(path_start + path_len - name_len, module_name) == 0) {
base = strtol(line, NULL, 16);
printf("[*] Found module %s at base 0x%lx (pid=%d)\n", module_name, base, pid);
break;
}
}
}
fclose(fp);
return base;
}
// 通过地址查找其所在的模块名和模块基址
static int find_module_by_addr(void *addr, char *module_name, size_t name_len, long *base) {
char line[512];
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return -1;
unsigned long target = (unsigned long)addr;
char found_module[256] = {0};
while (fgets(line, sizeof(line), fp)) {
unsigned long start, end;
if (sscanf(line, "%lx-%lx", &start, &end) != 2) continue;
if (target >= start && target < end) {
char *path = strrchr(line, '/');
if (path) {
char *nl = strchr(path, '\n');
if (nl) *nl = '\0';
strncpy(found_module, path, sizeof(found_module) - 1);
}
break;
}
}
fclose(fp);
if (found_module[0] == '\0') return -1;
*base = get_module_base(0, found_module);
strncpy(module_name, found_module, name_len - 1);
module_name[name_len - 1] = '\0';
return 0;
}
// 计算目标进程中某函数的地址(自动检测所在模块)
static long get_remote_func_addr(pid_t pid, void *local_func, const char **out_module) {
char module_name[256];
long local_base;
if (find_module_by_addr(local_func, module_name, sizeof(module_name), &local_base) < 0) {
fprintf(stderr, "Failed to find module for addr %p\n", local_func);
return 0;
}
long remote_base = get_module_base(pid, module_name);
if (!local_base || !remote_base) {
fprintf(stderr, "Failed to get module base for %s (local: 0x%lx, remote: 0x%lx)\n",
module_name, local_base, remote_base);
return 0;
}
if (out_module) *out_module = strdup(module_name);
long offset = (long)local_func - local_base;
long remote_addr = remote_base + offset;
printf("[*] %s: local_func=%p, local_base=0x%lx, remote_base=0x%lx, offset=0x%lx\n",
module_name, local_func, local_base, remote_base, offset);
printf("[*] 计算出的远程地址: 0x%lx\n", remote_addr);
return remote_addr;
}
// 向目标进程写入数据(以 long 为单位,按需补齐)
static int ptrace_write_data(pid_t pid, unsigned long addr, const void *data, size_t len) {
const unsigned char *src = (const unsigned char *)data;
size_t i = 0;
for (i = 0; i + sizeof(long long) <= len; i += sizeof(long long)) {
long long val;
memcpy(&val, src + i, sizeof(long long));
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA");
return -1;
}
}
if (i < len) {
long long val = ptrace(PTRACE_PEEKDATA, pid, (void *)(addr + i), NULL);
memcpy(&val, src + i, len - i);
if (ptrace(PTRACE_POKEDATA, pid, (void *)(addr + i), (void *)val) < 0) {
perror("PTRACE_POKEDATA tail");
return -1;
}
}
return 0;
}
// 在远程进程中调用一个函数(支持6个参数,用于mmap)
// MIPS64 N64 ABI: 前8个整数参数通过 $a0-$a7 (gpr[4]-gpr[11]) 传递
// 关键: MIPS PIC 代码需要设置 $t9 寄存器为目标地址 (gpr[25])
// 返回值在 $v0 (gpr[2])
static long call_remote_func(pid_t pid, struct mips64_regs *orig_regs,
long func_addr,
long arg0, long arg1, long arg2, long arg3,
long arg4, long arg5) {
struct mips64_regs regs;
memcpy(®s, orig_regs, sizeof(regs));
// MIPS64 N64 ABI: 前8个参数都通过寄存器传递
regs.regs[REG_A0] = arg0; // $a0
regs.regs[REG_A1] = arg1; // $a1
regs.regs[REG_A2] = arg2; // $a2
regs.regs[REG_A3] = arg3; // $a3
regs.regs[REG_A4] = arg4; // $a4 (第5个参数)
regs.regs[REG_A5] = arg5; // $a5 (第6个参数)
// ★ 关键: 设置 $t9 为目标地址 (MIPS PIC 代码需要)
// MIPS 的位置无关代码(PIC)使用 $t9 来访问全局偏移表(GOT)
regs.regs[REG_T9] = func_addr;
// 设置 pc 为目标函数地址
regs.cp0_epc = func_addr;
regs.regs[REG_RA] = DUMMY_RETURN_ADDRESS;
// 关键:保存原始 ra 和 pc,用于后续判断
unsigned long orig_ra = orig_regs->regs[31];
unsigned long orig_pc = orig_regs->cp0_epc;
printf("[*] call6: pc=0x%lx, t9=0x%lx, sp=0x%lx, ra=0x%lx\n",
regs.cp0_epc, regs.regs[REG_T9], regs.regs[REG_SP], regs.regs[REG_RA]);
printf("[*] args: a0=0x%lx, a1=0x%lx, a2=0x%lx, a3=0x%lx, a4=0x%lx, a5=0x%lx\n",
arg0, arg1, arg2, arg3, arg4, arg5);
printf("[*] orig: pc=0x%lx, ra=0x%lx\n", orig_pc, orig_ra);
dump_regs("before-set", ®s);
if (set_regs(pid, ®s) < 0) {
perror("set_regs");
return -1;
}
struct mips64_regs verify_regs;
if (get_regs(pid, &verify_regs) < 0) {
perror("get_regs after set");
return -1;
}
dump_regs("after-set", &verify_regs);
printf("[*] 调用 PTRACE_CONT...\n");
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
return -1;
}
// 读取当前寄存器
if (get_regs(pid, ®s) < 0) return -1;
printf("[*] current: pc=0x%lx, ra=0x%lx, v0=0x%lx\n",
regs.cp0_epc, regs.regs[REG_RA], regs.regs[REG_V0]);
// 正常返回时 pc == DUMMY_RETURN_ADDRESS
if (regs.cp0_epc == DUMMY_RETURN_ADDRESS) {
printf("[+] 函数正常返回(pc=0x%lx)\n", regs.cp0_epc);
printf("[*] return value v0=0x%lx\n", regs.regs[REG_V0]);
return (long) regs.regs[REG_V0];
}
printf("[!] 远程调用未正常返回,按失败处理\n");
return -1;
}
// 在远程进程中直接执行 MIPS64 syscall (最多6个参数)
static long call_remote_syscall(pid_t pid, struct mips64_regs *orig_regs,
long syscall_no,
long arg0, long arg1, long arg2,
long arg3, long arg4, long arg5) {
struct mips64_regs regs;
memcpy(®s, orig_regs, sizeof(regs));
regs.regs[REG_V0] = syscall_no;
regs.regs[REG_A0] = arg0;
regs.regs[REG_A1] = arg1;
regs.regs[REG_A2] = arg2;
regs.regs[REG_A3] = arg3;
regs.regs[REG_A4] = arg4;
regs.regs[REG_A5] = arg5;
unsigned long long saved_word = ptrace(PTRACE_PEEKDATA, pid, (void *)orig_regs->cp0_epc, NULL);
unsigned long long patched_word = (saved_word & 0xffffffff00000000ULL) | 0x0000000cULL;
if (ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)patched_word) < 0) {
perror("PTRACE_POKEDATA syscall");
return -1;
}
regs.cp0_epc = orig_regs->cp0_epc;
regs.regs[REG_RA] = DUMMY_RETURN_ADDRESS;
dump_regs("before-syscall", ®s);
if (set_regs(pid, ®s) < 0) {
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) {
perror("PTRACE_SYSCALL enter");
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
int status;
if (waitpid(pid, &status, WUNTRACED) < 0) {
perror("waitpid syscall enter");
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) {
perror("PTRACE_SYSCALL exit");
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
if (waitpid(pid, &status, WUNTRACED) < 0) {
perror("waitpid syscall exit");
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
struct mips64_regs result_regs;
if (get_regs(pid, &result_regs) < 0) {
ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word);
return -1;
}
if (ptrace(PTRACE_POKEDATA, pid, (void *)orig_regs->cp0_epc, (void *)saved_word) < 0) {
perror("PTRACE_POKEDATA restore");
}
dump_regs("after-syscall", &result_regs);
if (result_regs.regs[REG_A3] != 0) {
printf("[!] 远程 syscall 失败: errno=%ld (%s)\n",
(long) result_regs.regs[REG_V0],
strerror((int) result_regs.regs[REG_V0]));
return -1;
}
return (long) result_regs.regs[REG_V0];
}
// MIPS64 Linux syscall number for mmap
#define __NR_MMAP_MIPS64 5009
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <pid> <so_absolute_path>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
const char *so_path = argv[2];
int status;
printf("[*] 目标进程: %d\n", pid);
printf("[*] 注入 so: %s\n", so_path);
// 1. 附加目标进程
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("PTRACE_ATTACH");
return 1;
}
waitpid(pid, &status, WUNTRACED);
printf("[+] 已附加到目标进程\n");
// 2. 保存原始寄存器
struct mips64_regs orig_regs;
if (get_regs(pid, &orig_regs) < 0) {
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 已保存寄存器现场\n");
printf("[*] 原始寄存器: pc=0x%lx, ra=0x%lx, sp=0x%lx\n",
orig_regs.cp0_epc, orig_regs.regs[REG_RA], orig_regs.regs[REG_SP]);
// 检查 PC 是否有效
if (orig_regs.cp0_epc == 0 || orig_regs.cp0_epc > 0xfffffffffffff000) {
fprintf(stderr, "[-] 警告: 目标进程 PC 无效 (0x%lx),进程可能已崩溃\n", orig_regs.cp0_epc);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
// 3. 获取远程 mmap 地址 (自动检测所在模块)
// 尝试使用 __mmap(内部实现)而不是 mmap(可能是包装函数)
long remote_mmap = get_remote_func_addr(pid, (void *)mmap, NULL);
if (!remote_mmap) {
fprintf(stderr, "[-] 获取远程 mmap 地址失败\n");
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[*] 远程 mmap 地址: 0x%lx\n", remote_mmap);
// 4. 优先直接使用 MIPS64 mmap syscall,绕过 libc wrapper
long mmap_result = call_remote_syscall(pid, &orig_regs,
__NR_MMAP_MIPS64,
0, // addr = NULL
0x1000, // length = 4096
PROT_READ | PROT_WRITE, // prot
MAP_PRIVATE | MAP_ANONYMOUS, // flags
(unsigned long)-1, // fd = -1
0); // offset
// mmap 失败时返回 0 / MAP_FAILED / 异常高地址
if (mmap_result == 0 || mmap_result == -1 || (unsigned long)mmap_result > 0xfffffffffffff000) {
fprintf(stderr, "[-] 远程 mmap 失败, 返回: 0x%lx\n", mmap_result);
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 mmap 成功, 地址: 0x%lx\n", mmap_result);
// 5. 将 so 路径写入 mmap 分配的内存
size_t path_len = strlen(so_path) + 1;
if (ptrace_write_data(pid, (unsigned long)mmap_result, so_path, path_len) < 0) {
fprintf(stderr, "[-] 写入 so 路径失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] so 路径已写入远程内存 @ 0x%lx\n", mmap_result);
// 6. 获取远程 dlopen 地址 (自动检测所在模块)
void *local_dlopen = dlsym(RTLD_DEFAULT, "dlopen");
if (!local_dlopen) {
fprintf(stderr, "[-] 无法获取 dlopen 地址\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[*] 本地 dlopen 地址: %p\n", local_dlopen);
const char *dlopen_module = NULL;
long remote_dlopen = get_remote_func_addr(pid, local_dlopen, &dlopen_module);
if (!remote_dlopen) {
fprintf(stderr, "[-] 获取远程 dlopen 地址失败\n");
set_regs(pid, &orig_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
return 1;
}
printf("[+] 远程 dlopen 地址: 0x%lx (模块: %s)\n", remote_dlopen,
dlopen_module ? dlopen_module : "unknown");
// 7. 在远程进程中调用 dlopen(so_path, RTLD_NOW)
long dlopen_ret = call_remote_func(pid, &orig_regs, remote_dlopen,
mmap_result, // $a0 = so 路径地址 (mmap 分配的)
RTLD_NOW, // $a1 = flags
0, 0, // $a2, $a3
0, 0); // padding
printf("[+] dlopen 返回值: 0x%lx\n", dlopen_ret);
if (dlopen_ret == 0) {
printf("[-] dlopen 失败!\n");
} else {
printf("[+] 注入成功!handle = 0x%lx\n", dlopen_ret);
}
// 8. 恢复寄存器现场并分离
if (set_regs(pid, &orig_regs) < 0) {
perror("set_regs restore");
}
ptrace(PTRACE_DETACH, pid, NULL, NULL);
printf("[+] 已恢复现场并分离目标进程\n");
return 0;
}
⚠️ 免责声明:本文仅供学习和研究目的,介绍 ptrace 在 Linux 系统编程中的技术细节。ptrace 是 Linux 提供的标准系统调用,广泛用于调试器(如 gdb)开发。使用 ptrace 需要 root 权限,且应遵守相关法律法规。请勿将本文技术用于非法目的。
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。