
写在前面在之前的文章中我们搞懂了保护机制和调用约定。但当我们真正打开一个存在溢出的程序面对输入框时第一个要回答的问题是“我到底要输入多少个字符才能正好把返回地址给覆盖掉”本文将带你彻底解决两个基础痛点内存中的数据到底是怎么存的小端序以及如何精准计算溢出偏移量。 目录内存寻址基础大端序 vs 小端序PWN 中的“地址逆序”现象什么是偏移量偏移量计算实操一GDB 手动计算法偏移量计算实操二pwntools 自动化计算法总结1. 内存寻址基础大端序 vs 小端序在计算机中一个多字节的数据比如 32 位地址0x08048456占 4 个字节在内存中是如何排列的大端序高位字节存放在低地址低位字节存放在高地址。符合人类阅读习惯小端序低位字节存放在低地址高位字节存放在高地址。符合计算机运算习惯绝大多数 x86/x64 架构的 CPU 都采用小端序。举个例子我们要把地址0x08048456存入内存地址0x1000开始的 4 个字节中内存地址大端序存储内容小端序存储内容 (实际采用)0x1000 (最低地址)08560x100104840x100284040x1003 (最高地址)5608在内存中从低地址往高地址读这四个字节看到的是56 84 04 08。2. PWN 中的“地址逆序”现象因为小端序的存在当我们在构造 Payload 准备覆盖返回地址时必须把目标地址按字节逆序输入。假设我们通过分析知道返回地址位于偏移量 12 字节处我们要把返回地址覆盖为0x08048456。Payload 应该这么写[12个填充字符] [\x56\x84\x04\x08]注意不能用0x08打头因为计算机是从低地址开始读取的第一个写进去的字节会被放在最低地址处。Python 小技巧在写 Exploit 时我们不可能每次都手动去倒序。Python 的pwntools库提供了极其方便的打包函数p32(0x08048456)自动转为 32 位小端序字节流b\x56\x84\x04\x08p64(0x401196)自动转为 64 位小端序字节流b\x96\x11\x40\x00\x00\x00\x00\x003. 什么是偏移量在栈溢出中偏移量指的是从我们输入的缓冲区起始地址到栈上的返回地址之间的距离。回顾一下我们之前学过的栈结构以 64 位为例[高地址] | Ret (返回地址 RIP) | - 目标覆盖这里 | RBP (栈底指针) | - 占 8 字节 | 局部变量/填充物 | - 我们的输入从这里开始 [低地址]如果我们的输入起点距离Ret有 24 个字节那么偏移量就是 24。我们需要先输入 24 个垃圾字符填满前面的空间第 25 个字节才会开始覆盖Ret。4. 偏移量计算实操一GDB 手动计算法这是最基础、最能理解栈结构的方法。我们准备一个简单的漏洞程序vuln。4.1 准备代码#include stdio.h #include string.h void vulnerable() { char buf[64]; gets(buf); // 存在溢出 } int main() { vulnerable(); return 0; }编译关闭 Canary 和 PIE开启 64 位默认栈不可执行gcc -fno-stack-protector -no-pie vuln.c -o vuln -g4.2 GDB 调试计算用 pwndbg 加载程序gdb ./vuln在gets函数调用之后下断点假设gets对应的汇编指令地址是0x40116e可以通过objdump -d vuln或在 pwndbg 中看反汇编找。pwndbg b *0x40116e pwndbg r此时程序等待我们输入。我们输入一串有规律的字符方便识别。比如 100 个 ‘A’AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA程序断住后查看buf所在的栈地址。我们可以通过看源码或汇编找到buf的地址比如lea rax, [rbp-0x40]说明 buf 起始于 rbp-0x40。使用 pwndbg 的stack 40命令查看栈情况pwndbg stack 40观察与计算你会在栈顶附近看到一串0x4141414141414141(因为 ‘A’ 的 ASCII 码是 0x41)。找到buf起始的地址假设是0x7fffffffe2c0。往下找找到原本存放Ret的位置。通常在rbp的下面。假设存放返回地址的栈地址是0x7fffffffe308。偏移量 0x7fffffffe308 - 0x7fffffffe2c0 0x48 (十进制 72)。公式法更简单在 64 位下偏移量 buf到rbp的距离 8。如果汇编里是rbp-0x40说明距离是 0x40 (64)。偏移量 64 8 72。5. 偏移量计算实操二pwntools 自动化计算法在实际打 PWN 题时手动算太慢了。我们通常使用pwntools库里的cyclic功能这也是最通用的无脑计算法。5.1 原理cyclic会生成一个独特的模式字符串比如aaaabaaacaaadaaa...。每 4 个或 8 个字节为一组不重复。当程序崩溃时崩溃地址会告诉我们到底是覆盖到了第几个字节。5.2 实操步骤在 Python 中生成模式串并交给程序运行直到程序崩溃Segmentation Fault。查看崩溃时RIP或 EIP寄存器里的值。用cyclic_find查找该值在模式串中的位置即为偏移量。Python 自动化脚本示例 (calc_offset.py)from pwn import * # 开启进程 p process(./vuln) # 生成 200 字节的模式串 # 64位程序用 cyclic(200)32位用 cyclic(200) payload cyclic(200) # 发送 payload p.sendline(payload) # 等待程序崩溃 p.wait() # 读取核心转储或崩溃信息 # 在 pwndbg/gdb 中也可以直接看这里演示纯 python core p.corefile # 64 位程序看 rip32 位程序看 eip crash_addr core.read(core.rsp, 8) # 有时直接看 core.fault_addr # 更简单的是直接看崩溃时 gdb 里 rip 的值 # 假设我们通过 gdb 发现崩溃时 RIP 0x6161616161616161 (偏移如果小于8或者是 cyclic 的子串) # 比如我们在 gdb 中看到 RIP 被覆盖成了 0x6261616161616161 (baaaaaaa) offset cyclic_find(0x6261616161616161) # 如果是 32位: offset cyclic_find(0x62616161) print(找到的偏移量是:, offset)更常用的手操法结合 GDB在终端输入cyclic 200会输出aaaabaaacaaadaaa...gdb ./vulnr运行输入上述长字符串回车。程序崩溃pwndbg 会自动显示寄存器状态RIP 0x6261616161616161 (baaaaaaa)复制这个字符或十六进制值在终端执行cyclic_find -b 0x6261616161616161 # 或者直接找字符串 cyclic_find baaaaaaa输出结果72。完美偏移量就是 726. 总结这两个知识点虽小但在每一次实战中都会用到小端序永远记住“高高低低”。在构造 Payload 时地址必须用p32()或p64()转换绝对不能直接当字符串写。偏移量计算首选cyclic工具。cyclic 200发送 - 崩溃看 RIP -cyclic_find查询。三板斧搞定准没错。如果本篇文章对您有帮助请点赞收藏支持一下感谢阅读