# PTIT CTF 2025
## Summoner - Hard

Ở thử thách này, tác giả đã cho khá nhiều gợi. Mình cũng đã bám theo những gợi ý của tác giả nhưng kết quả vẫn không khả thi.
Chương trình có khá nhiều cơ chế antidebug, kiểm tra máy ảo, cũng như là kiểm tra các tiến trình để phát hiện các công cụ như là ida chẳng hạn, để xem chúng có đang được mở không.
Mình đã thử phân tích động và tìm cách bypass chương trình, nhưng kết quả vẫn là không thể.
Đầu tiên hãy đi vào phân tích chương trình

Ở địa chỉ `0x140005DF7`, IDA không thể phân tích được lệnh `call` nên việc decompile thật bại

Lệnh call ở đây là bình thường, nhưng ida lại báo phân tích thất bại. Vậy lý do ở đây là gì?
Xem ngược lại các hàm ở phía trên, ta thấy có rất nhiều hàm trước đó đã bị làm rối bằng cách chèn các vòng lặp vô dụng hoặc các bytes rác

Xem qua một vài hàm thì chúng đều có chung một dạng
```
push
push
....
pop
pop
```
hoặc
```
push
....
pop
```
Những đoạn lệnh vô dụng này khiến việc phân tích tĩnh trở nên khó khăn. Do đó mình đã thử chuyển sang phân tích động.
Như mình đã nói từ đầu, chương trình có khá nhiều cơ chế antidebug, kiểm tra máy ảo, cũng như là kiểm tra các tiến trình để phát hiện các công cụ như là ida. Mình đã tìm cách bypass nhưng vẫn gặp lỗi và không load vào game được. Do đó mình sẽ tiếp tục với việc phân tích tĩnh.
Trước đó mình có đề cập đến việc chương trình chứa các đoạn lệnh vô dụng làm rối chương trình. Nên trước tiên việc cần làm là patch lại toàn bộ bằng cách thay chúng thành các byte `0x90`. Quá trình này mất khiến mình mất nhiều thời gian vì có rất nhiều hàm như vậy.


Sau khi patch xong toàn bộ, ta đã có một chương trình hoàn chỉnh


Hàm `main` (đã được rename) là phần logic chính của chương trình
```c=
// local variable allocation has failed, the output may be wrong!
int __fastcall main(int argc, const char **argv, const char **envp)
{
HANDLE hObject; // [rsp+20h] [rbp-148h]
CHAR Name[252]; // [rsp+40h] [rbp-128h] BYREF
int v6; // [rsp+13Ch] [rbp-2Ch]
int v7; // [rsp+14Ch] [rbp-1Ch]
int v8; // [rsp+150h] [rbp-18h]
_QWORD v9[2]; // [rsp+158h] [rbp-10h] BYREF
v9[1] = *(_QWORD *)&argc;
v9[0] = 0xAEEF959E9985828DuLL;
v7 = v8;
while ( v7 < 100000 )
{
if ( !qword_7FF797E96FB8 )
Sleep(0x3E8u);
sub_7FF797D44FD0(Name, 0, 260);
sub_7FF797D44680((__int64)Name, (__int64)v9, (unsigned int)v7);
if ( sub_7FF797D6DD80(qword_7FF797E96FB8, 1203389044) )
{
if ( !OpenMutexA(0x1F0001u, 0, Name) )
v7 = v8;
if ( v8 < v7 )
v8 = v7;
if ( v8 == 99999 )
unk_7FF797E96F94 = 1;
if ( unk_7FF797E96F94 )
{
if ( unk_7FF797E96F7C )
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, 0, 0, 0);
}
CloseHandle(hObject);
}
Sleep(0x14u);
++v6;
}
return 0;
}
```
Ở đây ta chỉ cần để ý đến điều kiện `v8 == 99999`, tức là đã triệu hồi đủ 100000 (0->99999) thì cờ sẽ được bật `unk_7FF797E96F94 = 1`. Sau đó một threat sẽ được tạo với hàm bắt đầu là `StartAddress`
```c=
void __fastcall StartAddress(
__int64 lpThreadParameter,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5,
__int64 a6,
__int64 a7,
int a8,
__int64 a9,
__int64 a10,
__int64 a11,
__int64 a12,
__int64 a13,
__int64 a14,
__int64 a15,
__int64 a16,
__int64 a17,
__int64 a18,
__int64 a19,
__int64 a20,
__int64 a21,
__int64 a22,
__int64 a23,
__int64 a24,
__int64 a25,
__int64 a26,
__int64 a27,
__int64 a28,
__int64 a29,
__int64 a30,
__int64 a31,
__int64 a32,
__int64 a33,
__int64 a34,
__int64 a35,
__int64 a36,
__int64 a37,
__int64 a38,
__int64 a39,
__int64 a40,
__int64 a41,
__int64 a42,
__int64 a43,
__int64 a44,
__int64 a45,
__int64 a46,
__int64 a47,
__int64 a48,
__int64 a49,
__int64 a50,
__int64 a51,
__int64 a52,
__int64 a53,
__int64 a54,
__int64 a55,
__int64 a56,
__int64 a57,
__int64 a58,
__int64 a59,
__int64 a60,
__int64 a61,
__int64 a62,
__int64 a63)
{
__int64 v67; // [rsp+1C8Eh] [rbp-3C0h]
_BYTE v68[272]; // [rsp+1C96h] [rbp-3B8h] BYREF
_BYTE v69[260]; // [rsp+1DA6h] [rbp-2A8h] BYREF
unsigned int v70; // [rsp+1EAAh] [rbp-1A4h] BYREF
__int64 v71; // [rsp+1EAEh] [rbp-1A0h]
__int16 v72; // [rsp+1EB6h] [rbp-198h] BYREF
__int16 v73; // [rsp+1EB8h] [rbp-196h]
__int16 v74; // [rsp+1EBAh] [rbp-194h]
__int16 v75; // [rsp+1EBCh] [rbp-192h]
__int16 v76; // [rsp+1EBEh] [rbp-190h]
__int16 v77; // [rsp+1EC0h] [rbp-18Eh]
__int16 v78; // [rsp+1EC2h] [rbp-18Ch]
__int16 v79; // [rsp+1EC4h] [rbp-18Ah]
__int16 v80; // [rsp+1EC6h] [rbp-188h]
__int16 v81; // [rsp+1EC8h] [rbp-186h]
__int16 v82; // [rsp+1ECAh] [rbp-184h]
__int16 v83; // [rsp+1ECCh] [rbp-182h]
__int64 v84[2]; // [rsp+1FB6h] [rbp-98h] BYREF
__int64 v85; // [rsp+1FC6h] [rbp-88h]
__int64 v86; // [rsp+1FCEh] [rbp-80h]
__int64 v87; // [rsp+1FD6h] [rbp-78h]
__int64 v88; // [rsp+1FDEh] [rbp-70h]
__int64 v89; // [rsp+1FEEh] [rbp-60h]
void (__fastcall *v90)(__int64, _QWORD, _BYTE *, __int64); // [rsp+1FF6h] [rbp-58h]
__int64 (__fastcall *v91)(_BYTE *, __int64, _QWORD, _QWORD, int, int, _QWORD); // [rsp+1FFEh] [rbp-50h]
void (__fastcall *v92)(__int64); // [rsp+2006h] [rbp-48h]
void (__fastcall *v93)(__int64, _BYTE *); // [rsp+200Eh] [rbp-40h]
__int64 v94; // [rsp+2016h] [rbp-38h]
unsigned __int64 v95; // [rsp+201Fh] [rbp-2Fh] BYREF
__int16 v96; // [rsp+2027h] [rbp-27h]
char v97; // [rsp+2029h] [rbp-25h]
int i; // [rsp+202Ah] [rbp-24h]
unsigned __int64 v99; // [rsp+2030h] [rbp-1Eh] BYREF
int v100; // [rsp+2038h] [rbp-16h]
__int16 v101; // [rsp+203Ch] [rbp-12h]
__int64 v102; // [rsp+203Eh] [rbp-10h]
int v103; // [rsp+204Ah] [rbp-4h]
v102 = lpThreadParameter;
v99 = 0x95FE8D95F998FA81uLL;
v100 = 0xAF87FB9F;
v101 = 0xA3A5;
for ( i = 0; (unsigned __int64)i < 14; ++i )
*((_BYTE *)&v99 + i) ^= 0xCAu;
v95 = 0xE4FB95AEB9AB95ABuLL;
v96 = 0xA4BA;
v97 = 0xAD;
HIDWORD(v94) = 0;
while ( (unsigned __int64)SHIDWORD(v94) < 0xB )
{
*((_BYTE *)&v95 + SHIDWORD(v94)) ^= 0xCAu;
++HIDWORD(v94);
}
sub_7FF797D44FD0(v84, 0, 96);
sub_7FF797D44FD0(&v72, 0, 256);
v72 = 'k';
v73 = 'e';
v74 = 'r';
v75 = 'n';
v76 = 'e';
v77 = 'l';
v78 = '3';
v79 = '2';
v80 = '.';
v81 = 'd';
v82 = 'l';
v83 = 'l';
v84[0] = sub_7FF797D6E490((__int64)&v72);
sub_7FF797D44FD0(&v72, 0, 256);
v72 = 'u';
v73 = 's';
v74 = 'e';
v75 = 'r';
v76 = '3';
v77 = '2';
v78 = '.';
v79 = 'd';
v80 = 'l';
v81 = 'l';
v85 = sub_7FF797D6E170((__int64)&v72);
v86 = sub_7FF797D6E260(v84[0], 658262634);
v87 = sub_7FF797D6E260(v84[0], 634893392);
v91 = (__int64 (__fastcall *)(_BYTE *, __int64, _QWORD, _QWORD, int, int, _QWORD))sub_7FF797D6E260(v84[0], -644587988);
v88 = sub_7FF797D6E260(v84[0], 1747529885);
v92 = (void (__fastcall *)(__int64))sub_7FF797D6E260(v84[0], 780657750);
v93 = (void (__fastcall *)(__int64, _BYTE *))sub_7FF797D6E260(v84[0], -2044680275);
v89 = sub_7FF797D6E260(v85, -1577606512);
v90 = (void (__fastcall *)(__int64, _QWORD, _BYTE *, __int64))sub_7FF797D6E260(v85, 1651648520);
v71 = 0;
v70 = 0;
v71 = decrypt((__int64)&unk_7FF797D9D000, 20204u, &v99, (__int64)&v70);
sub_7FF797D44FD0(v69, 0, 260);
v93(260, v69);
sub_7FF797D44E70(v69, &v95);
sub_7FF797D44DB0(v68, v69);
v68[(_QWORD)(sub_7FF797D44E20(v68) - 1)] = 0;
v67 = v91(v68, 3221225472LL, 0, 0, 2, 128, 0);
if ( v67 == -1 )
{
v103 = 0;
}
else
{
sub_7FF797D41C50(v67, v71, v70, (__int64)v84);
v92(v67);
v90(20, 0, v68, 1);
sub_7FF797D41A20((__int64)v84);
}
}
```
Đầu tiên key sẽ được khởi tạo
```c=
v99 = 0x95FE8D95F998FA81uLL;
v100 = 0xAF87FB9F;
v101 = 0xA3A5;
for ( i = 0; (unsigned __int64)i < 14; ++i )
*((_BYTE *)&v99 + i) ^= 0xCAu;
```
-> key: `K0R3_G4_U1Meoi`
Tiếp đến tạo tên file ouput
```c=
v95 = 0xE4FB95AEB9AB95ABuLL;
v96 = 0xA4BA;
v97 = 0xAD;
HIDWORD(v94) = 0;
while ( (unsigned __int64)SHIDWORD(v94) < 0xB )
{
*((_BYTE *)&v95 + SHIDWORD(v94)) ^= 0xCAu;
++HIDWORD(v94);
}
```
-> output: `a_asd_1.png`
Cuối cùng là gọi đến `decrypt` để giải mã vùng `unk_7FF797D9D000` và ghi ảnh có chứa flag ra file

```c=
_BYTE *__fastcall decrypt(__int64 enc, unsigned __int64 enc_len, _QWORD *key, unsigned __int64 *out)
{
bool v5; // [rsp+2Fh] [rbp-39h]
_QWORD v6[2]; // [rsp+30h] [rbp-38h] BYREF
unsigned __int64 i; // [rsp+40h] [rbp-28h]
unsigned __int64 *v8; // [rsp+48h] [rbp-20h]
_QWORD *v9; // [rsp+50h] [rbp-18h]
unsigned __int64 v10; // [rsp+58h] [rbp-10h]
__int64 v11; // [rsp+60h] [rbp-8h]
v11 = enc;
v10 = enc_len;
v9 = key;
v8 = out;
v6[0] = *key;
v6[1] = key[1];
for ( i = 0; ; ++i )
{
v5 = 0;
if ( i < 0x10 )
v5 = *((_BYTE *)v6 + i) != 0;
if ( !v5 )
break;
}
++i;
while ( i < 16 )
*((_BYTE *)v6 + i++) = 0;
return sub_7FF797D46CA0(v11, v10, (__int64)v6, v8);
}
```
```c=
_BYTE *__fastcall sub_7FF797D46CA0(__int64 enc, unsigned __int64 enc_len, __int64 key, unsigned __int64 *out)
{
unsigned int *v4; // rax
size_t v6; // [rsp+30h] [rbp-50h] BYREF
__int64 v7; // [rsp+38h] [rbp-48h] BYREF
void *Block; // [rsp+40h] [rbp-40h]
void *v9; // [rsp+48h] [rbp-38h]
_BYTE *v10; // [rsp+50h] [rbp-30h]
unsigned __int64 *v11; // [rsp+58h] [rbp-28h]
__int64 v12; // [rsp+60h] [rbp-20h]
unsigned __int64 v13; // [rsp+68h] [rbp-18h]
__int64 v14; // [rsp+70h] [rbp-10h]
v14 = enc;
v13 = enc_len;
v12 = key;
v11 = out;
if ( !enc_len )
return 0;
v9 = sub_7FF797D46E30(v14, v13, 0, (size_t *)&v7);
if ( !v9 )
return 0;
Block = sub_7FF797D46E30(v12, 16u, 0, &v6);
if ( Block )
{
v4 = sub_7FF797D472D0((unsigned int *)v9, v7, (__int64)Block);
v10 = sub_7FF797D46F90((__int64)v4, v7, 1, v11);
free(v9);
free(Block);
return v10;
}
else
{
free(v9);
return 0;
}
}
```
```c=
_DWORD *__fastcall sub_7FF797D46E30(__int64 enc, unsigned __int64 enc_len, int a3, size_t *a4)
{
size_t v5; // [rsp+28h] [rbp-40h]
_DWORD *v6; // [rsp+38h] [rbp-30h]
if ( (enc_len & 3) != 0 )
v5 = (enc_len >> 2) + 1;
else
v5 = enc_len >> 2;
if ( !a3 )
{
v6 = calloc(v5, 4u);
if ( !v6 )
return 0;
*a4 = v5;
LABEL_11:
sub_7FF797D44EB0((__int64)v6, enc, enc_len);
return v6;
}
v6 = calloc(v5 + 1, 4u);
if ( v6 )
{
v6[v5] = enc_len;
*a4 = v5 + 1;
goto LABEL_11;
}
return 0;
}
```
```c=
__int64 __fastcall sub_7FF797D44EB0(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 j; // [rsp+20h] [rbp-20h]
unsigned __int64 i; // [rsp+20h] [rbp-20h]
if ( (a1 & 3) != 0 || (a2 & 3) != 0 || (a3 & 3) != 0 )
{
for ( i = 0; i < a3; ++i )
*(_BYTE *)(a1 + i) = *(_BYTE *)(a2 + i);
}
else
{
for ( j = 0; j < a3 >> 2; ++j )
*(_DWORD *)(a1 + 4 * j) = *(_DWORD *)(a2 + 4 * j);
}
return 0;
}
```
```c=
_DWORD *__fastcall sub_7FF797D46E30(__int64 enc, unsigned __int64 enc_len, int a3, size_t *a4)
{
size_t v5; // [rsp+28h] [rbp-40h]
_DWORD *v6; // [rsp+38h] [rbp-30h]
if ( (enc_len & 3) != 0 )
v5 = (enc_len >> 2) + 1;
else
v5 = enc_len >> 2;
if ( !a3 )
{
v6 = calloc(v5, 4u);
if ( !v6 )
return 0;
*a4 = v5;
LABEL_11:
sub_7FF797D44EB0((__int64)v6, enc, enc_len);
return v6;
}
v6 = calloc(v5 + 1, 4u);
if ( v6 )
{
v6[v5] = enc_len;
*a4 = v5 + 1;
goto LABEL_11;
}
return 0;
}
```
```c=
unsigned int *__fastcall sub_7FF797D472D0(unsigned int *enc, unsigned int enc_len_4, __int64 key_16)
{
unsigned int v3; // eax
unsigned int v4; // eax
unsigned int v6; // [rsp+8h] [rbp-3Ch]
unsigned int v7; // [rsp+Ch] [rbp-38h]
int i; // [rsp+14h] [rbp-30h]
unsigned int v9; // [rsp+18h] [rbp-2Ch]
unsigned int v10; // [rsp+1Ch] [rbp-28h]
v9 = *enc;
v7 = 0x9E3779B9 * (0x34 / enc_len_4 + 6);
if ( enc_len_4 == 1 )
return enc;
while ( v7 )
{
v6 = (v7 >> 2) & 3;
for ( i = enc_len_4 - 1; i; --i )
{
v10 = enc[i - 1];
v3 = enc[i]
- (((v10 ^ *(_DWORD *)(key_16 + 4LL * (v6 ^ i & 3))) + (v9 ^ v7))
^ (((16 * v10) ^ (v9 >> 3)) + ((4 * v9) ^ (v10 >> 5))));
enc[i] = v3;
v9 = v3;
}
v4 = *enc
- (((enc[enc_len_4 - 1] ^ *(_DWORD *)(key_16 + 4LL * v6)) + (v9 ^ v7))
^ (((16 * enc[enc_len_4 - 1]) ^ (v9 >> 3)) + ((4 * v9) ^ (enc[enc_len_4 - 1] >> 5))));
*enc = v4;
v9 = v4;
v7 += 0x61C88647;
}
return enc;
}
```
Tóm tắt logic của `decrypt`
1. pad thêm các byte `0x0` vào password cho đủ 16bytes
2. Biến đổi pass và cipher thành các khối 4bytes
3. Cuối cùng là giải mã xxtea theo từng khối 4bytes
**Script giải**
```python=
MASK32 = 0xFFFFFFFF
DELTA = 0x9E3779B9
def u32(x: int) -> int:
return x & MASK32
def xxtea_decrypt(enc: list[int], key: list[int]) -> list[int]:
n = len(enc)
if n <= 1:
return enc
sum_ = u32(DELTA * (52 // n + 6))
y = u32(enc[0])
while sum_ != 0:
e = (sum_ >> 2) & 3
for i in range(n - 1, 0, -1):
z = u32(enc[i - 1])
mx = (((z ^ key[(i & 3) ^ e]) + (y ^ sum_)) ^
(((u32(z << 4)) ^ (y >> 3)) + ((u32(y << 2)) ^ (z >> 5))))
enc[i] = y = u32(enc[i] - mx)
z = u32(enc[n - 1])
mx0 = (((z ^ key[e]) + (y ^ sum_)) ^
(((u32(z << 4)) ^ (y >> 3)) + ((u32(y << 2)) ^ (z >> 5))))
enc[0] = y = u32(enc[0] - mx0)
sum_ = u32(sum_ - DELTA)
return enc
import struct
key = b'K0R3_G4_U1Meoi\x00\x00'
key = list(struct.unpack("<4I", key))
enc = open('flag.enc', 'rb').read()
enc = list(struct.unpack('<5051I', enc))
out = xxtea_decrypt(enc, key)
out = [int.to_bytes(i, 4, 'little') for i in out]
out = b''.join(map(bytes, out))
# print(out[:10])
with open('flag.png', 'wb') as f:
f.write(out)
```

Tuy nhiên, nếu dùng quen ghidra (mình ít khi dùng nên hơi khó xem) thì sẽ dễ xem hơn nhiều so với ida vì các hàm đã được hiển thị rõ ràng, không cần phải patch thủ công lại. Nhưng vẫn có một vài chỗ cần disassemble lại để có thể xem được hàm.
```c=
/* WARNING: Instruction at (ram,0x000140006e01) overlaps instruction at (ram,0x000140006dff)
*/
/* WARNING: Removing unreachable block (ram,0x000140006d9b) */
/* WARNING: Removing unreachable block (ram,0x000140006dfc) */
/* WARNING: Restarted to delay deadcode elimination for space: stack */
void * FUN_140006ca0(ulonglong param_1,ulonglong param_2,ulonglong param_3,ulonglong *param_4)
{
ulonglong uVar1;
char unaff_BL;
size_t local_50;
ulonglong local_48;
void *local_40;
undefined4 *local_38;
void *pvStack_30;
ulonglong *local_28;
ulonglong local_20;
ulonglong local_18;
ulonglong local_10;
void *local_8;
if (param_2 == 0) {
local_8 = (void *)0x0;
}
else {
local_28 = param_4;
local_20 = param_3;
local_18 = param_2;
local_10 = param_1;
local_38 = (undefined4 *)FUN_140006e30(param_1,param_2,0,&local_48);
if (local_38 == (undefined4 *)0x0) {
local_8 = (void *)0x0;
}
else {
*(char *)((longlong)local_38 + 0x58030472) =
*(char *)((longlong)local_38 + 0x58030472) - unaff_BL;
local_40 = FUN_140006e30(local_20,0x10,0,&local_50);
if (local_40 == (void *)0x0) {
free(local_38);
local_8 = (void *)0x0;
}
else {
uVar1 = FUN_1400072d0(local_38,local_48,local_40);
pvStack_30 = FUN_140006f90(uVar1,local_48,1,local_28);
free(local_38);
free(local_40);
local_8 = pvStack_30;
}
}
}
return local_8;
}
```
```c=
/* WARNING: Control flow encountered bad instruction data */
/* WARNING: Instruction at (ram,0x000140007449) overlaps instruction at (ram,0x000140007447)
*/
/* WARNING: Removing unreachable block (ram,0x000140007444) */
/* WARNING: Removing unreachable block (ram,0x00014000736d) */
/* WARNING: Restarted to delay deadcode elimination for space: stack */
undefined4 * FUN_1400072d0(undefined4 *param_1,ulonglong param_2,longlong param_3)
{
uint uVar1;
uint uVar2;
uint uVar3;
uint local_38;
uint local_30;
uint local_2c;
uVar2 = (int)param_2 - 1;
local_2c = *param_1;
local_38 = ((int)(0x34 / (param_2 & 0xffffffff)) + 6) * -0x61c88647;
if (uVar2 != 0) {
for (; local_38 != 0; local_38 = local_38 + 0x61c88647) {
uVar3 = local_38 >> 2 & 3;
for (local_30 = uVar2; local_30 != 0; local_30 = local_30 - 1) {
uVar1 = param_1[local_30 - 1];
local_2c = param_1[local_30] -
((uVar1 >> 5 ^ local_2c << 2) + (local_2c >> 3 ^ uVar1 << 4) ^
(local_38 ^ local_2c) +
(*(uint *)(param_3 + (ulonglong)(local_30 & 3 ^ uVar3) * 4) ^ uVar1));
param_1[local_30] = local_2c;
}
uVar1 = param_1[uVar2];
local_2c = *param_1 -
((uVar1 >> 5 ^ local_2c << 2) + (local_2c >> 3 ^ uVar1 << 4) ^
(local_38 ^ local_2c) + (*(uint *)(param_3 + (ulonglong)uVar3 * 4) ^ uVar1));
*param_1 = local_2c;
}
}
return param_1;
}
```
## Update
Sau khi tìm được `StartAddress` và biết ở đây nó sẽ giải mã flag. Nếu không muốn viết lại script ta có thể sửa lại oep của file load thẳng vào đây để in ra flag

1. Mở Summon.exe
2. Sử dụng scylla và tìm tiến trình của Summon.exe

3. Thực hiện thay đổi OEP

4. Thực hiện dump ra file mới




5. Thực hiện chạy file `Summon_dump.exe`
flag sẽ tự động set thành background và file được lưu ở thư mục `Temp`

