zer0pts22 CTF writeup: A part Flag-Checker
Cuối tuần trước, team mình có chơi giải zer0pts CTF. Cả giải mình và 1 bạn cùng team chỉ làm mỗi bài này, hơi buồn nhưng sau bài này mình đã học thêm được rất nhiều, cảm ơn tác giả vì chall gồm rất nhiều kiến thức

Miêu tả challenge

Phân tích
Extract autoit script
Chall đưa cho mình một file task.exe có size ~13Mb. Sau khi decompile, chúng mình có nhìn thấy string autoit
// truncated...
if ( IsDebuggerPresent() )
{
MessageBoxA(0i64, "This is a third-party compiled AutoIt script.", Caption, 0x10u);
return sub_140008B2C(lpFile);
}
Đây là cơ chế anti debug của nhưng file được code bằng autoit và sau đó compile về pe file để run trên các hệ điều hành windows.
Lúc đầu, chúng mình extract autoit script hơi cồng kềnh :<, sau giải mình mới biết có một cách khác nhanh hơn, vậy nên mình sẽ trình bày cả 2 cách trong bài writeup này luôn.
Xài Exe2Aut.exe
Mọi người có thể tải file ở link
Nhưng tool đó chỉ support những file exe 32bit, còn file challenge của mình 64bit. Vậy nên chúng mình đã tìm cách đưa file 64bit về 32bit theo như cách làm ở blog Decompiling compiled AutoIT scripts (64-bit)
Cơ bản là mình xài script của họ để đưa file 64bit về 32bit, sau đó dùng tool Exe2Aut
Tool UnAutoIt
Sau giải mình tìm được cách hay hơn để extract script autoit là dùng ngay tool của tác giả
UnAutoIt giúp extract được cả file 64bit và 32bit, xịnnnn :>
Bên lề
Ngoài script autoit mà chúng mình extract được thì trong file task.exe còn có 2 resources nữa khá nghi ngờ.
-
1 là Icon số 12

-
2 là rescource có tên
SCR1PT(cáiSCRIPTcòn lại là file autoit mà chúng mình extract đc bên trên)
:thinking: không biết để làm gì nhỉ?
Rev au_1 autoit script
Script autoit vừa extract được mình tạm gọi là script au_1. Khi đọc qua file au_1, mình thấy có một đoạn chương trình chạy một file anime.exe trong thư mục temp
Func getanimelist()
Local $path = @TempDir & "\\anime.exe"
FileDelete($path)
FileInstall("anime.exe", $path)
$g_pid = Run($path, '', @SW_HIDE)
EndFunc ; -> getanimelist
Múc ra để rev thôi :))
Bên cạnh đó, file au_1 còn tạo pipe để trao đổi data với file anime.exe
Func getufoname()
Return "\\\\.\\pipe\\anime"
EndFunc ; -> getufoname
Chương trình au_1 bắt đầu với hàm hahahahahahahahahahahahaahahahahh(), nhận input từ inputbox sau đó gọi đến hàm runtime_gogogogo().
Func hahahahahahahahahahahahaahahahahh()
Local $ret = $funcptr($MB_YESNO, $title, "Do you want to proceed?")
If $ret = $IDNO Then
$runtime_lockosthread("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
Exit
EndIf
Local $iron_man = InputBox($title, "Please enter the flag", "hahaha")
If @error = 0x1 Then
$runtime_lockosthread("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
Exit
EndIf
If runtime_gogogogo($iron_man) = 0x0 Then
$funcptr(0x40, $title, "Well done. You got the flag!")
Else
$funcptr($MB_ICONQUESTION, $title, "Oops. try again!")
EndIf
EndFunc ; -> hahahahahahahahahahahahaahahahahh
Func runtime_gogogogo($text)
Local $magic = StringToASCIIArray($text)
modarr($magic)
getanimelist()
Local $this_is_a_variable = playsomeanime($magic)
Return $this_is_a_variable
EndFunc ; -> runtime_gogogogo
Trong hàm modarr mình thấy chương trình có kiểm tra độ dài của input, nếu =0x6e thì sẽ return luôn còn chưa bằng sẽ padding thêm một đoạn random :thinking: (có khi nào 0x6e là length của flag không ta).
Func modarr(ByRef $arr)
If UBound($arr) = 0x6e Then
Return
Else
_ArrayAdd($arr, Random(0x0, 0xff))
modarr($arr)
EndIf
EndFunc ; -> modarr
Sau đó chương trình đi đến hàm playsomeanime(). Chương trình reverse sau đó pop ra từng ký tự của flag => lấy các ký tự từ cuối lên đầu và đưa vào getanimedescription. Nếu tất cả giá trị trả về =0 thì có nghĩ là flag mà chúng ta truyền vào là đúng.
Func playsomeanime($anime_title)
If UBound($anime_title) = 0x0 Then
Return 0x0
EndIf
_ArrayReverse($anime_title)
Local $anime_desc = getanimedescription(_ArrayPop($anime_title))
_ArrayReverse($anime_title)
Return $anime_desc + playsomeanime($anime_title)
EndFunc ; -> playsomeanime
Trong hàm getanimedescription, chương trình tạo từng struct và truyền giá trị cho từng trường rồi đưa vào hàm findbestanimefrommyanimelist. Ví dụ như một phần code của hàm getanimedescription ở dưới, chương trình tạo một struct có 2 trường là int;int, giá trị truyền vào cho trường số 1 là 1, giá trị truyền vào cho trường số 2 là $anime_title (cũng chính là 1 byte của flag). Sau đó, hàm findbestanimefrommyanimelist nhận struct vừa tạo và truyền qua pipe đến cho chương trình anime để xử lý và nhận kết quả trả về.
Func getanimedescription($anime_title)
Local $what_is_this_i_dont_know = "int;int"
Local $unidentified_flying_object = $runtime_systemstack($what_is_this_i_dont_know)
$runtime_morestack($unidentified_flying_object, 0x1, 0x1)
$runtime_morestack($unidentified_flying_object, 0x2, $anime_title)
Local $resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
$runtime_morestack($unidentified_flying_object, 0x1, 0x3)
// truncated...
EndFunc ; -> getanimedescription
Func findbestanimefrommyanimelist($haha_null_ptr, $this_is_null)
Local $idatalostintonulldevice, $inullptr
Local $ufo = $runtime_systemstack("align 1;byte[4096]")
$null_far_ptr = $runtime_getg($ufo)
Local $this_is_dev_null_in_windows
Do
$this_is_dev_null_in_windows = _WinAPI_CreateFile(getufoname(), 0x2, 0x6)
Until $this_is_dev_null_in_windows > 0x0
_NamedPipes_SetNamedPipeHandleState($this_is_dev_null_in_windows, 0x1, 0x0, 0x0, 0x0)
_WinAPI_WriteFile($this_is_dev_null_in_windows, $haha_null_ptr, $this_is_null, $inullptr, 0x0)
_WinAPI_ReadFile($this_is_dev_null_in_windows, $null_far_ptr, 0x1000, $idatalostintonulldevice, 0x0)
$runtime_free($this_is_dev_null_in_windows)
Return $ufo
EndFunc ; -> findbestanimefrommyanimelist
Func getufoname()
Return "\\\\.\\pipe\\anime"
EndFunc ; -> getufoname
Đó là tất cả những việc cơ bản mà file au_1 thực hiện, bây giờ chúng ta cần tìm hiểu chương trình anime sẽ xử lý như thế nào những data được truyền từ au_1
Rev anime.exe
File anime.exe gần như bị strip hoàn toàn, vậy nên việc rev cũng hơi khó khăn. Và để hiểu hơn về chương trình xử lý như thế nào, chúng mình sẽ tìm cách debug. Vì chương trình task.exe là autoit nên nó sẽ mang thêm cơ chế antidebug của AutoIt, và nếu để chương trình task.exe chạy sau đó attach vào thì mình cũng khó để debug được file anime.exe (vì chương trình tạo pipe để trao đổi, nếu pipe với cùng tên đã được tạo rồi thì khi chúng ta chạy file anime khác sẽ khiến cho hàm pipe trả về error, với trong quá trình chạy, file AutoIt có lấy resource của chính nó, nếu chúng ta build riêng script AutoIt au_1 thì khả năng chương trình sẽ không đi theo flow chính). Vậy nên, để debug đc chọn vẹn file anime, chúng mình đã làm như sau:
- Chạy file
task.exeđầu tiên, trong khi chương trình đợiyestừ người dùng, chúng ta sẽ dùngx64dbgđể attach vào chương trìnhtask=> bypass antidebug của AutoIt - Khi đã attach được vào
task, đặt breakpoint ở đầu hàmCreateProcessorCreateProcessW. Sau khi chương trình chạy tiếp, đến đoạnCreateProcesschương trìnhanime, chúng taf8để bắt đầu chạyanimenhưngtasksẽ chưa gửi bất kỳ data gì quapipechoanime. Ngay sau đó, chúng mình mở IDA lên và attach vàoanime=> debug được fileanimevà chương trình vẫn đi theo flow chuẩn.
Hàm sử lý chính
Quay lại một chút với script au_1, mỗi một struct mà nó tạo sẽ thì trường số 1 sẽ chính là id, tương ứng với mỗi id mà dữ liệu gửi lên, chương trình anime sẽ đưa vào các hàm xử lý khác nhau như phần code bên dưới:
void main_process()
{
// truncated...
id_0 = 0;
handle_func(v10, &id_0, fn_DEAD);
v9 = v11;
id_1 = 1;
handle_func(v11, &id_1, fn_CODE);
v9 = v12;
id_2 = 2;
handle_func(v12, &id_2, FN_12345678);
v9 = v13;
id_3 = 3;
handle_func(v13, &id_3, fn_BADFOOD);
v9 = v14;
id_4 = 4;
handle_func(v14, &id_4, fn_CAFEBABE);
v9 = v15;
id_5 = 5;
handle_func(v15, &id_5, fn_RET2);
v9 = v16;
id_6 = 6;
handle_func(v16, &id_6, fn_unknown);
v9 = v17;
id_7 = 7;
handle_func(v17, &id_7, fn_PNG_resource);
// truncated...
}
Trong file anime có sử dụng một technique gọi các hàm qua hash, mình có sử dụng một plugin tên là hashdb để lookup giúp quá trình reverse static đơn giản hơn, nhưng vẫn trầm cảm vler :<
Đi theo flow của data
ID=1
Bắt đầu, struct gửi từ script au_1 đến file anime
Local $what_is_this_i_dont_know = "int;int"
Local $unidentified_flying_object = $runtime_systemstack($what_is_this_i_dont_know)
$runtime_morestack($unidentified_flying_object, 0x1, 0x1)
$runtime_morestack($unidentified_flying_object, 0x2, $anime_title)
Local $resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
Miêu tả
struct ID_01 {
int id=1;
int byte_check=int(byte_flag);
}
Đây là đoạn code xử lý với id=1 trong chương trình anime
__int64 __fastcall fn_CODE(__int64 a1)
{
sub_140004A60(&unk_14007DF40, a1);
*(a1 - 4) = 0xC0DE;
return 4i64;
}
Có thể ở hàm này, chương trình lưu inp vào một biến global (cái này mình không chắc chắn lắm).
ID=3
Tiếp theo truyền một struct số 2 là:
$runtime_morestack($unidentified_flying_object, 0x1, 0x3)
Local $gobinary = -1042320
$runtime_morestack($unidentified_flying_object, 0x2, $gobinary)
$resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
struct ID_03 {
int id=3;
int sign_maybe=0xfff01870;
}
Map với data trong resource như hình dưới, mình thấy giá trị 0xfff01870 giống như signature nơi bắt đầu data, và giá trị bôi vàng bên cạnh 0x0026ba10 giống như là size của data.

Hàm sử lý id=3 là hàm fn_BADFOOD
void main_process()
{
// truncated...
id_3 = 3;
handle_func(v13, &id_3, fn_BADFOOD);
// truncated...
}
Trong hàm fn_BADFOOD có gọi đến hàm fn_focus
__int64 __fastcall fn_BADFOOD(int *a1)
{
// truncated...
fn_focus(v5, sign_FFF01870);
*(v7 - 1) = 0xBADF00D;
// truncated...
return 28i64;
}
Trong hàm fn_focus, như đoạn code bên dưới:
__int64 __fastcall fn_focus(__int64 a1, int sign_FFF01870_)
{
// truncated...
OpenProcess = calc_hash_lookup_api(-1084555921, -270362688);
v34 = (OpenProcess)(1040i64, 0i64, qword_14007DEF0);
if ( v34 )
{
memset(Dst, 0, 0x30ui64);
NtQueryInformationProcess = calc_hash_lookup_api(-1908709007, -1324361671);
NtQueryInformationProcess(v34, 0i64, Dst, 48i64, v35);
idx = 0i64;
ReadProcessMemory = calc_hash_lookup_api(-1084555921, 1469914089);
ReadProcessMemory(v34, *&Dst[8] + 16i64, &idx, 8i64, 0i64);
v29 = j_HeapAlloc(0x1000, v5, v6);
buf = 0i64;
v27 = 0i64;
v26 = 0;
while ( 1 )
{
v13 = 0;
if ( !v27 )
{
ReadProcessMemory_1 = calc_hash_lookup_api(0xBF5AFD6F, 0x579D1BE9);
v13 = ReadProcessMemory_1(v34, buf + idx, v29, 4096i64, v35) != 0;
}
if ( !v13 )
break;
badfood = 0xBADF00DCAFEDEADi64; // <-- start signature
for ( i = 0i64; i < 0xFF8; ++i )
{
cnt = 0i64;
for ( j = 0i64; j < 8; ++j )
cnt += *(&badfood + j) == v29[j + i];
if ( cnt == 8 )
{
v27 = i + buf + idx;
break;
}
}
buf += 4096i64;
}
j_Free(v29);
v21 = 0i64;
v20 = 0i64;
if ( v27 )
{
v19 = 0i64;
ReadProcessMemory_2 = calc_hash_lookup_api(-1084555921, 1469914089);
ReadProcessMemory_2(v34, v27 + 8, &v19, 8i64, v35);
v27 += 16i64;
for ( k = 0i64; k < v19; ++k )
{
ReadProcessMemory_3 = calc_hash_lookup_api(-1084555921, 1469914089);
ReadProcessMemory_3(v34, v27, &v17, 16i64, v35);
v27 += 16i64;
if ( sign_FFF01870 == v17 ) // <-- check end signature here
{
v20 = v18;
v21 = v27;
break;
}
if ( (v18 & 7) != 0 )
v12 = 8 - (v18 & 7);
else
v12 = 0;
v27 += v12;
v27 += v18;
}
}
CloseHandle = calc_hash_lookup_api(-1084555921, 268277755);
CloseHandle(v34);
nothing(a1, &v21, &v20);
}
// truncated...
}
Đoạn code trên đọc data từ offset đầu tiên resource SCR1PT (bắt đầu là 0xBADF00DCAFEDEAD) của file task.exe, mỗi lần đọc 4096 bytes, đọc đến khi 16 bytes cuối cùng có 4 bytes đầu là 0xFFF01870.
Sau đó, script au_1 sẽ nhận 8 bytes đầu là signature, 4 bytes tiếp theo là sizeof_data
Local $signature_data = qwertyuiopasdfghjkl($resp, 0x8)
Local $sizeof_data = qwertyuiopasdfghjkl($resp, 0x10, 0x4)
Local $pid = qwertyuiopasdfghjkl($resp, 0x14, 0x4)
Struct data tiếp theo chương trình cũng làm như vậy:
$runtime_morestack($unidentified_flying_object, 0x1, 0x3)
Local $eihowgtw = -1042320
$runtime_morestack($unidentified_flying_object, 0x2, $eihowgtw)
$resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
Local $wlegowtuqepo = qwertyuiopasdfghjkl($resp, 0x8)
Local $sizeof_data = qwertyuiopasdfghjkl($resp, 0x10, 0x4)
Local $pid = qwertyuiopasdfghjkl($resp, 0x14, 0x4)
ID=4
Tiếp đến, chương trình có gửi một data với id=4
Local $t_pid_left = $runtime_panic($tprocess, "ProcessID")
Local $t_tid_left = $runtime_panic($tprocess, "ThreadID")
// truncated...
Local $signature = qwertyuiopasdfghjkl($resp, 0x8)
Local $ptr_sizeof_data = qwertyuiopasdfghjkl($resp, 0x10, 0x4)
// truncated...
$what_is_this_i_dont_know = "int;int;int;align 8;ptr;int;int;int;int"
$unidentified_flying_object = $runtime_systemstack($what_is_this_i_dont_know)
$runtime_morestack($unidentified_flying_object, 0x1, 0x4)
$runtime_morestack($unidentified_flying_object, 0x2, $t_pid_left)
$runtime_morestack($unidentified_flying_object, 0x3, $t_tid_left)
$runtime_morestack($unidentified_flying_object, 0x4, $signature)
$runtime_morestack($unidentified_flying_object, 0x5, $ptr_sizeof_data)
$runtime_morestack($unidentified_flying_object, 0x6, 0x2 * 0x0 + 0x0)
$runtime_morestack($unidentified_flying_object, 0x7, _WinAPI_GetCurrentProcessID())
$runtime_morestack($unidentified_flying_object, 0x8, _WinAPI_GetCurrentThreadId())
Local $resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
Struct mà au_1 tạo ra sẽ như sau:
struct ID_04 {
int id=4;
int pid=xx; // processID that will be injected gofile
int tid=xx;
long signature=0xFFF01870;
int sizeof_data=0x0026ba10;
int unk_1=0;
int hCurrentProcessID=_WinAPI_GetCurrentProcessID();
int hCurrentThreadId=_WinAPI_GetCurrentThreadId();
}
Sau đó chuyển qua pipe đến cho hàm trong anime xử lý:
void main_process()
{
// truncated...
id_4 = 4;
handle_func(v14, &id_4, fn_CAFEBABE);
// truncated...
}
Hàm xử lý là fn_CAFEBABE [=)) mấy tên tạo bằng hex hay ghê]
__int64 __fastcall fn_CAFEBABE(int *a1)
{
// truncated...
fn_magic(*v6, qword_14007DEF0, &v2, v4);
*(v7 - 1) = 0xCAFEBABE;
// truncated...
return 8i64;
}
Đi tiếp đến hàm fn_magic
_QWORD *__fastcall fn_magic(__int64 a1, __int64 a2, __int128 *a3, int a4)
{
// truncated...
OpenProc = calc_hash_lookup_api(-1084555921, 0xEFE297C0);
result = (OpenProc)(0x1FFFFFi64, 0i64, LODWORD(v30[0]));
v27 = result;
if ( result )
{
OpenProcess = calc_hash_lookup_api(-1084555921, 0xEFE297C0);
v26 = (OpenProcess)(0x1FFFFFi64, 0i64, v29);
if ( v26 )
{
Openthred = calc_hash_lookup_api(-1084555921, 0x58C91E6F);
v25 = (Openthred)(0x1FFFFFi64, 0i64, HIDWORD(v30[0]));
if ( v25 )
{
v23 = *a3;
decrypt_data(v24, v26, &v23);
v22 = inject_gofile_to_process(v27, v25, v26, v24);
sub_140003DF0(v21);
sub_140004610(v21, v24);
sub_140003E50(v21);
v19 = v30[0];
v20 = sub_140002560(v30[0]);
if ( v20 != -1 )
{
v18 = *sub_140004680(&unk_14007DF40);
v18 >>= v20 - 1;
v18 &= 1u;
v17 = (2 * (v28 & 0x7E)) | (2 * v18) | v28 & 1;
v17 |= 8 * (v20 >= 9);
v12 = operator new(0x20ui64);
v15 = v30[0];
sub_1400046B0(v12, v30[0]);
v16 = v12;
v12[6] = v17 & 1;
*(v16 + 24) |= (v17 >> 1) & 2;
sub_1400046E0(&unk_14007DEF8, v14, v30, &v16);
NtWriteVirtualMemory = calc_hash_lookup_api(-1908709007, -988771134);
(NtWriteVirtualMemory)(v27, v22 + 2, &v17, 1i64, 0i64);
// truncated...
return result;
}
Chương trình strip nhiều, quá là mệt mỏi 
Hàm này chúng mình đọc loanh quanh 1 tí xong thì debug, nhận thấy rằng, chắc là hàm quan trọng chỉ là hàm decrypt_data kia thôi, vì data truyền vào chính là data được đọc ra từ resource trên. Vậy nên mình sẽ đi vào hàm này, nhưng đáng buồn thay, hàm này có stack fame quá lớn, vậy nên không thể f5 được nữa
. Chúng ta phải quay lại với giá trị cốt lõi của rev thôi, đó là đọc asm =)))
.text:0000000140002F60 decrypt_data proc near ; CODE XREF: fn_magic+F7↓p
;; truncated...
;; move "abO4oHxrfR03YwaX4KuEFUoV"
.text:0000000140002F98 movups xmm0, xmmword ptr cs:aAbo4ohxrfr03yw+9 ; "R03YwaX4KuEFUoV"
.text:0000000140002F9F movups xmmword ptr [rbp+0C0h+Str+9], xmm0
.text:0000000140002FA6 movaps xmm0, xmmword ptr cs:aAbo4ohxrfr03yw ; "abO4oHxrfR03YwaX4KuEFUoV"
.text:0000000140002FAD movaps xmmword ptr [rbp+0C0h+Str], xmm0
;; Create AcquireContextW
.text:0000000140002FC9 mov dword ptr [rcx+20h], 0F0000000h
.text:0000000140002FD0 lea r8, aMicrosoftEnhan ; "Microsoft Enhanced RSA and AES Cryptogr"...
.text:0000000140002FD7 xor ecx, ecx
.text:0000000140002FD9 mov [rbp+0C0h+var_A4], ecx
.text:0000000140002FDC mov edx, ecx
.text:0000000140002FDE mov [rbp+0C0h+var_C0], rdx
.text:0000000140002FE2 lea rcx, [rbp+0C0h+var_48]
.text:0000000140002FE6 mov r9d, 18h
.text:0000000140002FEC call rax ; CryptAcquireContextW
;; CryptCreateHash with Alg=0x800C => SHA256
.text:0000000140002FFB mov r8, [rbp+0C0h+var_C0]
.text:0000000140002FFF mov r9d, [rbp+0C0h+var_A4]
.text:0000000140003003 mov rcx, [rbp+0C0h+var_48]
.text:0000000140003007 mov rdx, rsp
.text:000000014000300A lea r10, [rbp+0C0h+var_50]
.text:000000014000300E mov [rdx+20h], r10
.text:0000000140003012 mov edx, 800Ch
.text:0000000140003017 call rax ; CryptCreateHash
;; CryptHashData "abO4oHxrfR03YwaX4KuEFUoV" --> SHA256
.text:0000000140003026 mov [rbp+0C0h+CryptHashData], rax
.text:000000014000302A lea rcx, [rbp+0C0h+Str] ; Str
.text:0000000140003031 mov [rbp+0C0h+var_B8], rcx
.text:0000000140003035 call strlen
.text:000000014000303A mov rdx, [rbp+0C0h+var_B8]
.text:000000014000303E mov r9d, [rbp+0C0h+var_A4]
.text:0000000140003042 mov rcx, rax
.text:0000000140003045 mov rax, [rbp+0C0h+CryptHashData]
.text:0000000140003049 mov r8d, ecx
.text:000000014000304C mov rcx, [rbp+0C0h+var_50]
.text:0000000140003050 call rax ; CryptHashData
;; CryptDerivedKey với Alg=0x660E --> AES128
.text:000000014000305F mov r9d, [rbp+0C0h+var_A4]
.text:0000000140003063 mov r8, [rbp+0C0h+var_50]
.text:0000000140003067 mov rcx, [rbp+0C0h+var_48]
.text:000000014000306B mov rdx, rsp
.text:000000014000306E lea r10, [rbp+0C0h+var_58]
.text:0000000140003072 mov [rdx+20h], r10
.text:0000000140003076 mov edx, 660Eh
.text:000000014000307B call rax ; CryptDerivedKey
;; truncated...
Sau đoạn asm trên ta có thể thấy được hàm decrypt data từ resource là AES128 ở mode CBC (mode mặc định của API windows) với key là 16 bytes đầu của SHA256("abO4oHxrfR03YwaX4KuEFUoV"), iv sẽ là 16 bytes \x00.
Lạc trôi
Chúng mình thử decrypt từ resource thì nhận được 3 files
- 1 là file golang
- 1 là script AutoIt mình gọi là script
au_2 - 1 là file exe
Chúng mình có xem qua cả 3 files, và ngạc nhiên khi đọc au_2 vì có đoạn:
Func compute()
Local $msg = ''
For $i = 0x0 To 0x28 Step + 0x1
Sleep(0x2710)
$msg = $msg & Chr(getchar($i))
Next
Return $msg
EndFunc ; -> compute
Func getchar($number)
If $number <= 0x9 Then
Return 0x30 + $number
Else
Local $sum = 0x0
For $i = 0x1 To 0xa Step + 0x1
$sum = BitAND($sum + getchar($number - $i), 0xff)
Next
Return $sum
EndIf
EndFunc ; -> getchar
MsgBox(0x40, "Flag Generator", "Computing flag....")
Sleep(0xc350)
Local $flag_text = "zeropts<"
Sleep(0xc350)
Local $flag = compute()
Sleep(0x186a0)
MsgBox(0x40, "Flag Generator", $flag_text & $flag & ">")
MsgBox(0x40, "Flag Generator", "Bye Bye...")
Mình đã nghĩ đến đây sẽ có được
, nhưng đời không như mơ, nếu dễ thế này thì đã nhiều team làm được rồi 
Đừng thấy hoa nở mà ngỡ xuân về
Một file exe thì chỉ là run cmd.exe và khi debug chúng mình không thấy anime chạy file cmd.exe , vậy nên khả năng cao file exe mình decrypt được cũng chỉ là một file lừa 
Cách go file đc inject vào các process
Sau khi được decrypt, file golang được inject vào tất cả các process random trong những process sau: ["write.exe", "notepad.exe", "calc.exe", "werfault.exe", "cscript.exe"]
Trong script au_1 ta thấy, có một đoạn chương trình sẽ chạy random một trong 5 file và lấy ra: hProcess, hThread, ProcessID, ThreadID. Sau đó truyền đến cho file anime xử lý.
Local $tprocess = $runtime_systemstack($TAGPROCESS_INFORMATION)
Local $tstartup = $runtime_systemstack($TAGSTARTUPINFO)
$runtime_newgoroutine('', "C:\\Windows\\System32\\" & getanimepath(), 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, $tstartup, $tprocess)
Local $h_pr_left = $runtime_panic($tprocess, "hProcess")
Local $h_th_left = $runtime_panic($tprocess, "hThread")
Local $t_pid_left = $runtime_panic($tprocess, "ProcessID")
Local $t_tid_left = $runtime_panic($tprocess, "ThreadID")
// truncated...
Func getanimepath()
Local $anime_list[] = ["write.exe", "notepad.exe", "calc.exe", "werfault.exe", "cscript.exe"]
Return $anime_list[Random(0x0, UBound($anime_list) + -1)]
EndFunc ; -> getanimepath
Đoạn code xử lý inject trong file anime. Chương trình ghi data sau khi decrypt ra một file trong temp và lại được map vào một vùng trong process mới, ngay sau đó file trong temp cũng được xóa đi luôn.
__int64 __fastcall inject_gofile_to_process(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
// truncated...
v24 = sub_7FF7B1AF4B90(a4);
v23 = sub_7FF7B1AF4BB0(v29);
v21 = write_temp_file(v24, v23); // <-- write data to temp file (format: TH*.tmp)
if ( v21 )
{
if ( v21 != -1 )
{
v20 = 0;
v19 = 0i64;
v18 = 0i64;
NtMapViewOfSection = calc_hash_lookup_api(-1908709007, -720004204);
v20 = (NtMapViewOfSection)(v21, v26, &v18, 0i64, 0i64, 0i64, &v19, 1, 0, 2);
if ( !v20 || v20 == 1073741827 )
{
v17[1] = v24;
v17[0] = v18;
v16 = *(v24 + 60) + v24;
v15 = *(v16 + 40) + v18;
memset(Dst, 0, 0x4D0ui64);
v12 = 1048578;
NtGetContextThread = calc_hash_lookup_api(-1908709007, -382344301);
NtGetContextThread(v27, Dst);
v13 = v15;
NtSetContextThread = calc_hash_lookup_api(-1908709007, 1765139349);
NtSetContextThread(v27, Dst);
v10 = v14;
v9 = v14 + 16;
NtWriteVirtualMemory = calc_hash_lookup_api(-1908709007, -988771134);
NtWriteVirtualMemory(v26, v9, v17, 8i64, v22); // <-- Write from file to virtual memory in each process
return v10;
}
}
}
return v25;
}
Mình nhận thấy chương trình chỉ decrypt và inject file golang.
ID=7
Dưới đây là đoạn code tạo struct data để truyền cho anime
_NamedPipes_CreatePipe($eoigwowufbwfo, $hwritepipe)
_NamedPipes_CreatePipe($hreadpipe, $asfgeonwog)
$unidentified_flying_object = $runtime_systemstack("int;int;int")
$runtime_morestack($unidentified_flying_object, 0x1, 0x7)
$runtime_morestack($unidentified_flying_object, 0x2, $hreadpipe)
$runtime_morestack($unidentified_flying_object, 0x3, $hwritepipe)
// truncated...
$runtime_free($asfgeonwog)
$runtime_free($eoigwowufbwfo)
$resp = findbestanimefrommyanimelist($runtime_getg($unidentified_flying_object), $runtime_funcsize($unidentified_flying_object))
$unidentified_flying_object = $runtime_systemstack("int;int", $runtime_getg($resp))
Local $l1 = $runtime_panic($unidentified_flying_object, 0x2)
Struct được gửi đi sẽ như sau:
struct ID_07 {
int id=7;
int hreadpipe=xx;
int hwritepipe=xx;
}
Có thể thấy, script au_1 gửi đi 2 pipe một là read và một là write Mà trước đó, data được read từ resource:
Local $aa1 = _WinAPI_FindResource(0x0, 0x3, 0xc)
Local $aa2 = _WinAPI_SizeOfResource(0x0, $aa1)
Local $aa3 = _WinAPI_LoadResource(0x0, $aa1)
Local $aa4 = _WinAPI_LockResource($aa3)
Local $nullptr = $runtime_systemstack("ptr;int")
$runtime_morestack($nullptr, 0x1, $aa4)
$runtime_morestack($nullptr, 0x2, $aa2)
Local $inullptr
_WinAPI_WriteFile($asfgeonwog, $runtime_getg($nullptr), $runtime_funcsize($nullptr), $inullptr)
Hàm _WinAPI_FindResource(0x0, 0x3, 0xc) sẽ lấy resource thứ 12 (0xc) ở resourc icon (id=3)
RT_ICON
MAKEINTRESOURCE(3) Hardware-dependent icon resource.
Và struct truyền vào pipe mới này sẽ có dạng như sau:
struct DATA_PIPE2 {
char *ptr=xx; // pointer to start of icon12 resource
int sizeof_rs=xx; // size of resource
}
Trong chương trình anime, hàm xử lý id=7 là hàm fn_resource. Hàm này có nhận struct DATA_PIPE2 từ pipe mới bên trên và đọc ra 2 giá trị ptr_data và size_data. Sau đó dùng hàm ReadProcessMemory để đọc toàn bộ data từ file Icon12.png
void main_process()
{
// truncated...
handle_func(v17, &id_7, fn_resource);
// truncated...
}
void __fastcall fn_resource(int *a1, __int64 a2)
{
// truncated...
v38 = (OpenProcess)(0x450i64, 0i64, qword_14007DEF0);
DuplicateHandle = calc_hash_lookup_api(0xBF5AFD6F, 0xBD566724);
(DuplicateHandle)(v38, v40, -1i64, &v37, 0, 0, 2);
DuplicateHandle_1 = calc_hash_lookup_api(-1084555921, 0xBD566724);
(DuplicateHandle_1)(v38, v39, -1i64, v36, 0, 0, 2);
ReadFile = calc_hash_lookup_api(-1084555921, 0x10FA6516);
ReadFile(v37, &data, 16i64, &v33, 0i64); // <-- read struct DATA_PIPE2
ptr_data = data;
size_data = size_data;
v30 = sub_14003C460(0i64);
new_mem = j_HeapAlloc(v35_1, v2, v3);
cnt = size_data;
v27 = 0i64;
while ( 1 )
{
v15 = 0;
if ( cnt > 0 )
{
ReadProcessMemory = calc_hash_lookup_api(-1084555921, 1469914089);
v15 = ReadProcessMemory(v38, v27 + ptr_data, &new_mem[v27], v28, &v33) != 0;
}
if ( !v15 )
break;
v27 += v33;
cnt -= v33;
}
// trucated...
}
Tiếp theo đó, chương trình sẽ xử lý những data trong file PNG qua các hàm:
// truncated...
v24 = j_HeapAlloc(v25, v4, v5);
sub_37830(data_pipe, v24, v25, 4, 0); // <-- process PNG data
sub_3D750(data_pipe); // <-- process PNG data too
sub_5CB0(v23, v6);
v22 = 0i64;
v21 = 1;
while ( 1 )
{
v12 = 0;
if ( v22 < sub_6100(v49) )
v12 = qword_7EF68 < v25;
if ( !v12 )
break;
if ( (sub_4A20(qword_7EF68 / 3ui64, qword_7EF68 % 3ui64) & 1) != 0 )
{
v11 = v24[qword_7EF68] % 2;
++v22;
v21 = v11 == (*sub_6110(v49) ^ 0x30) && v21;
}
qword_7EF68 += 3i64;
}
// truncated...
}
Đến đoạn này, đi vào 2 hàm xử lý data PNG là sub_37830 và sub_3D750 mình đã bỏ cuộc tại đây (vì code strip nhiều, đọc rối quá :<). Sau giải mình thấy có team đã rev nó và chức năng của nó là:

Thật khâm phục luôn!!!
Rev golang file
Trong file golang, tại hàm main_init_0, chương trình sẽ lấy handle của 2 hàm:
main_g_func2_r = GetCurrentThreadId;
main_g_func3_r = GetCurrentProcessId;
Tại main_main, chương trình chọn random một process, và sau đó tạo một struct gửi lại cho chương trình anime thông qua pipe đã được tạo trước đó.
struct DATA_GO {
int id=2;
int pid=xx;
int tid=xx;
char byte_data=xx; // <-- pointer to flag characters
//... or maybe something (just rev static only in golang file :< )
}
ID=2
Chương trình gọi đến hàm FN_12345678, chắc là trong hàm này, chương trình sẽ kiểm tra input và giá trị byte nhận được từ go file.
void main_process()
{
// truncated...
id_2 = 2;
handle_func(v12, &id_2, FN_12345678);
// truncated...
}
Kết thúc
Chúng mình không solve được hoàn toàn challenge, chúng mình đã từ bỏ ở bước rev hàm gen_primes và lấy flag từ file PNG .
Buồn++
Ngoài ra
Mọi người có thể tìm hiểu đầy đủ hơn ở những blog sau:
[+] Writeup của team duy nhất solve đc ngày hôm đó:
https://blog.cirn09.xyz/2022/03/23/flag-checker/
[+] Writeup của chính tác giả:
https://suvaditya.one/blog/zer0pts-flag-checker/ __ lanleft __