---
# System prepended metadata

title: SWSEC HW2 Writeup
tags: [swsec, writeup]

---

# SWSEC HW2 Writeup
###### tags: `swsec` `writeup`

<style>
    p:has(img) {
        text-align: center;
    }
    
    .markdown-body img {
        max-width: 75%;
    }
</style>

## crackme_vectorization
一開始先吃一個 size，然後讀入 size 個 int。
malloc 一塊 0x10 大的 memory，並將 `ceil(sqrt(size))` 存入，且透過 SIMD 指令 (`_mm_shuffle_epi32`) 複製成兩份。

malloc 另一塊 memory 放前面提到的 int 們。

![image](https://hackmd.io/_uploads/ByhEgBpEp.png)

![image](https://hackmd.io/_uploads/BJjWWrTN6.png =40%x)

Create Struct:

![image](https://hackmd.io/_uploads/Sk7tbraEa.png =50%x)

下面檢查 `sqrt_ceil` = 7，才會傳入 `sub_1300` (`matrix_mul`) 做檢查。

![image](https://hackmd.io/_uploads/SkdQZSpEp.png =60%x)

malloc 另一塊 memory 作為回傳值:

![image](https://hackmd.io/_uploads/rkikfSaNT.png)

接下來因為 `sqrt_ceil` 固定為 7，因此有些計算結果為定值 (`const*` 變數們)，下面有一連串 SIMD 操作:

![image](https://hackmd.io/_uploads/HJ6U7rTEp.png)
![image](https://hackmd.io/_uploads/B1R_XHpV6.png)
![image](https://hackmd.io/_uploads/SynWBHTE6.png)


可以參考 https://www.officedaytime.com/simd512e/ 來看 SIMD 指令的行為圖解。
中間有許多 if 會被跳過，可以用 `Numpad -` 把那些 branch 隱藏起來。

觀察 `chunk_idx` 與 `buffer_cnt`，可以發現 input 會被切成 7 個 chunk，然後透過 `chunk_idx` 來存取 chunk 中的第幾個數字，而 key 則是直接線性存取。

另外觀察 LABEL_21 沒有 SIMD 的部分，他把上面 SIMD 沒處理掉的第 5、6、7 個 chunk 也做了 reduce。

最後直接通出來是矩陣乘法，`input @ key = res`，直接解反矩陣得到:
`crackme_vectorization/solve.py`
```python
key_mat = numpy.array(key, dtype=int).reshape(7, 7)
res_mat = numpy.array(res, dtype=int).reshape(7, 7)
input_mat = numpy.linalg.solve(key_mat, res_mat).round().astype(int)

assert numpy.equal(key_mat @ input_mat, res_mat).all() # sanity check
print(*input_mat.flatten().tolist())
print(*map(chr, input_mat.flatten().tolist()), sep="")
```

要記得用 `.round()` 來把浮點數誤差修正，不能直接轉 int，否則有些 char 會無條件捨去造成錯誤。

<img src="https://hackmd.io/_uploads/rk4-qtnf6.png" style="width: 65%">
<img src="https://hackmd.io/_uploads/ByF9KtnMT.png" style="width: 35%">

FLAG: `FLAG{yOu_kn0w_hOw_to_r3v3r53_4_m47riX!}`

## Banana Donut Verifier
將 scanf 的參數 rename 為 `input`

![image](https://hackmd.io/_uploads/HJRUJXTVa.png)

觀察 xref，可以發現他只會被上面的迴圈拿 intermediate state 做 XOR，以及傳入某個 function (`sub_1A05`，renamed to `some_kind_of_hash`)。

![image](https://hackmd.io/_uploads/By0u1QpVp.png)

另外底下可以看到其驗證邏輯，XOR 過後的 input 之 hash 要跟 `off_6050` 算出來的一樣。

![image](https://hackmd.io/_uploads/SkGllmaEa.png)

若按下 `Ctrl+D`，則可直接傳 EOF 給 scanf，讓 input 內全為 0，這樣就能得到 XOR 的 mask，且 input = mask $\oplus$ ans (`off_6050`)。

`mask.txt` 與 `ans.txt` 是在做 `some_kind_of_hash(input, 1024uLL);` 前下斷點，透過 gdb dump memory 資料，將兩者 XOR 則可取得 session 以交換 flag。

`Banana Donut Verifier/solve.py`
```python
def parse(f):
    with open(f, "r") as f:
        ret = sum([
            list(map(lambda x: int(x, 16), l.strip()[16:].split("    ")))
            for l in f.readlines()
        ], [])
    return ret

mask = parse("mask.txt") # input, $rbp-0x480
ans = parse("ans.txt") # 0x2010 (.rodata)
val = [a ^ b for a, b in zip(mask, ans)]

print(*map(chr, val), sep="")
```

<img src="https://hackmd.io/_uploads/BJdTk8pGT.png" style="width: 55%">
<img src="https://hackmd.io/_uploads/By_6JLaza.png" style="width: 45%">

FLAG: `FLAG{d0_Y0u_l1k3_b4n4Na_d0Nut?}`

## Baby Ransom 1 -- Next Stage Payload
Entry point -> `start` 裡面呼叫了 `sub_140001131`，重命名為 `main`。

下方有複製 `argv` 的部分，並將其傳入 `sub_140001DBB` (`load_payload`)。

![image](https://hackmd.io/_uploads/Hyj9fQp4a.png)

裡面檢查 `data_140001DBB` (`encrypted_command`) 是否是可以存取的 web url，若是則不載入。

![image](https://hackmd.io/_uploads/BkTAzmaN6.png)

此資料實際上為 XOR 加密的其他 payload。

![image](https://hackmd.io/_uploads/SJnzXXpVT.png)

![image](https://hackmd.io/_uploads/SJ3JI76V6.png)

跟入 `sub_140001B0A` (`hide_self`):

![image](https://hackmd.io/_uploads/SkGAEmpVa.png)

前面先呼叫 `SHGetKnownFolderPath` API 拿了一個路徑，其為使用者的下載資料夾，參考文件如下:
https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid

<img src=https://hackmd.io/_uploads/BJAYN7pE6.png style="width: 50%">
<img src=https://hackmd.io/_uploads/Hk3947aE6.png style="width: 50%">

而後透過 `PathAllocCombine` 將 XOR 解密後的路徑與使用者下載資料夾做組合。

![image](https://hackmd.io/_uploads/Bk9FrQTN6.png)

下方將自己移到該資料夾，並將自己設成 `FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM` 以隱藏自身。最後呼叫 `sub_140001992` (`schtasks_create`)。

![image](https://hackmd.io/_uploads/ryArLX6Na.png)

`schtasks_create` 會把自己透過 `schtasks` 來排程，解密後指令為 `schtasks /create /sc minute /mo 5 /tn "Microsoft Windows Update" /tr "%s thisisthestart!!" /f`，可以看到指定了參數 `thisisthestart!!`。

![image](https://hackmd.io/_uploads/rJWCIXaNa.png)

後面若檔案是在正確的路徑 (`ppszPathOut`) 被執行，則呼叫 `sub_14000197A`，裡面最終呼叫 `sub_1400018DA`，並檢查 `IsDebuggerPresent` 與參數，若檢查通過即開始惡意行為  (`sub_140001653`，`start_payload`)。

![image](https://hackmd.io/_uploads/SyPNv7a4T.png)

裡面呼叫 `sub_14000155A` (`decrypt_next_stage_payload`) 來取得 next stage payload，並透過 `sub_1400013B4` (`migrate`) 將 payload inject 到其他 process (`calc`)。

![image](https://hackmd.io/_uploads/SycKPQaVa.png)

跟進 `decrypt_next_stage_payload`，可以看到 payload 是透過 `FindResourceA` 取得。

![image](https://hackmd.io/_uploads/BkTIX_8Va.png)

並用 `0x8711` 做了 XOR 加密。

![image](https://hackmd.io/_uploads/SJ1K7_UNT.png)

用 Resource Hacker dump 加密的 payload 並自行解密。

![image](https://hackmd.io/_uploads/H1ig7uUV6.png)

`Baby Ransom 1 -- Next Stage Payload/decrypt.py`
```python
import itertools

key = b"\x11\x87"

payload = b""

with open("next_stage.bin", "rb") as f:
    encrypted_payload = f.read()

for a, b in zip(encrypted_payload, itertools.cycle(key)):
    payload += (a ^ b).to_bytes(1, 'little')
    
with open("payload.dll", "wb") as f:
    f.write(payload)
```

![image](https://hackmd.io/_uploads/BywO8dU4a.png)


FLAG: `FLAG{e6b77096375bcff4c8bc765e599fbbc0}`

## Baby Ransom 2 -- Encrypted File
`start` -> `__scrt_common_main_seh` -> `WinMain` -> `sub_140002870`

![image](https://hackmd.io/_uploads/SJYst7pEp.png =40%x)

`sub_140001B10` (`my_start`)
retype `p_InLoadOrderModuleList` -> `_LDR_DATA_TABLE_ENTRY *`
`DllNameBuffer` -> check loading module

retype `v17` -> `IMAGE_EXPORT_DIRECTORY *` => `v5`: `sub_140002810(Function Name)`

![image](https://hackmd.io/_uploads/B1VTtXp4T.png)

`sub_140002810` (`FNV_1a`)

![image](https://hackmd.io/_uploads/H1_A5XTE6.png =40%x)

`v5` 傳入 switch case，若 hash 符合則將對應的 address 設為 `v8` (`AddressOfFunction`)。

寫個腳本 parse address 與其 hash value，以快速計算並透過 `MakeName` 重新命名該 address。
此處 `*.dll.txt` 存有 export table 所有 function name，`cpp` 則為 IDA 複製出來的 switch case decompile 結果。

`Baby Ransom 2 – Encrypted File/brute.py`
```python
import re
import os
import glob

# FNV-1a
def name_hash(a1):
  v2 = 0xCBF29CE484222325
  for c in a1:
    v3 = (ord(c) ^ v2) & ((1 << 64) - 1)
    v2 = (0x100000001B3 * v3) & ((1 << 64) - 1)
  return v2

for fn in glob.glob("*.dll.txt"):
  dll = fn.split(".")[0]
  cpp = f"{dll}.cpp"
  if not os.path.exists(cpp): continue

  db = dict()
  with open(fn) as f:
    for l in f.readlines():
      db[name_hash(l.strip())] = l.strip()

  with open(cpp, "r", encoding="utf-8") as f:
    try:
      while True:
        h = int(re.findall("case (0x\w+)i64:", f.readline())[0], 16)
        a = re.findall("qword_(\w+) = v", f.readline())[0]
        f.readline() # break
        print(f'MakeName(0x{a}, "{db[h]}_{a[-4:]}");')
    except:
      pass
```

效果:

![image](https://hackmd.io/_uploads/ByYE2QpVa.png =40%x)

繼續跟 `sub_140001660` (`do_encryption`):

![image](https://hackmd.io/_uploads/SJ0qhXTN6.png)

其從 `advapi32.dll` 載入了 `SystemFunction033`，並把資料夾底下所有檔案丟到 `sub_140001960` (`encrypt_file`) 來加密。

`sub_1400028B0` (`download_robots_txt`) 裡面下載了 `https://shouldhavecat.com/robots.txt`，並取 `flag.txt[2687:2705]` 作為 key (length = 19)。

![image](https://hackmd.io/_uploads/r1kphm6V6.png)

`sub_140001960` (`encrypt_file`)

![image](https://hackmd.io/_uploads/S1bYCXpVp.png)

參考 https://osandamalith.com/2022/11/10/encrypting-shellcode-using-systemfunction032-033/ ，得知 `SystemFunction033` 為 RC4，以及其參數結構:

![image](https://hackmd.io/_uploads/SkuT1Np4a.png =40%x)

另外能看到 L23，key length 實際上只取了 8 bytes。

寫腳本解密 `enc_flag.txt`。

`Baby Ransom 2 – Encrypted File/solve.py`
```python
from Crypto.Cipher import ARC4
import requests as r

url = "https://shouldhavecat.com/robots.txt"
res = r.get(url)
key = res.content[2687:2687+8]
cipher = ARC4.new(key)

with open("enc_flag.txt", "rb") as f:
    flag = cipher.decrypt(f.read())

print(flag.decode())
```

FLAG: `FLAG{50_y0u_p4y_7h3_r4n50m?!hmmmmm}`

## Evil Flag Checker
此題有許多 anti debugger 機制，如 `IsDebuggerPresent` 與 `SEH` 跳 EIP 等，但如果能直接跳到驗證邏輯的 function，就不需要動態跑一個一個跟，也就不會被 anti debugger 噁心到。

![image](https://hackmd.io/_uploads/SkbFlNaVa.png)
![image](https://hackmd.io/_uploads/S1p3l4pE6.png)

從字串回推驗證 function 位置: `sub_4013C0` (原本有點壞掉，對著 function 按 y 重跑他就解好了 ??)

![image](https://hackmd.io/_uploads/B1q1bETVT.png =40%x)

驗證邏輯在 `sub_4012A0` (`validate_flag`):

![image](https://hackmd.io/_uploads/HkvNWE6NT.png)

前半段在計算 hash。

![image](https://hackmd.io/_uploads/rJKPWVT4T.png)

後半段看起來像優化過的 `strcmp`，所以不管他。

照著 hash 邏輯重新計算，寫個腳本解密。

`Evil Flag Checker/solve.py`
```python
unk_403400 = [
  0xED, 0x03, 0x81, 0x69, 0x7B, 0x84, 0xA6, 0xA0, 0x5B, 0x2B, 
  0xB6, 0xE6, 0x5C, 0x57, 0xC9, 0x99, 0xE8, 0xB2, 0x20, 0x72, 
  0x38, 0xF1, 0x58 #,0x00
]

def ror4(n, b):
    bits = f"{n:>032b}"
    return int(bits[-b:] + bits[:-b], 2) & ((1 << 32) - 1)

l = len(unk_403400)

key = 0xE0C92EAB
flag = []
for i, b in enumerate(unk_403400[:l]):
    v = b ^ (key & 0xFF)
    flag.append(v)
    # key = (l + (v ^ ror4(key, 3)) - i) & ((1 << 32) - 1)
    key = (l + (b ^ ror4(key, 3)) - i) & ((1 << 32) - 1) # v should be b here

print(bytes(flag).decode())
```

這裡要注意 `unk_403400` 最後的 NULL 不能納入，因為他是 string terminator。
若照著 decompile 結果，key 更新是 `key = (l + (v ^ ror4(key, 3)) - i) & ((1 << 32) - 1)`，但若在寫腳本解密，要把 `v` 換成 `b`，因為實際上在程式解密當下，v 的值會是最後的輸出，即 `b`。


FLAG: `FLAG{jmp1ng_a1l_ar0und}`


## TrashCan
![image](https://hackmd.io/_uploads/BkI7UEaNT.png =40%x)

前面先 allocate 了兩個 0x10 大的 memory，其中 `off_140004468` 是 function table。
第一塊 memory 會將第二塊 memory 的值存到 `tree + 8`。

![image](https://hackmd.io/_uploads/S1K4UVpEp.png)

`sub_1400013B0` (`traverse_tree`)、`sub_1400015B0` (`build_tree`)

下面再 cin 收 input，並呼叫 function table 中的第二個 function 處理各 input 字元。
最後呼叫 function table 中的第一個 function 來 validate flag。

![image](https://hackmd.io/_uploads/HJXxS4TVT.png)

跟入 `sub_1400015B0` (`build_tree`)，可以發現若符合條件，則會 dereference 某個 pointer、呼叫 `sub_140001290` (`node_append`)、並把結果存回去。
若該 ptr 為空，則 alloc 一個新的 0x18 大的 memory，並初始化值。

![image](https://hackmd.io/_uploads/BkgCBUNT4a.png)


`sub_140001290` (`node_append`) 是一個遞迴函數，行為與 `sub_1400015B0` (`build_tree`) 中呼叫他的部分相同。

![image](https://hackmd.io/_uploads/r1hd8E6Np.png)

仔細觀察這段:

![image](https://hackmd.io/_uploads/Sk1XKE6NT.png)

其中 `a2` 是由 main 傳入的 character，`v5` 則在 main 中第二塊 alloc 的記憶體裡，通靈出來這是一個建 tree 的過程，`v5[1]` 為 node 的 data，`v5 + 1` 與 `v5 + 2` 則分別為 left，right child。

故推測 `sub_140001290` (`node_append`) 就是做 node append。
另外觀察 `v7`，可以看到其中除了 character 以外，還有 `v4`，且每次呼叫 `sub_1400015B0` (`build_tree`) 就會 +1，故推測此為 input 的 index。

Create struct:

![image](https://hackmd.io/_uploads/rk2L9EaV6.png)

重新標 type 與重命名:

`main`

![image](https://hackmd.io/_uploads/ryZsNNTNa.png =45%x)
![image](https://hackmd.io/_uploads/SJdXBNaVT.png)

`build_tree`

![image](https://hackmd.io/_uploads/B1DnBVpVT.png)

`node_append`

![image](https://hackmd.io/_uploads/BJqKUNpVa.png)

回去看 `traverse_tree`:

![image](https://hackmd.io/_uploads/H1lj2ETV6.png =50%x)

裡面呼叫 `sub_140001320` (`preorder_traverse`)，且也為遞迴函數。

![image](https://hackmd.io/_uploads/r127nEpV6.png)

先走訪 `v4`、再來是 left、再來是 right，推論為 preorder traversal。

`a3` 會被傳入 `sub_140001F60` (`vector_push_expand`)，裡面呼叫了 `sub_1400020F0`，會丟出 `vector too long` 的錯誤，故推測 `a3` 是個 vector。

參考此篇文章 https://www.msreverseengineering.com/blog/2021/9/21/automation-in-reverse-engineering-c-template-code ，建立 vector 與 vector_item struct:

![image](https://hackmd.io/_uploads/ByX_nE646.png)

`v6` 為 push 到 vector 的東西，為 64 bits 大，剛好是兩個 int，一個為 index 一個為 data，這裡因為 little endian 的關係，higher address 是較後面的 field。

retype & rename:

![image](https://hackmd.io/_uploads/BywPa4aVT.png)

推測 `a3` 是存遍歷路徑，`vector_push_expand` 是在 vector 滿 (`last == vector->end`) 的時候會做 expand 以存放更多資料。

`traverse_tree`

![image](https://hackmd.io/_uploads/ByFzCNa4a.png)

上面透過 SIMD 一次載入多筆資料 (retype `data` 與 `index` 為 `int[22]`)

![image](https://hackmd.io/_uploads/rkVLRNT46.png =40%x)

下面會一一對照路徑是否正確 (檢查 `data` 與 `index`)。

實際上 binary 裡的 flag 只是打亂順序，並沒有做其他處理，因此可以直接透過 index 回推。

`Trashcan/solve.py`
```python
# setup data and index...

node = []

for d, dep in zip(data, index):
    node.append((d, dep))

flag = ""
for n in sorted(node, key=lambda x: (x[1], data.index(x[0]))):
    flag += chr(n[0])
print(flag)
```

![image](https://hackmd.io/_uploads/HJN12pjET.png)

FLAG: `FLAG{s1mpl3_tr4S5_caN}`