---
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`
-->