# SWSEC LAB2 Writeup ###### tags: `swsec` `writeup` <style> p:has(img) { text-align: center; } .markdown-body img { /* max-width: 80%; */ } </style> ## HelloRevWorld 雖然 Flag 是 wide char,但透過 ghidra 的 Defined Strings window,可以簡單的將 wide char string 提取出來。 ![](https://hackmd.io/_uploads/Sy-L8ohGT.png) FLAG: `FLAG{h311O_revers1ng_3ngineer5}` ## AssemblyDev `arithmetic.asm` - 照著算數學 ```assembly ; a + b mov eax, [rsp] add eax, [rsp+0x4] ; a - b mov ebx, [rsp] sub ebx, [rsp+0x4] ; -c mov ecx, [rsp+0x8] neg ecx ; 9*a + 7 mov edx, [rsp] lea edx, [edx+edx*8+7] ``` `[reg]` 可以用 reg 中的值作為 address 做存取 `mov` 無法直接傳兩個 address,故先放到 `r14`/`r15` 暫存。 `data_movement.asm` ```assembly add rax, 0x87 ; RAX += 0x87 sub rbx, 0x63 ; RBX += 0x63 ; swap RCX, RDX mov r15, rcx mov rcx, rdx mov rdx, r15 ; modify memory add dword [rsp], 0xdeadbeef sub dword [rsp+0x4], 0xfaceb00c ; swap mov r14, [rsp+0x8] mov r15, [rsp+0xc] mov [rsp+0x8], r15 mov [rsp+0xc], r14 ``` cmp1、cmp2 中會先做條件成立的行為,若條件成立,就跳過條件不成立時的行為 (`jge`, `jb`) 檢查是否是偶數,可以用 `test reg, 1` 配合 `jz` 跳到對應行為 (`test` 中實際上做了 AND) 在 odd 中加上 `jmp end` 以跳過 even 的 instruction `condition.asm` ```assembly cmp1: mov eax, [rsp] ; a mov ebx, [rsp+0x4] ; b cmp eax, ebx jge cmp2 mov eax, ebx ; a < b cmp2: mov ebx, [rsp+0x8] ; c mov edx, [rsp+0xc] ; d cmp ebx, edx jb cmp3 mov ebx, edx ; c >= d cmp3: mov ecx, [rsp+0x8] test ecx, 1 jz even ; ecx & 1 == 0 odd: shr ecx, 3 jmp end even: shl ecx, 2 end: nop ``` ![](https://hackmd.io/_uploads/BkUoUi2GT.png) FLAG: `FLAG{c0d1Ng_1n_a5s3mB1y_i5_sO_fun!}` ## Clipboard Stealer 1 -- sub_140001C80 查閱 `GetModuleFileNameA` 文件,若 `hModule` 為 NULL,則回傳目前執行檔的路徑。 另其在 `GetUserNameA` 後,將其傳入 `sprinf` 造成完整路徑,並透過 `CopyFileA` 將目前執行檔複製到該路徑,且使用 `SetFileAttributeA` 設該檔為 `FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM` 以隱藏該檔案。 ![image](https://hackmd.io/_uploads/ByB6iF2Ep.png) 該路徑位於 `Start Menu\Programs\Startup` 下,會於開機時自動啟動,故推論此 function 之行為為 `Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder`,其 MITRE ATT&CK technique ID 為 `T1547.001`,推得 flag。 ![](https://hackmd.io/_uploads/HkQRLj3G6.png) FLAG: `FLAG{T1547.001}` ## Clipboard Stealer 2 -- sub_140001030 此 function 建立了一個 timer,會於 2023/11/18 後才啟動。 ![image](https://hackmd.io/_uploads/Hy29CF3Va.png) 其可用來讓此惡意程式只在環境符合特定條件時執行,以針對特定對象進行攻擊。 也並未有繞過 VM 的意圖,故推測為 `Defense Evasion — Execution Guardrails`,其 MITRE ATT&CK technique ID 為 `T1480`,推得 flag。 ![](https://hackmd.io/_uploads/Syo6Ui3Ga.png) FLAG: `FLAG{T1480}` ## Clipboard Stealer 3 -- sub_140001120 xor_key 原本為 `int[2]`,將其 retype 成 `char[4]`。 重新命名變數後可以發現為簡單的 XOR 加密。 ![image](https://hackmd.io/_uploads/BkegS23Np.png) 透過 Export data 把 mutex_name 的 data 複製出來,寫個 script 解密。 `Clipboard Stealer 3 -- sub_140001120/solve.py` ```python xor_key = bytes.fromhex("6463627A")[::-1] # little endian mutex_name = [ 0x0E, 0x0A, 0x52, 0x51, 0x25, 0x2B, 0x57, 0x3B, 0x4E, 0x3D, 0x0E, 0x11, 0x0E, 0x51, 0x1B, 0x3B, 0x11, 0x53, 0x2F, 0x28, 0x25, 0x31, 0x14, 0x0D, 0x0E, 0x01, 0x2B, 0x64 ] for i in range(len(mutex_name)): print(chr(mutex_name[i] ^ xor_key[i % len(xor_key)]), end="") ``` FLAG: `FLAG{th15_I4_4_mut3x_k1LL_SwitcH}` ## Clipboard Stealer 4 -- Extract Next Stage Payload 觀察 `sub_140001BF0`,裡面 `sub_140001870` 之參數為 pointer of pointer 與 pointer of size,看起來像在讀東西。 ![image](https://hackmd.io/_uploads/B1JEApnET.png) 查看 `sub_140001870` 實作,並參考其訊息,可推論 pe_data 為內嵌之 dll。 將此 function 重命名為 `getEmbeddedPE`。 ![image](https://hackmd.io/_uploads/Sy6GRp246.png) 透過 Shift+E 將 `pe_data` export 為 `exported.dll`。 ![image](https://hackmd.io/_uploads/H1wDx0h4p.png) 將 `exported.dll` 提取出來後,可以透過 `certutil -hashfile exported.dll MD5` 來計算其 MD5 hash。 ![](https://hackmd.io/_uploads/BJFkPjnfa.png =x60) FLAG: `FLAG{462fe0007f86957f59824e113f78947c}` ## Clipboard Stealer 5 -- Exfiltrate Data 該 dll 忘記拔掉 debug info,function name 都還留著。 從 `_DllMainCRTStartUp` -> `DllMain` 會看到兩個 function call,其中一個叫做 `collect_and_exfiltrate`。 跟進去之後會看到 `collect_data` 與 `exfiltrate`,而 `exfiltrate` 中又有`connect_to_c2` 與 `send_collected_data_to_c2`。 `connect_to_c2` 中能看到以下程式碼: ![image](https://hackmd.io/_uploads/H1vWEfpNa.png) 其行為為 TCP 連線到 `192.168.10.10:11187`,故可知流量中何者為回傳 C2 的封包。 簡單觀察 `send_collected_data_to_c2` 後,可以回推其封包格式。 每個 packet 都是 0x4C 的大小,而有個欄位會固定為 `0x11877811`,另外 switch 所判斷的值看起來會決定行為,故猜設為 `cmd`,整個封包後 24 個 byte 則為 `data`。 建立 `C2_PACKET` struct: ![image](https://hackmd.io/_uploads/ByAGrfa4T.png) 最後標註如下: ![image](https://hackmd.io/_uploads/H1koVMaEp.png) `cmd = 1` 會從 C2 取得 key 並將資料做加密,`cmd = 2` 則是將加密的資料回傳。 查看 `encrypt_data`,其使用了 `RC4` 作為加密演算法,並取 8 bytes 作為 key。 ![image](https://hackmd.io/_uploads/BkQGIfTET.png) 寫一個腳本從封包撈取資料並解密。 `Clipboard Stealer 5 -- Exfiltrate Data/solve.py` ```python from scapy.all import * from Crypto.Cipher import ARC4 C2 = "192.168.10.10" MAGIC = b"\x11\x87\x78\x11"[::-1] pkts = rdpcap("capture.pcapng") key = None for pkt in pkts: if TCP not in pkt: continue payload = bytes(pkt[TCP].payload) if not payload.startswith(MAGIC): continue cmd = int.from_bytes(payload[0x8:0x8+4], 'little') data = payload[0xC:0xC+24] if pkt[IP].src == C2 and cmd == 1: key = data[:8] print(f"key={key.hex()}") elif pkt[IP].dst == C2 and cmd == 2: cipher = ARC4.new(key) print(f"cipher={data.hex()}") print(cipher.decrypt(data).decode()) ``` FLAG: `FLAG{C2_cU540m_Pr0t0C01}` ## Clipboard Stealer 6 -- Dynamic API Resolution 用 Ctrl+E 跳到 `my_start`。 先將 `p_InLoadOrderModuleList` retype 成 `_LDR_DATA_TABLE_ENTRY *`,讓 `j` 的型別也正確,`j[6].Flink` 此時變成 `j->BaseDllName.Buffer`,最後將 `j` rename 成 `module `。 將底下針對 `dll_name` 的判斷常數按 R 轉成 char,可以發現他在找 `dll_name == kernel32`,也就是 `kernel32.dll`。 <img src=https://hackmd.io/_uploads/ByXR_zaVT.png style="width: 55%"> <img src=https://hackmd.io/_uploads/BJvyKf6N6.png style="width: 45%"> 左: Before; 右: After ![image](https://hackmd.io/_uploads/SJKGsGTNa.png) 往下滑到 `user32` 的部分,並將變數 retype: `export_table_user32` -> `IMAGE_EXPORT_DIRECTORY *`、`addr_of_*` -> `_DWORD *`。 可以看到程式會遍歷 `export_table_user32` 並取其 function name (`v41`) 做 hash,當 hash 符合時,則將其 address 載入。 在 [Mr-Un1k0d3r/WindowsDllsExport](https://github.com/Mr-Un1k0d3r/WindowsDllsExport) 可找到 `user32.dll` 的 export table 中所有 function 名稱,全部掃過一次即可找到該 API 為何者。 `Clipboard Stealer 6 -- Dynamic API Resolution/solve.py` ```python import requests name_list_url = "https://raw.githubusercontent.com/" \ "Mr-Un1k0d3r/WindowsDllsExport/main/Win11-22000/user32.dll.txt" res = requests.get(name_list_url) name_list = res.text.splitlines(keepends=False) def rol4(n, b): bits = f"{n:032b}" # rol"4" -> 4 bytes = 32bits return int(bits[b:] + bits[:b], 2) & ((1 << 32) - 1) for name in name_list: fn_hash = 0 for c in name: fn_hash += ord(c) + rol4(fn_hash, 11) + 1187 fn_hash &= ((1 << 32) - 1) if fn_hash == 0x416F607: print(name) break ``` FLAG: `FLAG{MessageBoxA}` ## Super Angry ![image](https://hackmd.io/_uploads/r1Rc_h3Na.png) 此題會將 argv 作為 command line arg 傳入 `sub_11C9` 做轉換之後驗證。 ![image](https://hackmd.io/_uploads/ryMwY334p.png) `sub_11C9` 看起來像一個 state machine,第三個參數為長度。 可透過 angr 爆搜。 ```python import angr import claripy import sys def solve(elf_binary, l): project = angr.Project(elf_binary) flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(l)] flag = claripy.Concat(*flag_chars) initial_state = project.factory.entry_state( args=[elf_binary, flag], add_options=angr.options.unicorn ) simulation = project.factory.simgr(initial_state) simulation.explore(find=is_successful, avoid=fail) if len(simulation.found) > 0: for solution_state in simulation.found: print("[>>] {!r}".format(solution_state.solver.eval(flag,cast_to=bytes))) else: print("[>>] no solution found :(") def is_successful(state): output = state.posix.dumps(sys.stdout.fileno()) return b'Correct' in output def fail(state): output = state.posix.dumps(sys.stdout.fileno()) return b'Incorrect' in output solve("./super_angry", 0x20) ``` 透過 `claripy.BVS('flag_%d' % i, 8)` 建立一個 8 bit char,參考 `sub_11C9` 的呼叫,flag 長度為 0x20,最後將這些 BVS concat 起來。 另外透過 `project.factory.entry_state` 建立 state 及指定輸入位置 (`args`),與 `project.factory.simgr` 建立 simulation。 `simlutaion.explore` 來設定何謂正確輸出,可傳入 function 或 address。 ![image.png](https://hackmd.io/_uploads/HJfVZXzXa.png) FLAG: `FLAG{knowing_how_2_angr!}` ## Scramble 針對每個 flag 字元,題目會隨機產生一系列操作 (add, sub, lsh) 來變動其值,並提供過程與結果。 可以透過 z3 來設定 constraint 來自動解原始值 (`x`)。BitVec 的 bits = 32 = 8 * 4 bytes。 內層迴圈會建立多個 `x{i}` 作為 intermediate state。 最後在 `solver.check()` = `sat` 後,即可透過 model 取得 `x` (flag 字元)。 這裡可以加上 `x < 256` 與 `x > 0` 來確保解出來的是 char。 外層迴圈用來指定解第幾個字元。 ```python= from z3 import * pattern = [...] result = [...] flag = [] for op, r in zip(pattern, result): s = Solver() x = BitVec('x', 32) s.add(x < 256) s.add(x > 0) xs = [x] for i, (operator, operand) in enumerate(op): xi = BitVec(f'x{i}', 32) if operator == "add": s.add(xs[-1] + operand == xi) elif operator == "sub": s.add(xs[-1] - operand == xi) elif operator == "lsh": s.add((xs[-1] << operand) == xi) xs.append(xi) s.add(xi == r) if s.check() == sat: flag.append(s.model()[x].as_long()) else: print(s) print(flag) print(*map(chr, flag), sep="") ``` ![solve.png](https://hackmd.io/_uploads/Hywrg7f7p.png) FLAG: `FLAG{scramble_and_using_solver_to_solve_it}` ## Unpackme 直接 strings 可發現此程式透過 upx 加殼 ![image](https://hackmd.io/_uploads/Hkm0b52NT.png) 試著用 `upx -d` 來脫殼,卻顯示 `Checksum Error` ![image](https://hackmd.io/_uploads/rkttW93Ea.png) 自行 patch upx 的 checksum 檢查,即可正常脫殼 <img src=https://hackmd.io/_uploads/SkuzGqnEa.png style="width: 36%"> &nbsp; <img src=https://hackmd.io/_uploads/S1YVW9hE6.png style="width: 60%"> 隨意丟進 ghidra 看即可知道其 input 為 `just_a_key` 時,即會解密 flag 並輸出。 ![image](https://hackmd.io/_uploads/HyeEQq3Np.png) ![image.png](https://hackmd.io/_uploads/Hydrb7G7p.png) FLAG: `FLAG{just_4_simple_unpackme_challenge!}`