###### tags: `NCTU` `CS` `Course` `SecureProgramming` HW 0x05 Write Up == yysung ## [Lab 5-1] fmt 題目分成三個 flag 第一個 flag 是要將全域變數 a 的值改成 `0xfaceb00c` 做法就是使用第一次輸入 fmt string 可操控的漏洞 先輸入數個 `"$p."` 來找出輸入字串在 fmt string 中的位置 得到 `0x7f4a85e927e3.0x7f4a85e938c0.0x7f4a85bb6154.0x6.0x7f4a860b14c0.0x7f4a860c0a98.0x300000000.0x14a0c248ba3b06e5.0xbd76540b3d9690d7.0xffffffff.(nil).0x7ffc921c3268.0x7f4a860c0710.(nil).(nil).0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.` 第 16 個位置後是 fmt string 開始的位置 所以可以在字串後面加上 a 的位址,並用 `%c` 和 `%n` 對 a 的位址寫值 因為 `0xfaceb00c` 太大的關係,直接寫會讓 `%c` 產生的字串過大導致程式執行錯誤 所以可以分成對 a 和 a + 2 的位址寫值,第一次用 `%hn` 對 a 寫入 2 bytes 第二次再用 `%hn` 對 a + 2 寫入 2 bytes 也因為 `0xb00c < 0xface` 的關係,可以在一次 fmt 中就可以達成寫入 理由是 `%n` 寫入的值是之前 printf 印出的字元數,在同一次 printf 中會累加,所以寫入的值只能非嚴格遞增 這樣就能拿到 lab 5-1-1 的 flag 第二個 flag 是要將 secret 的值傳回程式 secret 的值是 16 bytes 的 `/dev/urandom` 產生的隨機值 可以在第一次輸入 fmt string 中順便 leak secret 的值 secret 的值可以用 `%8$p.%9$p.` 印出 在第二次輸入回傳 secret 的值就可以拿到 lab 5-1-2 的 flag 第三個 flag 是要得到 shell 可以在第一次輸入 fmt string 中順便 leak `libc_start_main` 的位址 做法是在 fmt string 後面放上 `libc_start_main got` 的位址 就可以將 `libc_start_main` 的位址印出來了 減去 `libc_start_main` 在 `libc` 上的 offset 就可以得到 `libc base` 的位址 第三次輸入也可以使用 fmt string的漏洞 可以在第三次輸入中將 `printf_got` 的值改成 `system` 的位址 之後執行 `printf` 就會變成執行 `system` 了 程式最後會把第四次的輸入當作 `printf` 的 fmt string 所以輸入 `sh` 就可以執行 `system("sh")` 了 就可以拿到 lab 5-1-3 的 flag ## [Lab 5-2] fmt revenge 這次的 fmt string 並不在 stack 上 所以無法直接在 fmt string 後面加上記憶體位址直接改值 做法是找到 RBP Chain RBP Chain 是在 stack 上保存 rbp 的空間,特點是值會剛好指到上一個 stack frame 保存 rbp 的空間 也就是一串都會指到 stack 的空間 假設有以下的 RBP Chain ``` 1->2->3 ``` 第2個 rbp 指到第3個 rbp,可以用對第2個 rbp 值 `%n` 來修改第3個 rbp 值 讓第3個 rbp 值可以變成要修改的記憶體位址 然而也會遇到 `%c` 產生的字串過大導致程式執行錯誤的問題,所以要使用第1個 rbp 來微調第2個 rbp 的值 讓第2個 rbp 可以指到第3個rbp + offset 的空間,讓寫入可以每次只寫 2 bytes 這題有開 pie ,所以要先將 return address leak 出再減掉 offset 算出 pie 再 leak `libc base` 並將 `printf_got` 改成 `system`,修改手法就是使用上面的方法 ## [Lab 5-3] migration 這題的輸入有兩次,第一次寫到 global,最多 `0x280` bytes,第二次寫到 stack,最多 `0x30` bytes 第二次輸入可以改到 saved rbp 和 return address,但是沒有其餘的空間可以做 ROP Chain 所以需要利用 leave ret 的特性來修改 stack 的位址到 global,來執行第一次輸入做的 ROP Chain leave ret 實際上等價於以下指令 ``` mov rsp, rbp pop rbp pop rip ``` 所以在第二次輸入中將 saved rbp 蓋成 stack migration 的位址,執行完一次 leave ret 後 rbp 就被修改成 stack migration 的位址 需要再一次 leave ret 才會把 rsp 修改成 stack migration 的位址 所以將 return address 蓋成 leave ret gadget 的位址 就可以將 stack 轉到 global 上,也就是第一次輸入做的 ROP Chain 的地方 接下來還有一個 `pop rbp` 所以先再放一個 global 的位址成為下一個 stack migration 跳的地方 ROP Chain 中可以將 printf 的位址印出來 leak libc 並且再跳回 main function 第二次輸入的位址 main function 第二次輸入寫入的地方是 `[rbp-0x20]` ,所以可以寫到 [rbp+0x8] 也就是 return address 所以將 return address 蓋成 one_gadget 就可以得到 shell 並拿到 flag 了 ## [HW 0x05] echo 這題的作法和 lab 5-2 非常類似,都是輸入到 global 的 fmt string 然而執行 fmt 的 function 是 `dprintf(fd, fmt)` 且 fd 為 global 變數且預設值為 2 在遠端執行的程式不會印出任何值,推測程式執行方法是 `./echo 2&>/dev/null` 所以要先在無法 leak 任何資訊的情況下將 fd 改成 1 因為 `dprintf` 是寫入 stderr 並且 stderr 導入 `/dev/null` 對 stderr 寫入多少 bytes 都會視為成功寫入 所以可以找一組 RBP Chain 利用第一個 RBP 直接將第二個 RBP 改成 fd 的 global 位址 再用第二個 RBP 將 fd 改成 1 接下來就和 lab 5-2 使用 fmt 的方法類似 leak 出 libc base 再將 return address 蓋成 one_gadget 就可以得到 shell 並且拿到 flag 了 stderr -> stdout leak libc -> one gadget leak stack -> hijack return address