###### tags: `NCTU` `CS` `Course` `SecureProgramming` HW 0x01 Write Up == ## [Lab 1-1] pwntools practice flag: FLAG{pwntools_is_not_that_hard} 題目要求是做100次讀入100個數字後排序並回傳 可以使用 python pwntools 來達成自動化 `r = remote('csie.ctf.tw', 10123)` 建立連線 `r.recvuntil("left\n")` 可以讀取到數字前一行 `r.recvline()` 將整行數字讀入 `r.sendline(payload)` 將排序過的數字轉成字串後回傳 ## [Lab 1-2] orw flag: FLAG{N0w_Y0u_le4rn4d_h0w_T0_she11c0de} 題目是要傳入 shellcode 讀取 `/home/orw/flag` 得到 flag 但是限制 system call 只能使用 open read write 所以就需建構如下的程式 ``` int fd = open("/home/orw/flag"); char buf[40]; read(fd, buf, 40); write(0, buf, 40); ``` 而且為了做成 shellcode 需要轉成 asm 呼叫 syscall 時,使用 `rax` 來指定要執行的 syscall 參數需要照 `rdi` `rsi` `rdx` `r10` `r8` `r9` 的順序擺放 回傳值會放在 `rax` |rax|syscall|rdi|rsi|rdx| |--|--|--|--|--| |0|read|fd|buf|size| |1|write|fd|buf|size| |2|open|filename|flags|mode| 使用的 stack 如圖 ``` +--------------+ HIGH + w/flag\0\0 + +--------------+ + /home/or + +--------------+ + buf(40bytes) + +--------------+ LOW <- rsp ``` stack 是由 high address 往 low address 使用的 所以在將 path 放入 stack 時,需要分成 8bytes 一組,並且要從較後面的組堆疊下來 ## [HW 0x01] short shell flag: FLAG{r34d_4RE_th3_es1esT} 這個程式會從 stdin 讀取 10 bytes 並執行 但是執行 `exec("/bin/sh")` shellcode 大於 10 bytes 無法直接傳入 所以可以先建構一個 10 bytes 只做 read syscall 的 shellcode 將真正的 shellcode 讀到接下來執行的 stack 上 就可以繞過只能讀取 10 bytes 的限制了 在 call rdx 之前的 register 值 |rax|rdi|rsi|rdx| |--|--|--|--| |0|0x7b||shellcode最底下| 在 read syscall 之前 register 需要改成 |rax|rdi|rsi|rdx| |--|--|--|--| |0|0|原rdx + 10|超過`len(shellcode)`| 要先把 `rdx` 值複製到 `rsi` 上再讓 `rsi` 加 10,讓下一個 shellcode 連在 read syscall 後面,讀入完後直接執行 `rdx` 需要一個超過 `len(shellcode)` 的值,原本 rdi 的值非常適合,所以就把 `rdi` 值複製到 `rdx` `rdi` 需要設為 0,所以就把 `rax` 值複製到 `rdi` 上就可以了 因為只能用 10 bytes 的限制 所以有很多 opcode 使用空間太多而無法使用 要找可以做到相同功能但使用較少空間的指令取代 e.g. ``` mov rsi, rdx => push rdx pop rsi ``` 使用的 stack 如圖 ``` +----------------+ + exec shellcode + +----------------+ <- rsi + read shellcode + +----------------+ <- 原本的 rdx ``` ## [HW 0x01] shellsort2.0 flag: FLAG{SUPERB_sh311c0d1ng_ski115!!!} 這個程式會從 stdin 讀取 10000 bytes 以降序排序後再執行 所以只能建構原本就是降序的程式才會照著原本的功能執行 因為建構符合上面條件的 `exec("/bin/sh")` shellcode 過於困難 所以也是建構一個只做 read syscall 的 shellcode,將真正的 shellcode 讀到接下來執行的 stack 上 在 `call rdx` 之前的 register 值 |rax|rdi|rsi|rdx| |--|--|--|--| |0|0|0|shellcode最底下| 在 read syscall 之前 register 需要改成 |rax|rdi|rsi|rdx| |--|--|--|--| |0|0|rdx|超過`len(shellcode)`但不能超過 2^31| 所以要做的事情有兩個 1. `mov rsi, rdx` 2. 減少 `rdx` 的值 在大量嘗試後發現可以的指令順序是 ``` nop pop push xor rsi, [rdx] ``` 所以可以使用多次固定次數 `pop` + 一次 `push rdx` 做到 `*rdx = rdx` 再接上 `rsi = *rdx` 來達成原本的 `mov rsi, rdx` 因為 `rdx` 指的位址就是 shellcode 的開頭,然而這個作法會把 shellcode 拔掉 8 bytes,所以要在前面放 8 個 `nop` 減少 `rdx` 的值的方法也是在大量嘗試後發現可以的指令是 ``` sub edx, [] ``` 因為直接修改 `edx` 可以讓 high bits 直接歸 0,就可以達到 `rdx < 2^32` 使用 gdb 觀察後發現 `rcx` 通常會是指到 stack 上的記憶體,所以可以拿來當減數 這個做法就有一定機率可以成功 最後在傳入 exec shellcode 時 `rip > rsi`,這時只要在 shellcode 前加一堆 `nop` 就可以堆到 `rip` 後面,讓 shellcode 可以順利執行了 使用的 stack 如圖 執行 `call rdx` 前 ``` +----------------+ + read shellcode + +----------------+ + nop * 8 + +----------------+ <- rdx + main function + + 的變數 + +----------------+ <- rsp ``` 執行所有的 `pop` 後 ``` +----------------+ + read shellcode + +----------------+ <- rsp + + +----------------+ <- rdx ``` `push rdx` 後 ``` +----------------+ + read shellcode + +----------------+ + rdx value + +----------------+ <- rdx rsp ``` read syscall 後 ``` +----------------+ + exec shellcode + +----------------+ + many nops + <- rip +----------------+ <- rdx rsp ```