--- tags: CS 2022 Fall, 程式安全 --- 影片: - https://youtu.be/YbPdGuFCpFI - https://youtu.be/htqMSWqi4Ps - https://youtu.be/7qbKBOAAZT4 簡報:https://drive.google.com/file/d/1i7DVMgj9SzXG4 # [0x04] Reverse I(程式安全) ### [LAB] Sacred Arts #### 開檔案 ![](https://i.imgur.com/veyCYVi.png) 題目一開始會 `open('/tmp/flag', 0)` 然後把 `rax (fd)` 移到 `r15` <style> img { box-shadow: 0px 0px 15px #000; } </style> #### 確認開檔案成功 ![](https://i.imgur.com/N0bM8MX.png) 如果沒有成功打開檔案 ($rax <= 0$) - 會跳到 `loc_401035` 如果成功打開檔案 - 會跳到 `loc_40106F` #### `loc_401035` 與 `loc_40106F` - `loc_401035` 是 `write(1, 'wrong\n', 7)` 可以看得出是會輸出 `wrong` ![](https://i.imgur.com/x9z5m1H.png) - `loc_40106F` 是 `read(fd, rsp, 0x32)` 其中可以看到他先把 `rsp` 往上移動 $0x40$ 然後讀檔案近去 `rsp` 的地方 之後會跳到 `loc_4010C3` 由於他讀了 `/tmp/flag` $0x32$ 個字 我們可以大膽假設 `flag` 的長度為 $0x32$ 我們暫且把 `buf` 的東西稱作 `flag_input` ![](https://i.imgur.com/EnvUIVM.png) #### `loc_4010C3` ![](https://i.imgur.com/Uo55xVj.png) 看出是一個迴圈 做的事情可以寫成以下的 `Psudo-code` ```cpp // flag_enc: byte_40108B for (int rcx = 7; rcx != 0; rcx--) { int rdx = rcx-1; // lea rdx, [rcx*8-8] char rax = flag_input[rdx]; rax = -rax; xchg(al, ah); if (flag_input[rdx] != flag_enc[rdx]) goto wrong; // loc_401035 (wrong) } ``` 可以看出 `flag_input` 經過一連串操作若與 `flag_enc` 相同 則不會跳到 `loc_401035 (wrong)` 我們猜測 `flag_enc` 做上述的操作反過來做就會是 `flag` 所以試著把 `flag_enc` 拉出來操作 ##### `Sacred Arts/exp.py` ```python from Crypto.Util.number import bytes_to_long, long_to_bytes flag_e = ["8D909984B8BEBAB3", "8D9A929E98D18B92", "D0888BD19290D29C", "8C9DC08F978FBDD1", "D9C7C7CCCDCB92C2", "C8CFC7CEC2BE8D91", "FFFFFFFFFFFFCF82"] flag_e = [ bytearray.fromhex(a) for a in flag_e ] for c in range(len(flag_e)): t = int.from_bytes(flag_e[c], byteorder='big') flag_e[c] = bytearray(t.to_bytes(8, byteorder='little')) # reverse order # xchg al, ah flag_e[c][0], flag_e[c][1] = flag_e[c][1], flag_e[c][0] # neg rax t = int.from_bytes(flag_e[c], byteorder='little') t ^= 0xFFFFFFFFFFFFFFFF t = (t+1) & 0xFFFFFFFFFFFFFFFF flag_e[c] = t.to_bytes(8, byteorder='little') for fi in flag_e: print(fi.decode(), end="") # output: FLAG{forum.gemer.com.tw/C.php?bsn=42388&snA=18071} ``` ### [LAB] Kekkou #### `main` ![](https://i.imgur.com/9iXKTEg.png) 一開始會被要求輸入 `flag` 所以 `flag` 的長度可能為 $65$ 然後經過 `sub_11F1` `sub_125F` 後 如果 `sub_131C` 輸出 `Correct~~~` 可以猜測 `sub_131C` 就是 `check_flag` #### `sub_11F1` 我們從 `main` 看到他把 `v5 (flag_input)` 放進來 所以先把第二個參數名稱及型態改好 這讓程式碼看起來更舒服 ![](https://i.imgur.com/wv6emlU.png) 接著來看 `sub_1169` #### `sub_1169` ![](https://i.imgur.com/7vnzHBT.png) 可以看到 `sub_1169` 的 `result` 的操作看起來很神奇 既然題目都叫做 `ケッコウ` 了 那麼這可能是一個 `struct` 就先 `Reset Pointer Type` 看看 ![](https://i.imgur.com/YuG599J.png) 現在我們知道 `*(result+8)` 是個 `char` 了 就來做一個 `struct` ```cpp struct struct1 { _QWORD a1; _QWORD a2; _BYTE flag_i; }; ``` ![](https://i.imgur.com/UURcORn.png) 放上 `struct` 後看起來很舒服 `sub_1169` 做的事情就是 `init_struct1` 現在回到前一層 把 `struct1` 放上去 ![](https://i.imgur.com/FtB0YyS.png) 然後進入 `sub_11AC` #### `sub_11AC` ![](https://i.imgur.com/ZCxk6UN.png) 可以看到 `a1` 可能又是一個 `struct` 他做的的事情是把兩個 `struct` 指來指去 很像 `linked-list` 的行為 把原本的 `struct` 改成 `Node` ```cpp struct Node { Node *next; Node *prev; char flag_i; }; ``` ![](https://i.imgur.com/pc2gJ8G.png) 簡單的模擬幾次可以畫出以下的圖 ![](https://i.imgur.com/amWEGoU.jpg) 所以真的是 `liked-list` 把函式叫做 `append_node` `init_struct1` 改名為 `init_node` 回到前一層 ![](https://i.imgur.com/BG2V2BU.png) 變得好看許多 回到 `main` ![](https://i.imgur.com/fYzjUQz.png) 進入 `sub_125F` #### `sub_125F` ![](https://i.imgur.com/uwsXw7y.png) `:11` `byte_4041` 看起來像是 $3$ 個一組的東西 所以先放上 `struct` ```cpp struct struct2 { _BYTE a; _BYTE b; _BYTE c; }; ``` `:12` 迴圈會跑到 `(result = byte_4041[3*i]) == 0` 的時候 ![](https://i.imgur.com/Hxwd9H1.png) 看到這個之後覺得 `unk_4040` 跟 `byte_4041` 是同一個陣列 把 `byte_4040` 設城 `sruct2` ![](https://i.imgur.com/U16wlSB.png) 由迴圈的跳出條件可知陣列會到有 $0$ 的地方 推測陣列長度大概 68 ![](https://i.imgur.com/3wafjEj.jpg) 修好大概像這樣 ![](https://i.imgur.com/xekaz2d.png) 可以看出 `:11` 得知 `struct2.b` 控制迴圈是否繼續 `:15` `:16` `:19` 得知 `struct2.a` 如果是奇數 就在 `linked-list` 往後走 `struct2.b` 步 反之往前走 `struct2.b` 步 然後把走到的地方跟 `struct2.c` `xor` 加密 所以這個 function 是 `encrypt` 使用的是 `xor` 加密 #### `check_flag` ![](https://i.imgur.com/dzf1FGc.png) 得知加密後的 `flag` 位於 `byte_4120 (flag_enc)` 由於加密使用的是 `xor` 使用 `encrypt` 在加密一次 `flag_enc` 就會變回原本的 `flag` #### solve ##### `kekkou/exp.cpp` ```cpp int main() { Node *head; init_doubly_lined_list(&head, flag_enc); encrypt(head); print_flag(head); // output: FLAG{kekkou_muri_jya_nai?nai_ai_kazoe_te_cyotto_kurushi_ku_na_ru} } ``` ### [LAB] Why #### `main` ![](https://i.imgur.com/bzJGClz.png) 看出 `flag` 長度為 $25$ 把 `enc_flag` 拉出來 ```cpp unsigned char enc_flag[] = { 0x50, 0x56, 0x4B, 0x51, 0x85, 0x73, 0x78, 0x73, 0x7E, 0x69, 0x70, 0x73, 0x78, 0x73, 0x69, 0x77, 0x7A, 0x7C, 0x79, 0x7E, 0x6F, 0x6D, 0x7E, 0x2B, 0x87}; ``` 看到第一個字 `0x50` 如果照題目的運算的反運算來做 `+10` 會變成 `Z` 可是我們知道 `flag` 是 `F` 開頭的 應該是 `0x50-10` 才對 所以把 `flag_enc` 每個字都 `-10` ##### `why/exp_test.cpp` ```cpp int main() { char flag[25+1]; for (int i = 0; i < 25; i++) { flag[i] = enc_flag[i]; flag[i] -= 10; } flag[25] = 0; printf("%s", flag); // output: FLAG{init_fini_mprotect!} } ``` 可以發現我們已經取得 `flag` 了 不過繼續看看他的 `strings` #### `strings` ![](https://i.imgur.com/DzDFj5J.png) 從 `main` 裏面都找不到 `Correct :)` 的字串 看看他的 `xrefs` ![](https://i.imgur.com/cygOvDi.png) 找到了 `sub_11f8` `:11FC` 看到了 `main` 裏面的 `pass` 看看 `sub_11f8` `xrefs` 來到了 `init_array` `fini_array` #### `init_array` `fini_array` ![](https://i.imgur.com/43As2Re.png) 於 `init_array` 看到神秘的 `sub_1169` `sub_119d` `sub_10d8` #### `mprotect` ![](https://i.imgur.com/bXTeU64.png) ![](https://i.imgur.com/YxPo6N5.png) 看到 `sub_1169` `sub_10d8` 都呼叫了 `mprotect` > The mprotect() system call changes the specified pages to have protection prot. #### `sub_119d` ![](https://i.imgur.com/FE2v3Fl.png) 看到他把 `main_page+643` 取了 `2's complement` #### `main_page+643` ```cpp gef➤ p (char*)main_page+643 $10 = 0x555555555283 <main+89> "\n\213E\374H\230H\215\025\240-" gef➤ p main_page 'main_page' has unknown type; cast it to its declared type gef➤ p (char*) main_page $11 = 0x555555555000 <_init> "H\203\354\bH\213\005\305/" gef➤ p (char*) main_page+643 $12 = 0x555555555283 <main+89> "\n\213E\374H\230H\215\025\240-" gef➤ p (char*) main_page+640 $13 = 0x555555555280 <main+86> "\300\215H\n\213E\374H\230H\215\025\240-" ``` 看到 `main_page+643` 是 `<main+89>` ![](https://i.imgur.com/C8FzAX1.png) 他做的事就是直接把 `<main+89>` 的 `instruction` 更改 ![](https://i.imgur.com/hDV1oM8.jpg) 經由動態分析發現 我們的 `-10`  就變成 `+10` 了 所以上面的 `flag` 才會有那麼酷的情況 ### [HW] trace #### `main` ![](https://i.imgur.com/vjdQHET.png) 首先看到 `main` 呼叫了 `sub_1289` #### `sub_1289` ![](https://i.imgur.com/5MLZ6TS.png) 看到他把 `unk_4020` 的東西寫進 `/tmp/cs_2022_fall_ouo` ![](https://i.imgur.com/bnTjUQn.png) `unk_4020` 的東西看起來是 `ELF` 就先把 `sub_1289` 稱作 `write_elf_to_ouo` #### `sub_1346` ![](https://i.imgur.com/8SOELa9.png) 查了一下 [fork](https://www.geeksforgeeks.org/fork-system-call/) 的功能 表示上註解 然後追近 `sub_12DC` #### `sub_12DC` ![](https://i.imgur.com/GKsekZG.png) 查一下 [ptrace](https://www.linuxjournal.com/article/6100) 看到會執行 `/tmp/cs_2022_fall_ouo` 所以來看看 `/tmp/cs_2022_fall_ouo` #### `/tmp/cs_2022_fall_ouo` ![](https://i.imgur.com/D5dzTtz.png) 進去後 string 看到神秘的字串 ![](https://i.imgur.com/T7tMzb4.png) `Give me flag` 的 `xrefs` 有 `sub_4011B0` 可是 `sub_4011B0` 沒有 `xrefs` `Please` 的 `xrefs` 有 `sub_401196` 可是 `sub_401196` 沒有 `xrefs` `Well done!`  以及 `Try harder :(` 都找不到 `xrefs` 回到 `main` 然後近入 `sub_13C8` ![](https://i.imgur.com/zCQFqVq.png) #### `sub_13C8` ![](https://i.imgur.com/shMtOxI.png) 此函數做的是將 `child` 走一步然後取出他的 `reg` 值 所以 `&unk_7969` 更名為 `regs` 此函數更名為 `get_single_step_reg_13C8` 回到 `main` 進入 `sub_146E` #### `sub_146E` ![](https://i.imgur.com/MK8u29t.png) `PTRACE_PEEKTEXT` 的回傳值為 `addr_79E0` 位置的值 如果回傳值是 `0xE8CBCCDEADBEEFE8` 則把 `addr_79E0` 的位置寫入 `0x9090909090909090` 看到這邊覺得 `addr_79E0` 應該要是 `regs.rip` 才對 所以把 https://sites.uclouvain.be/SystInfo/usr/include/sys/user.h.html 的 `user_regs_struct` 搬進來 ```cpp struct user_regs_struct { unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; unsigned long r11; unsigned long r10; unsigned long r9; unsigned long r8; unsigned long rax; unsigned long rcx; unsigned long rdx; unsigned long rsi; unsigned long rdi; unsigned long orig_rax; unsigned long rip; unsigned long cs; unsigned long eflags; unsigned long rsp; unsigned long ss; unsigned long fs_base; unsigned long gs_base; unsigned long ds; unsigned long es; unsigned long fs; unsigned long gs; }; ``` ![](https://i.imgur.com/lcIleXe.png) 看起來正常多了 重新解讀這個 function 當 `child` 執行到 `0xE8CBCCDEADBEEFE8LL` 這個 instruction 的時候 把他換成 `0x9090909090909090LL` <!-- ##### `0xE8CBCCDEADBEEFE8LL` ```asm 0: e8 ef be ad de call 0xffffffffdeadbef4 5: cc int3 6: cb retf 7: e8 .byte 0xe8 ``` ##### `0x9090909090909090LL` ```asm 0: 90 nop 1: 90 nop 2: 90 nop 3: 90 nop 4: 90 nop 5: 90 nop 6: 90 nop 7: 90 nop ``` --> 之前直接執行 `ouo` 的時候都會 `seg fault` 可能就是因為 `0xE8CBCCDEADBEEFE8LL` 先找到他然後把他 `patch` 城 `0x9090909090909090LL` <!-- ![](https://i.imgur.com/mhotwmW.jpg) ![](https://i.imgur.com/B2vd5QF.jpg) --> ![](https://i.imgur.com/evO62aL.jpg) 現在 `ouo` 可以正常執行了 接下來只要 `debug` `ouo` 就可以了! #### `ouo` ![](https://i.imgur.com/XNDX0w9.png) `ouo` 變得好正常 經過整理可以看到以下的功能 ![](https://i.imgur.com/52aQ4n4.png) ![](https://i.imgur.com/gMCgisw.png) 可知 `flag` 的長度為 `0x22+1=23 ` 由於他是使用 `xor` 加密 只要多加密一次就會是原本 `flag` ##### `trace/exp.cpp` ```cpp int main () { decode_flag_4011CA(); printf("%s", s2); // output: FLAG{TrAc3_M3_1F_U_cAN} } ``` ### [HW] pwn_myself ![](https://i.imgur.com/d08s0iD.png) 直接執行發現會回傳 255 #### `strings` ![](https://i.imgur.com/64YCpCW.png) 看起來有用 `OpenSSL` #### `main` ![](https://i.imgur.com/H6ameaW.png) 可以發現 `:8` 的地方就是造成 回傳 255 的原因 所以 `geteuid` 要是 0 才會跑的下去 > The getuid() function returns the real user ID of the calling process. The geteuid() function returns the effective user ID of the calling process. 用 `sudo` 跑就可以達成條件 或者動態分吸直接把 `geteuid` 回傳值 `rax` 歸零 #### 動態分吸 把 `geteuid` 回傳值 `rax` 歸零 成功進入 `6A37` ![](https://i.imgur.com/XqO9zQA.png) 來看 `sub_66A37` #### `sub_66A37` ![](https://i.imgur.com/VdFXSdy.png) 感覺除了變數的操作之外看不到什麼 猜測 flag 長度為 $56$ 停在 `:31` 看一下 `v8` 的地方 ![](https://i.imgur.com/IRZjh9H.jpg) 然後跑完回到 `main` ![](https://i.imgur.com/ztj5qKk.jpg) 之後出現 ```cpp 55C76C1A6B6A: got SIGSEGV signal (Segmentation violation) (exc.code b, tid 30104) Debugger: process has exited (exit code 11) ``` 那是發生於 `leave` 這個地方 ![](https://i.imgur.com/M4jGCWc.png) 換用 `sudo` 跑也發生一樣的事 ![](https://i.imgur.com/W3uAvNv.png) 在世一次 觀察 `rbp` `rsp` `rip` #### `stack` ![](https://i.imgur.com/l5m2uuJ.jpg) ![](https://i.imgur.com/uKI7IBW.jpg) ![](https://i.imgur.com/Yh3jMTv.jpg) ![](https://i.imgur.com/baH9KPE.jpg) 結果沒有 `segmentation fault`?? 還跑到了其他的地方 猜測是 `stack` 的 `rip` 被竄改到了? stack 上一堆 `A` 感覺是用來 `pwn` 的 #### `sub_668BF` ![](https://i.imgur.com/cKGCzHA.jpg) 看到呼叫了 `_read` ![](https://i.imgur.com/3HcR8oO.png) ![](https://i.imgur.com/4yXeaVq.png) 來看 `sub_6668F` #### `sub_6668F` ![](https://i.imgur.com/Vua7Uu2.png) 進去之後覺得上面都寫 `/dev/input` 了 那應該是鍵盤之類的? `:13` 的 `v3` 正是讀取地方的關鍵 這裡想要動態分析 但是 `IDA` 不知道為什麼跑不起來了 `QQ` > 因為明天要期中考所以先解到這裡 > 感覺快要解出來了 --- 回到上一層,進入 `sub_6650C`。 #### `sub_6650C` ![](https://hackmd.io/_uploads/S1t-agmwn.png) 進入 `sub_6643F` #### `sub_6643F` ![](https://hackmd.io/_uploads/BkA_6lQwh.png) 看到 :11 是一個會跑 48 次的迴圈,而 48 正是 16 的三倍,懷疑是 block cipher 之類的東西。 看到 `byte_397040` 在 `.data`,長度也吻合,也許是 ciphertext。 進入 `sub_6622D` #### `sub_6622D` ![](https://hackmd.io/_uploads/HkJN8zEvn.png) 看到 :13 有跟我們猜是 ciphertext 相鄰的東西,感覺很可疑,跟進去看看。 #### `sub_6CF20` ![](https://hackmd.io/_uploads/HJT7PzEvn.png) 進來看到了與 block_size 相關的文字,應該就是 block_cipher。 然後那個 `../crypto/evp/evp_enc.c` 應該指的是這個。 https://github.com/openssl/openssl/blob/master/crypto/evp/evp_enc.c 進去看看會發現跟 `../crypto/evp/evp_enc.c` 長得很像(下圖的 assert),也許是同個東西? ![](https://hackmd.io/_uploads/BkAm5f4D2.png) 追到那個 assert 的 function 參數列,看到了放 `iv` 跟 `key` 的位置,對照回原本的 `sub_6622D`(上一層)的參數看看。 ![](https://hackmd.io/_uploads/rylaqfNwn.png) #### `sub_6622D` 把 `../crypto/evp/evp_enc.c` 裡面我們找到的那個很像 function 參數標回去,會發現原來我們前面覺得可疑的地方是 `key` 跟 `iv`。有 `key`、`iv`、`ciphertext` 就可以把他們拉出來來解密了。 ![](https://hackmd.io/_uploads/HJPE3fVD2.png) #### sol.py ```python ciphertext = 'D2B240F2DE77E085FDE5BFB1EBF76418E4AD85EF8068DA2C252DE1F8DDE70B59E8D757372FB54125785AB982228D8126' key = '4B29470F38D4A34D1C9F4FC774E4296A' iv = 'B59AEC9251E25E3F9081E427192E5029' ciphertext = bytes.fromhex(ciphertext) key = bytes.fromhex(key) iv = bytes.fromhex(iv) # --- START: CODE WRITTEN BY CHATGPT --- # from Crypto.Cipher import AES from Crypto.Util.Padding import unpad def decrypt_AES(ciphertext, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return plaintext # --- END: CODE WRITTEN BY CHATGPT --- # plaintext = decrypt_AES(ciphertext, key, iv) print(plaintext.decode('utf-8')) # output: FLAG{I5_tH15_cHA1l3nGe_t0O_eA5Y_0R_tO0_HARD} ``` <!-- qtwayland5 --> <!-- --- #### 下面是一些有去看但好像沒什麼關係的地方 #### `init_array` `fini_array` 應該有偷放一些東西在 `init_array` `fini_array` 打開 `Jump > Jump to segment...` 看到 ![](https://i.imgur.com/OFgruTn.png) 看看 `init_array` `fini_array` ![](https://i.imgur.com/Hud5c3e.png) 進入 `sub_66120` #### `sub_66120` ![](https://i.imgur.com/qaZd0Ra.png) 進入 `sub_660A0` ![](https://i.imgur.com/Vg6M7Vl.png) 沒東西 回到 `init_array` `fini_array` 進入 `fini_array` 的 `sub_660E0` #### `sub_660E0` ![](https://i.imgur.com/4TOp5c7.png) 進入 `sub_66070` #### `sub_66070` ![](https://i.imgur.com/By2Xepk.png) 沒東西 #### `.init` `.fini` ![](https://i.imgur.com/DP2kbSd.png) ![](https://i.imgur.com/1OEVPvK.png) `.init` 感覺有東西 進入 `sub_197450` #### `sub_197450` ![](https://i.imgur.com/30y7Vox.png) 感覺跟 `OpenSSL` 有關 進入 `sub_1973A0` #### `sub_1973A0` ![](https://i.imgur.com/PwQdq3j.png) 進入 `sub_197640` #### `sub_197640` ![](https://i.imgur.com/nABZsti.png) 發現是把大寫換成小寫 更名為 `to_lower_197640` 回到 `sub_1973A0` -->