Flare-on 10 WriteUp
Flare-on 10 2023 vừa mới kết thúc, mình cũng đã giải được một số challenges và dưới đây là một số bài writeup của mình. (Flare-on là một cuộc thi CTF hàng năm do FLARE tổ chức, cuộc thi này về mảng RE và kéo dài trong 6 tuần).
Challenge 01
Sử dụng dnSpy mở X.dll, ta dễ dàng tìm thấy flag ở hàm _lockButton_Click
Hàm _lockButton_Click kiểm tra số đầu vào từ người dùng là 42 thì sẽ hiển thị flag
Flag: glorified_captcha@flare-on.com
Challenge 02
Sử dụng JADX để đọc file .apk đề bài cho
Ban đầu, ta sẽ thấy chương trình bị làm rối bằng cách đổi tên class, hàm thành 1 hay 2 ký tự làm cho người phân tích dễ bị rối và khó phân tích hơn.
Sau khi xem qua mã nguồn, mình chạy chương trình để hiểu hơn về nó.
Chương trình đề cho thực ra là một trò chơi “bắn vịt” thần thánh :D.
Kiểm tra resources.arsc\res\values\strings.xml
Nhìn vào nội dung file strings.xml, mình chú ý đến một số string name như ag, alg, c2, file, iv. Ta có thể đoán chương trình có thể xử lý dữ liệu liên quan đến file .png bằng thuật toán AES và giao tiếp với C2 https://flare-on[.]com/evilc2server/report_token/report_token.php?token=
Tiếp tục kiểm tra AndroidManifest, mình xác định được nơi chương trình bắt đầu và chương trình có đăng ký Firebase Cloud Messaging.
Kiểm tra class xử lý game MalwareInvadersActivity, ta sẽ không tìm thấy bất cứ điều gì để tìm ra flag.
Kiểm tra class MessageWorker, có 2 method được ghi đè là onMessageRecieved() và onNewToken(). Để ý hàm onMessageRecieved() mỗi khi nhận message, nó sẽ xử lý message và gọi setFullScreenIntent để cài đặt thông báo với tham số pending intent là c.f362a.a(this, str)
Tùy thuộc vào message được nhận từ onRecieveMessage(), hàm c.f362a.a() sẽ gán các giá trị i2, i3 tương ứng và gọi vào bvar.f(context, i3), i3 ở đây có thể là tên file iv.png hoặc ps.png được đặt trong resource.
Tiếp tục phân tích hàm bVar.f()
Hàm này lấy dữ liệu từ tham số c(i2, context) (i2 chính là iv.png hoặc ps.png) sau đó put dữ liệu sang activity khác.
Hàm c() đọc nội dung từ file tham số truyền vào và giải mã dữ liệu bằng thuật toán AES với Iv = “abcdefghijklmnop” được giấu trong strings.xml
Key = 16 bytes ghép từ số nguyên hash crc32 của c2[4:10] + w1[2:5] = 4508305374508305
Thử giải mã 2 file iv.png và ps.png. Kết quả giải mã file iv.png cho ra output có định dạng .png
Mở file iv.png sau khi giải mã ta có được flag
Flag: Y0Ur3_0N_F1r3_K33P_601N6@flare-on.com
Challenge 03
Chương trình yêu cầu tham số đầu vào. Tham số chứa nhiều dữ liệu/điều kiện kiểm tra cho mỗi hàm hoặc shellcode, chúng được cách nhau bởi ký tự ‘/’.
– Ngay đầu chương trình, ta dễ thấy tham số arvg1 được kiểm tra với các điều kiện. Đối với những điều kiện đơn giản như so sánh thì mình sẽ không đề cập. Lần kiểm tra đầu 4*argv1[1] + argv1[2] = !295. Ở đây mình chọn 2 ký tự là “=, 3”.
Tham số argv1[7:12] được xor với key “ten” và so sánh với 0x16, 0x17, 0x3b, 0x17, 0x56 trong hàm sub_1EA00(), sử dụng python ta dễ dàng tìm được giá trị đúng của argv1[7:12]
Kết quả argv1[7:12] = “brUc3”. Kết hợp với một số điều kiện so sánh khác, ta có được dữ liệu tham số đầu tiên của tham số Argv1[] = “0=300R@brUc3E/input2/…”
Đối với input2: Dữ liệu được chia làm 2 phần, phần số được gán vào đầu file html như một signature, phần sau là tên của file html.
Nội dung của file .html đã bị mã hóa được hardcode sẵn, dữ liệu của file được biến đổi dựa theo ngày hiện tại của hệ thống.
Những điều kiện kiểm tra của input2 được kiểm tra ở hàm sub_18B0(). Input2 = 1337pr.ost. Như vậy ta có argv1[] = “0=300R@brUc3E/1337pr.ost /input3/…”
Input3 được kiểm tra ở hàm sub_1EAE0(), input3 cũng được chia làm 2 phần, phần đầu là một số hệ 10 sẽ được chuyển đổi sang hệ 4 ở sub_71A0() và là kích thước của chuỗi phần sau.
Phần đầu của input3 sau khi chuyển sang hệ 4 sẽ được làm tham số cho hàm sleep(). Tuy nhiên vì chương tình có kiểm tra thời gian thực thi bằng cách 2 lần gọi GetTickCount(), lần gọi 2 ở sub_18B0(). Nếu khoảng thời gian giữa 2 lần gọi bé hơn hoặc bằng 8s thì chương trình sẽ exit. Vì vậy, mình chọn tham số input3 = “20bcadbcda” (20 chuyển sang hệ 4 là 8, chuỗi ‘bcadbcda’ được chọn bất kỳ, miễn kích nó bằng phần trước)
Như vậy, ta có argv1[] = “0=300R@brUc3E/1337pr.ost /20bcadbcda/input4/…”
Input4 sẽ so sánh với chuỗi “pizza” ở sub_18B0(). Tham số bây giờ là argv1[] = “0=300R@brUc3E/1337pr.ost /20bcadbcda/5pizza/input5/…”
Input5 yêu cầu chúng ta cần phải sửa một số byte của 2 shellcode ở hàm sub_2170(). Nếu bạn quen với việc phân tích mã độc thì không khó để nhận ra chức năng của 2 shellcode, shellcode thứ nhất dùng để lấy base address của kernel32.dll và shellcode thứ 2 lấy địa chỉ của hàm API. Ở đây, chúng ta chỉ cần sửa các byte để shellcode có thể thực thi làm đúng chức năng của nó là được. Mình chọn input5 = “AMu$E`0R.0AZe”. Chúng ta có tham số argv1[] = “0=300R@brUc3E/1337pr.ost /20bcadbcda/5pizza/ AMu$E`0R.0AZe/input6/…”
Input6 sau khi biến đổi sẽ được so sánh với “RUECKWAERTSINGENIEURWESEN” trong hàm sub_2910().
Để tìm input6, ta có thể sử dụng python
Kết quả input6 = “YPXEKCZXYIGMNOXNMXPYCXGXN”. Như vậy, ta có argv1[] = “0=300R@brUc3E/1337pr.ost /20bcadbcda/5pizza/ AMu$E`0R.0AZe/ YPXEKCZXYIGMNOXNMXPYCXGXN/input7/input8/”
Đối với input7, input8 sẽ được so sánh lần lượt với “ob5cUr3” và “fin”. Cuối cùng ta có argv1[] = “0=300R@brUc3E/1337pr.ost /20bcadbcda/5pizza/ AMu$E`0R.0AZe/ YPXEKCZXYIGMNOXNMXPYCXGXN/ob5cUr3/fin/”
Dữ liệu file .html sau khi được giải mã bằng thuật toán RC4 với key = “REVERSEENGINEER” sẽ được checksum bởi hash crc32 ở hàm sub_2670(). Bây giờ, nếu bạn muốn chạy chương trình với tham số tìm được mà ra flag luôn thì buộc phải tìm đúng dữ liệu file .html. Tuy nhiên file html được gen từ một buffer hardcode sẵn và encode liên quan đến ngày hiện tại trên hệ thống nên cần phải thử từ ngày 1->31 để tìm được ngày đúng.
Còn mình thì thực hiện patch ở điều kiện if so sánh giá trị hash crc32. Chạy chương trình với tham số đã tìm, ta có được flag.
Flag: b0rn_t0_5truc7_b4by@flare-on.com
Challenge 04
Chương trình ban đầu tạo một cửa sổ có tên BananaAimBot có hàm callback 402AF0().
Mỗi khi có sự hiện WM_COMMAND hay chính là sự kiện click vào BUTTON “Launch Sauerbraten with Aimbot”, hàm callback sẽ gọi hàm 402150(). Ngoài ra, khi thấy tác giả viết chương trình nhắc đến sauerbraten by Potassium Crew như là một sự giới thiệu hay quảng cáo, mình nghĩ đến việc nó sẽ khởi chạy 1 chương trình/trò chơi khác.
Tiếp tục kiểm tra hàm 402150 (), ban đầu hàm này kiểm tra file “%PROGRAMFILES(X86)%\\Sauerbraten\\bin64\\sauerbraten.exe” và tính hash md5, sau đó so sánh với 1 giá md5 hardcode. Nếu bằng nhau chương trình tiếp tục thực thi, ngược lại thì nó sẽ exit.
Sau khi lên mạng tìm kiếm Sauerbraten, mình thấy nó là một trò chơi và thử tải phiên bản 2020_12_21 về cài đặt, sau khi kiểm tra hash thì mình thấy md5 đã bằng giá trị được hardcode nên tiếp tục phân tích luồng của chương trình.
Chương trình sau đó trích xuất 3 file miner.exe, config.json, aimbot.dll vào thư mục %APPDATA%\\BananaBot bằng cách giải mã resource sử dụng AES với key là “yummyvitamincjoy”
Sau đó chương trình tạo, thực thi lần 2 tiến trình miner.exe, sauerbraten.exe và inject aimbot.dll bằng kỹ thuật DLL injection vào tiến trình sauerbraten.exe.
Sử dụng ida để phân tích aimbot.dll. File thực thi này có nhiều kỹ thuật antidebug
- Windows api: IsDebuggerPresent, CheckRemoteDebuggerPresent, VirtualProtect, DbgBreakPoint
- Checksum hash tên các tiến trình như: ida64.exe, x64dbg.exe
- Kiểm tra parrent process có phải là aimbot.exe
- Kiểm tra aimbot.dll có chạy trong sauerbraten.exe
Có 4 strings ở địa chỉ 0x62FE4020 được giải mã trong hàm 62f439b0() bằng xor key hardcode A9F89964, kết quả các strings sau giải mã:
- http://127.0.0.1:57328/2/summary
- bananabot 5000
- “version”: “
- the decryption of this blob was successful
Sau đó chương trình giải mã shellcode bằng cách đọc key từ XMRig http://127.0.0.1:57328/2/summary. Shellcode được giải mã AES ECB với key “version”: “6.20
Sau khi giải mã, ta thu được 1 file chứa shellcode:
Xóa phần header, lấy dữ liệu từ offset 0x2A, ta tiếp tục phân tích với ida. Shellcode sẽ giải mã những shellcode khác và thực hiện lần lượt các hành vi đánh cắp dữ liệu của discord, steams, sparraw và lưu vào C:\depot. Sau đó sẽ gửi những dữ liệu này tới https://bighackies.flare-on.com/stolen.
Shellcode cuối cùng, đọc dữ liệu từ file .cfg của map trong trò chơi, và so sánh 4 byte đầu với “spcr”. Nếu đúng nó sẽ tiếp tục thực thi.
Tuy nhiên trò chơi có 2 map có chứa “spcr”. Chỉ cần debug shellcode và sau 2 lần thử chơi game với map spcr và spcr2 và thực thi bỏ qua các điều kiện IF đến cuối shellcode, thì với map spcr2 ta có được flag.
Flag: computer_ass1sted_ctfing@flare-on.com
Challenge 05: Where am i
Sử dụng API monitor, ta thấy chương trình load 2 resource và đều cấp phát vùng nhớ PAGE_EXCECUTE_READWRITE. Khả năng chương trình load và thực thi shellcode từ resource.
Ngoài, ta cũng có thể thấy chương trình tạo tiến trình Explorer.exe và sử dụng kỹ thuật APC injection để inject shellcode/PE file và sau đó tạo pipe name whereami để giao tiếp với tiến trình vừa tạo.
Như vậy, cơ bản chúng ta đã hiểu được luồng thực thi của chương trình. Tiến hành đặt breakpoint ở API VirtualAllocEx để xác định vùng bộ nhớ được tạo trong explorer.exe và WriteProcessMemory để theo dõi dữ liệu được ghi vào vùng nhớ đó, ta thấy dữ liệu được inject là shellcode.
Tiến hành dump shellcode. Vì shellcode được làm rối khá nặng bằng các kỹ thuật multi jump kết hợp junkcode. Tùy nhiên ghidra có thể đánh bại các kỹ thuật này nên mình đã chuyển shellcode thành file exe bằng công cụ shell2exe và phân tích nó bằng ghidra.
Ta thấy, shellcode tiếp tục giải mã một số phần dữ liệu của nó. Đặt breakpoint tại RVA 0x1df của shellcode và trace qua hàm decrypt, sau khi decrypt ta thấy dữ liệu sau giải mã là một pe file bị xóa một số phần header.
Tiến hành dump và fix PE file.
PE file đọc dữ liệu từ pipe, sau đó kiểm tra 3 điều kiện:
- Đường dẫn file thực thi có chứ “C\\Users\\Public\\”
- Username có phải là “flare”
- baseImage+0x3FC có bằng 0xBAADBEEF
Nếu thỏa mãn các điều kiện, nó sẽ hiện thị messagebox với nội dung gợi ý có mã hóa RC6 ở đâu đó và dĩ nhiên là chúng ta vẫn chưa có flag mặc dù đã đi hết luồng chương trình.
Vì các shellcode được làm rối khá nhiều nên mình nghĩ có gì đó được dấu trong shellcode. Mình quyết định dump 2 shellcode sau giải mã từ 2 resource ban đầu và chuyển chúng sang file exe và sử dụng ghidra để phân tích.
Thật may là mình đã phát hiện hàm genkey của thuật toán mã hóa RC6 với các signature 0xb7e15163 và 0x9e3779b9 được dùng để giải mã resource2.
Đặc biệt key dùng giải mã chính là 0xBAADBEEF ở offset 0x3fc, giá trị này cũng được kiểm tra trong điều kiện ở file PE được dump
Để ý dữ liệu từ offset 0x3FC của pe file được dump. Có bắt đầu và kết thúc đều là 0xBAADBEEF.
Tiến hành thay dữ liệu resource2 bằng vùng dữ liệu ở giữa 2 0xBAADBEEF, sau khi chương trình gọi hàm rc6decrypt ở RVA 0x69df từ vùng nhớ của shellcode1, ta có được flag.
Flag: WheR3_4m_I_fr0m_0TF@flare-on.com
By,
Blue_Team