<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pwn 基础 on RatherHard の Blog</title><link>https://blog.ratherhard.com/categories/pwn-%E5%9F%BA%E7%A1%80/</link><description>Recent content in Pwn 基础 on RatherHard の Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Wed, 22 Apr 2026 20:30:59 +0800</lastBuildDate><atom:link href="https://blog.ratherhard.com/categories/pwn-%E5%9F%BA%E7%A1%80/index.xml" rel="self" type="application/rss+xml"/><item><title>plt 和 got</title><link>https://blog.ratherhard.com/post/ctf-pwn/plt-and-got/</link><pubDate>Mon, 29 Dec 2025 14:29:00 +0000</pubDate><guid>https://blog.ratherhard.com/post/ctf-pwn/plt-and-got/</guid><description>&lt;img src="https://blog.ratherhard.com/" alt="Featured image of post plt 和 got" /&gt;&lt;p&gt;在 Linux ELF 文件和动态链接机制中，这四个段（Section）共同协作，实现了&lt;strong&gt;位置无关代码（PIC）&lt;strong&gt;和&lt;/strong&gt;延迟绑定（Lazy Binding）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;虽然它们名字很像，但功能和权限有着本质区别。我们可以将其分为两类：&lt;strong&gt;PLT 类（代码/执行）&lt;/strong&gt; 和 &lt;strong&gt;GOT 类（数据/读写）&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="1-plt-procedure-linkage-table---过程链接表"&gt;1. .plt (Procedure Linkage Table - 过程链接表)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;：&lt;strong&gt;代码段&lt;/strong&gt;（权限：读取+执行 &lt;code&gt;R-X&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：它包含了一系列小的&lt;strong&gt;可执行代码片段&lt;/strong&gt;（Stub）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能&lt;/strong&gt;：当程序调用一个外部函数（如 &lt;code&gt;printf&lt;/code&gt;）时，它实际上并不是直接跳到 &lt;code&gt;printf&lt;/code&gt; 的地址（因为在编译阶段不知道地址），而是跳到 &lt;code&gt;.plt&lt;/code&gt; 段中对应的条目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内容&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;第一项是特殊项，负责调用动态链接器的符号解析函数。&lt;/li&gt;
&lt;li&gt;后续每一项对应一个外部函数，代码逻辑通常是：&lt;code&gt;jmp *(.got.plt中的对应项)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-got-global-offset-table---全局偏移表"&gt;2. .got (Global Offset Table - 全局偏移表)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;：&lt;strong&gt;数据段&lt;/strong&gt;（权限：读取+写入 &lt;code&gt;RW-&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：用于存储&lt;strong&gt;全局变量&lt;/strong&gt;的绝对地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能&lt;/strong&gt;：程序在引用全局变量时，会先到 &lt;code&gt;.got&lt;/code&gt; 中查找该变量的真实地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么需要&lt;/strong&gt;：为了实现位置无关代码（PIC），代码段不包含变量的绝对地址，只包含到 &lt;code&gt;.got&lt;/code&gt; 的相对偏移。动态链接器在程序启动时会将变量的真实地址填入 &lt;code&gt;.got&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-gotplt-got-的-plt-部分"&gt;3. .got.plt (GOT 的 PLT 部分)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;：&lt;strong&gt;数据段&lt;/strong&gt;（权限：读取+写入 &lt;code&gt;RW-&lt;/code&gt;，但在 Full RELRO 下为 &lt;code&gt;R--&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：它是 &lt;code&gt;.got&lt;/code&gt; 的一个子集，专门用于存储&lt;strong&gt;外部函数&lt;/strong&gt;的绝对地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与 &lt;code&gt;.plt&lt;/code&gt; 的配合&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;在&lt;strong&gt;延迟绑定&lt;/strong&gt;（Lazy Binding）模式下：&lt;code&gt;.got.plt&lt;/code&gt; 的初始内容指向 &lt;code&gt;.plt&lt;/code&gt; 中的下一条指令（即“跳回 PLT”）。当函数第一次被调用时，动态链接器解析出真实地址并覆盖掉这个值。&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;第二次调用&lt;/strong&gt;时：&lt;code&gt;.plt&lt;/code&gt; 里的 &lt;code&gt;jmp&lt;/code&gt; 就会直接跳到 &lt;code&gt;.got.plt&lt;/code&gt; 中存储的真实地址，不再进入链接器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊项&lt;/strong&gt;：前三项通常预留给动态链接器的私有信息（如 &lt;code&gt;link_map&lt;/code&gt; 结构和 &lt;code&gt;_dl_runtime_resolve&lt;/code&gt; 函数地址）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-pltgot-专门的-plt-跳转表"&gt;4. .plt.got (专门的 PLT 跳转表)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;：&lt;strong&gt;代码段&lt;/strong&gt;（权限：读取+执行 &lt;code&gt;R-X&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：这是一个特殊的 &lt;code&gt;.plt&lt;/code&gt; 段，通常用于&lt;strong&gt;非延迟绑定&lt;/strong&gt;的情况，或者用于处理某些特定的重定位类型（如通过 &lt;code&gt;R_X86_64_GLOB_DAT&lt;/code&gt; 重定位的函数指针）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;区别&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;标准的 &lt;code&gt;.plt&lt;/code&gt; 条目通常包含三个动作：跳转到 GOT、压栈索引、跳到解析器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.plt.got&lt;/code&gt; 条目通常&lt;strong&gt;直接跳转&lt;/strong&gt;到 &lt;code&gt;.got&lt;/code&gt;（而不是 &lt;code&gt;.got.plt&lt;/code&gt;）中存储的地址，不包含延迟绑定的逻辑（没有压栈和解析器的跳转）。它通常出现在启用了 &lt;code&gt;-z now&lt;/code&gt;（Full RELRO）或编译器优化后的二进制文件中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="总结与对比"&gt;总结与对比
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;段名称&lt;/th&gt;
 &lt;th style="text-align: left"&gt;类型&lt;/th&gt;
 &lt;th style="text-align: left"&gt;权限&lt;/th&gt;
 &lt;th style="text-align: left"&gt;存储内容&lt;/th&gt;
 &lt;th style="text-align: left"&gt;核心目的&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;.plt&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;代码&lt;/td&gt;
 &lt;td style="text-align: left"&gt;R-X&lt;/td&gt;
 &lt;td style="text-align: left"&gt;跳转代码片段 (Stubs)&lt;/td&gt;
 &lt;td style="text-align: left"&gt;函数调用的中转站，触发延迟绑定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;.plt.got&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;代码&lt;/td&gt;
 &lt;td style="text-align: left"&gt;R-X&lt;/td&gt;
 &lt;td style="text-align: left"&gt;直接跳转代码&lt;/td&gt;
 &lt;td style="text-align: left"&gt;跳过延迟绑定逻辑，直接跳转到 GOT 地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;.got&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数据&lt;/td&gt;
 &lt;td style="text-align: left"&gt;RW-&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;全局变量&lt;/strong&gt;的绝对地址&lt;/td&gt;
 &lt;td style="text-align: left"&gt;变量引用的位置无关化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;.got.plt&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数据&lt;/td&gt;
 &lt;td style="text-align: left"&gt;RW-&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;外部函数&lt;/strong&gt;的绝对地址&lt;/td&gt;
 &lt;td style="text-align: left"&gt;配合 .plt 实现函数的延迟绑定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="协作流程演示以延迟绑定为例"&gt;协作流程演示（以延迟绑定为例）：
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Call &lt;code&gt;printf@plt&lt;/code&gt;&lt;/strong&gt;：程序跳转到 &lt;code&gt;.plt&lt;/code&gt; 中 &lt;code&gt;printf&lt;/code&gt; 对应的条目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Jmp to &lt;code&gt;.got.plt&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;.plt&lt;/code&gt; 里的第一条指令跳转到 &lt;code&gt;.got.plt&lt;/code&gt; 记录的地址。
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;第一次调用&lt;/em&gt;：&lt;code&gt;.got.plt&lt;/code&gt; 填的是 &lt;code&gt;.plt&lt;/code&gt; 的下一行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolve&lt;/strong&gt;：&lt;code&gt;.plt&lt;/code&gt; 剩下的代码调用动态链接器，找到 &lt;code&gt;printf&lt;/code&gt; 的真实地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update&lt;/strong&gt;：动态链接器将 &lt;code&gt;printf&lt;/code&gt; 的真实地址写回 &lt;code&gt;.got.plt&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subsequent Calls&lt;/strong&gt;：下次再调 &lt;code&gt;printf@plt&lt;/code&gt; 时，第 2 步的 &lt;code&gt;Jmp&lt;/code&gt; 会直接跳到 &lt;code&gt;printf&lt;/code&gt; 的真实地址。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="为什么现在的安全保护relro会影响这些段"&gt;为什么现在的安全保护（RELRO）会影响这些段？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partial RELRO&lt;/strong&gt;：&lt;code&gt;.got.plt&lt;/code&gt; 是可写的。攻击者可以利用堆栈溢出覆盖 &lt;code&gt;.got.plt&lt;/code&gt; 的条目，将 &lt;code&gt;printf&lt;/code&gt; 改为 &lt;code&gt;system&lt;/code&gt;，从而实现 &lt;strong&gt;GOT Hijacking&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full RELRO&lt;/strong&gt;：动态链接器在程序启动时就把所有函数解析完毕，并将 &lt;code&gt;.got.plt&lt;/code&gt; 设为&lt;strong&gt;只读&lt;/strong&gt;。此时，&lt;code&gt;.plt.got&lt;/code&gt; 的作用就会变得更明显，因为不再需要延迟绑定逻辑了。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>栈帧结构和栈溢出漏洞</title><link>https://blog.ratherhard.com/post/ctf-pwn/stack-frame-and-overflow/</link><pubDate>Mon, 27 Oct 2025 16:07:00 +0000</pubDate><guid>https://blog.ratherhard.com/post/ctf-pwn/stack-frame-and-overflow/</guid><description>&lt;img src="https://blog.ratherhard.com/" alt="Featured image of post 栈帧结构和栈溢出漏洞" /&gt;&lt;h1 id="栈帧"&gt;栈帧
&lt;/h1&gt;&lt;h2 id="什么是栈帧"&gt;什么是栈帧
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;函数栈帧 (stack frame)&lt;/strong&gt; 就是函数调用过程中程序的&lt;strong&gt;调用栈 (call stack)&lt;/strong&gt; 所开辟的空间，这些空间是用来存放：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;函数参数和函数返回值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;临时变量（包括函数的非静态的局部变量以及编译器自动生产的其他临时变量）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保存上下文信息（包括在函数调用前后需要保持不变的寄存器）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="栈帧结构"&gt;栈帧结构
&lt;/h2&gt;&lt;p&gt;栈&lt;strong&gt;从高地址向低地址生长&lt;/strong&gt;，而在 x86 架构下数据则以&lt;strong&gt;小端序&lt;/strong&gt;写入：&lt;strong&gt;高位字节放高地址端，低位字节放低地址端&lt;/strong&gt;，可以理解为&lt;strong&gt;从低地址向高地址生长&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以，栈顶在低地址处，栈基在高地址处。&lt;/p&gt;
&lt;p&gt;主调函数进行函数调用时，一般会在紧贴主调函数栈帧下方建立一个新的栈帧。&lt;/p&gt;
&lt;h3 id="重要指针寄存器"&gt;重要指针寄存器
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;栈指针&lt;/strong&gt;： sp ，指向栈顶， push 指令会使 sp 下移， pop 指令会使 sp 上移。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;帧指针&lt;/strong&gt;： bp ，指向当前函数栈帧的基，指向的位置用于保存当前函数的主调函数的帧指针。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指令指针&lt;/strong&gt;： ip ，是计算机处理器中用于存储下一条待执行指令内存地址的寄存器。&lt;/p&gt;
&lt;p&gt;注意，栈指针与帧指针正常情况下均应指向某个单位内存的最低地址处。&lt;/p&gt;
&lt;h3 id="32-位栈帧结构"&gt;32 位栈帧结构
&lt;/h3&gt;&lt;p&gt;&lt;img alt="这是什么鸭" class="gallery-image" data-flex-basis="148px" data-flex-grow="61" height="912" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pic.ratherhard.com/post/stack-frame-and-overflow/x86-stack-frame.jpg" width="564"&gt;&lt;/p&gt;
&lt;h3 id="64-位栈帧结构"&gt;64 位栈帧结构
&lt;/h3&gt;&lt;p&gt;&lt;img alt="这是什么鸭" class="gallery-image" data-flex-basis="148px" data-flex-grow="62" height="908" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pic.ratherhard.com/post/stack-frame-and-overflow/x86-64-stack-frame.jpg" width="563"&gt;&lt;/p&gt;
&lt;p&gt;64 位下，函数的前 6 个参数会存放在寄存器中，从第一个参数开始依次存入寄存器 &lt;strong&gt;rdi ， rsi ， rdx ， rcx ， r8 ， r9&lt;/strong&gt; 中。&lt;/p&gt;
&lt;p&gt;接下来的讨论默认为 64 位的情形。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="栈相关汇编机制"&gt;栈相关汇编机制
&lt;/h2&gt;&lt;h3 id="pop-与-push"&gt;pop 与 push
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;push x&lt;/code&gt; ：将 x 压入栈中（本质上是覆盖栈中数据），先使 rsp 下移 8 字节，再将内存单元 x （寄存器或内存地址）的值复制入此时 rsp 所指位置，等价于 &lt;code&gt;sub rsp, 0x8; mov [rsp], x&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pop x&lt;/code&gt; ：弹出栈顶（实际上不会删除原栈顶数据）将栈顶的值，即 rsp 所指位置的值复制入 x （寄存器或内存地址）中，并使 rsp 上移 8 字节，等价于 &lt;code&gt;mov x, [rsp]; add rsp, 0x8&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="call-与-leave-与-ret"&gt;call 与 leave 与 ret
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;call x&lt;/code&gt; ：本质上是 jump 指令，但会将本语句的下一条语句的地址 push 到栈中，等价于 &lt;code&gt;push (rip + 指令长度); jump x&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;leave&lt;/code&gt; ：清除该函数栈帧（实际上不会处理栈中残余数据），等价于 &lt;code&gt;mov rsp, rbp; pop rbp&lt;/code&gt; 执行后&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ret&lt;/code&gt; ：函数返回，等价于 &lt;code&gt;pop rip&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="函数序言"&gt;函数序言
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;函数序言 (Function Prologue)&lt;/strong&gt; 是函数开始时的一段标准汇编代码，用于建立函数的执行环境，通常包含对栈的操作，例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;; 函数序言
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;push&lt;/span&gt; &lt;span class="no"&gt;rbp&lt;/span&gt; &lt;span class="c1"&gt;; 保存调用者的栈基指针
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="no"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;rsp&lt;/span&gt; &lt;span class="c1"&gt;; 设置当前函数的栈基指针
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;sub&lt;/span&gt; &lt;span class="no"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x20&lt;/span&gt; &lt;span class="c1"&gt;; 为局部变量分配栈空间（32字节）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;push&lt;/span&gt; &lt;span class="no"&gt;rbx&lt;/span&gt; &lt;span class="c1"&gt;; 保存被调用者保存的寄存器
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="寄存器使用约定"&gt;寄存器使用约定
&lt;/h3&gt;&lt;p&gt;程序寄存器组是唯一能被所有函数共享的资源。虽然某一时刻只有一个函数在执行，但需保证当某个函数调用其他函数时，被调函数不会修改或覆盖主调函数稍后会使用到的寄存器值。因此， IA32 采用一套统一的寄存器使用约定，所有函数（包括库函数）调用都必须遵守该约定。&lt;/p&gt;
&lt;p&gt;根据惯例，寄存器 rax 、 rdx 和 rcx 为&lt;strong&gt;主调函数保存寄存器 (caller-saved registers)&lt;/strong&gt; ，当函数调用时，若主调函数希望保持这些寄存器的值，则必须在调用前显式地将其保存在栈中；被调函数可以覆盖这些寄存器，而不会破坏主调函数所需的数据。寄存器 rbx 、 rsi 和 rdi &lt;strong&gt;为被调函数保存寄存器 (callee-saved registers)&lt;/strong&gt; ，即被调函数在覆盖这些寄存器的值时，必须先将寄存器原值压入栈中保存起来，并在函数返回前从栈中恢复其原值，因为主调函数可能也在使用这些寄存器。此外，被调函数必须保持寄存器 rbp 和 rsp ，并在函数返回后将其恢复到调用前的值，亦即必须恢复主调函数的栈帧。&lt;/p&gt;
&lt;p&gt;当然，这些工作都由编译器在幕后进行。不过在编写汇编程序时应注意遵守上述惯例。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="栈溢出漏洞"&gt;栈溢出漏洞
&lt;/h1&gt;&lt;h2 id="栈溢出介绍"&gt;栈溢出介绍
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;栈溢出&lt;/strong&gt;指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数，因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的&lt;strong&gt;缓冲区溢出漏洞&lt;/strong&gt;，类似的还有堆溢出，bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃，重则可以使攻击者控制程序执行流程。此外，我们也不难发现，发生栈溢出的基本前提是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;程序必须向栈上写入数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写入的数据大小没有被良好地控制。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="栈溢出利用思路"&gt;栈溢出利用思路
&lt;/h2&gt;&lt;h3 id="寻找危险函数"&gt;寻找危险函数
&lt;/h3&gt;&lt;p&gt;通过寻找危险函数，我们快速确定程序是否可能有栈溢出，以及有的话，栈溢出的位置在哪里。常见的危险函数如下：&lt;/p&gt;
&lt;h4 id="输入"&gt;输入
&lt;/h4&gt;
 &lt;blockquote&gt;
 &lt;p&gt;gets : 直接读取一行，忽略&amp;rsquo;\x00'
scanf : 用于从标准输入（如键盘）读取数据并存储到指定变量&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="输出"&gt;输出
&lt;/h4&gt;
 &lt;blockquote&gt;
 &lt;p&gt;sprintf : 输出格式化字符串到缓冲区&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="字符串"&gt;字符串
&lt;/h4&gt;
 &lt;blockquote&gt;
 &lt;p&gt;strcpy : 字符串复制，遇到&amp;rsquo;\x00&amp;rsquo;停止
strcat : 字符串拼接，遇到&amp;rsquo;\x00&amp;rsquo;停止
bcopy : 内存拷贝&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="确定填充长度"&gt;确定填充长度
&lt;/h3&gt;&lt;p&gt;这一部分主要是计算我们所要操作的地址与我们所要覆盖的地址的距离。常见的操作方法就是打开 IDA ，根据其给定的地址计算偏移。一般变量会有以下几种索引模式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;相对于栈基地址的的索引，可以直接通过查看与 rbp 的相对偏移获得&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相对应栈顶指针的索引，一般需要进行调试，之后还是会转换到第一种类型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;直接地址索引，就相当于直接给定了地址。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般来说，我们会有如下的覆盖需求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;覆盖函数返回地址，这时候就是直接看 rbp 即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;覆盖栈上某个变量的内容，这时候就需要更加精细的计算了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;覆盖 bss 段某个变量的内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据现实执行情况，覆盖特定的变量或地址的内容。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之所以我们想要覆盖某个地址，是因为我们想通过覆盖地址的方法来直接或者间接地控制程序执行流。&lt;/p&gt;</description></item></channel></rss>