⚠ Spoiler: Đây là write-up cho các challenge của Flare-on 9 tổ chức vào khoảng tháng 11/2022 tại Website.
[04] darn_mice
"If it crashes its user error." -Flare Team
7-zip password: flare
Công cụ sử dụng:
- IDA Pro
- x96dbg
- Detect It Easy v3.06
Thử scan với Detect It Easy v3.06
chúng ta biết được darn_mice.exe
là một ứng dụng console viết bằng C++.
và nếu double-click vào chạy thử, ta thấy chương trình quit luôn. Phải chăng là do lỗi người dùng thật?. Nope, đơn giản là chương trình yêu cầu tham số nhập vào (nhìn vào hàm main
trong IDA ta thấy ngay điều này ở đoạn so sánh argc
với 2
):
Chạy lại với tham số, lần này có khá khẩm hơn 1 chút, chương trình in ra vài dòng nhưng rồi cũng exit luôn:
Lần theo chuỗi được in ra trong IDA, đưa chúng ta đến với sub_6E1000
là nơi chuỗi này được dùng (cũng chính là sub được gọi đến trong hàm main
). Tham số được truyền vào ở đây chính là chuỗi chúng ta nhập vào:
Ở đây mình đã rename các hàm cùng các biến để cho dễ hiểu:
sub_6E1280
có chuỗiBCryptDeriveKeyPBKDF2 failed
chúng ta tạm gọi làbcrypt_magic
. Trong đoạn này còn gọi đến hàm gì đó liên quan đếnRC4
, tạm thời chúng ta chưa cần quan tâm vội.- Chuỗi nhập vào chúng ta sẽ gọi là
pbPassword
. - Ở đây
if ( !v3 || v3 > 35 )
có kiểm tra độ dài password nhập vào. Cùng với đoạn for ngay tiếp theo, ta đoán password có độ dài 35 ký tự (hay0x23 = 0x24 - 1
). - Đoạn for phía sau khá là lạ:
- Đầu tiên, thực hiện allocation một vùng nhớ, kích thước
0x1000
lưu vàov2
. - Sau đó là lấy từng ký tự của
password
, sau đó đem cộng với giá trị tương ứng ở vị trí đó của biếnmagic
được setup ở đầu function. - Gán giá trị này cho byte đầu tiên của vùng nhớ vừa khởi tạo.
- Nhảy đến và chạy code tại vùng nhớ đó (tương ứng với lệnh gọi
v2(v2)
). - In ra chuỗi
Nibble...
- Tiếp tục vòng lặp
- Đầu tiên, thực hiện allocation một vùng nhớ, kích thước
- Qua đoạn for này, nếu nhập đúng password, chương trình sẽ dùng password này để decrypt ra flag.
Giả sử với chuỗi nhập vào là AAAAAAAAA
, thì ở lần lặp đầu tiên của vòng for, giá trị đầu tiên mảng magic
là P
(code qmemcpy(magic, "P^^", 3);
) chúng ta sẽ tính toán như sau:
>>> hex(ord('A') + ord('P'))
'0x91'
để ra giá trị tại vùng nhớ địa chỉ 010F0000 là
0x91` như trong screen shot dưới đây, khi debug bằng x96dbg:
Vậy bài toán của chúng ta là xây dựng code làm sao để thực hiện đúng 1 lệnh assembly (opcode) mà:
- Chương trình không bị crash.
- Sau khi thực hiện xong thì quay về đúng với flow cũ của chương trình (để tiếp tục in ra dòng
Nibble...
).
Câu trả lời của chúng ta không phải là opcode 0x90
(NOP) mà chính là opcode 0xC3
(RET), thường xuất hiện ở cuối của function:
Khi call vào vùng nhớ, gặp opcode ret
thì ngay lập tức flow code sẽ quay về và tiếp tục chạy như bình thường.
Viết một đoạn python để re-build lại password từ magic:
# from setup part of sub_6E1000
>>> magic = [0x50, 0x5E, 0x5E, 0xA3, 0x4F, 0x5B, 0x51, 0x5E, 0x5E, 0x97, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA3, 0x80, 0x90, 0xA2, 0xA3, 0x6B, 0x7F]
>>> "".join([chr(0xC3 - m) for m in magic])
'see three, C3 C3 C3 C3 C3 C3 C3! XD'
Vậy password đúng là see three, C3 C3 C3 C3 C3 C3 C3! XD
. Chạy lại chương trình với password này sẽ in ra flag:
i_w0uld_l1k3_to_RETurn_this_joke@flare-on.com