原理
基本的栈帧结构(以 x64 的栈为例)
(图片摘自Hello-CTF)
RBP 为栈底寄存器,RSP 为栈顶寄存器,分别记录了栈帧中记录数据部分的起始和终止地址。函数的临时变量的在内存中的位置都是通过这两个寄存器加减偏移确定的。
栈底分别还记录了上一个栈帧的 RBP 的值,以及函数的返回地址。
方法:
1、找常发生栈溢出的危险函数
输入:
gets()
:直接读取一行,到换行符’\n’为止,同时’\n’被转换为’\x00’;scanf(),格式化字符串中的%s不会检查长度;
vscanf()
:同上。
输出:
sprintf()
:将格式化后的内容写入缓冲区中,但是不检查缓冲区长度
字符串:
strcpy()
:遇到’\x00’停止,不会检查长度,经常容易出现单字节写0(off by one)溢出;
strcat()
:同上。
2、确定填充长度
这一部分主要是计算我们所要操作的地址与我们所要覆盖的地址的距离。常见的操作方法就是打开 IDA,根据其给定的地址计算偏移。一般变量会有以下几种索引模式:
- 相对于栈基地址的的索引,可以直接通过查看 EBP 相对偏移获得
- 相对应栈顶指针的索引,一般需要进行调试,之后还是会转换到第一种类型。
- 直接地址索引,就相当于直接给定了地址。
一般来说,我们会有如下的覆盖需求:
- 覆盖函数返回地址,这时候就是直接看 EBP 即可。
- 覆盖栈上某个变量的内容,这时候就需要更加精细的计算了。
- 覆盖 bss 段某个变量的内容。
- 根据现实执行情况,覆盖特定的变量或地址的内容。
之所以我们想要覆盖某个地址,是因为我们想通过覆盖地址的方法来直接或者间接地控制程序执行流程。
这里给出两种方法:
1、肉眼看
看到函数距离rbp为0x30距离,加上默认RBP与下面栈祯默认距离0x8,因此总距离为0x38
2、看v4的IDA定义
点v4,上面注释里全写好了
(“Saved Regs” 是 “Saved Registers” 的缩写,意思是 “已保存的寄存器”。)
3、找函数泄露地址
shift+F12发现/bin/sh很可疑,因此找到该函数,按空格找到地址
注意这里最好用offset command这一行,原因是函数首地址在编译可能发生改变,而下面这一行是不变的(已经存在了寄存器里)
4、写exp
from pwn import *
p=remote("1.95.36.136",2051)
payload=b'a'*(0x38)+p64(0x000000000040059A)
p.sendline(payload)
p.interactive()
最后cat flag
获得flag的值