⚠ 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.
[06] à la mode
FLARE FACT #824: Disregard flare fact #823 if you are a .NET Reverser too.
We will now reward your fantastic effort with a small binary challenge. You've earned it kid!
7-zip password: flare
Công cụ sử dụng:
- IDA Pro
- x96dbg
- dnSpy
- Visual Studio 2019
- Detect It Easy v3.06
Giải nén ra chúng ta được một file HowDoesThisWork.dll
và đoạn chat log của team Incident Response (IR):
Có vẻ chúng ta sẽ gặp vài khó khăn để có thể chạy được cái DLL này lên chăng, vì ngoài DLL này ra chúng ta cần thêm một binary khác nữa? (nên nó mới tên là HowDoesThisWork
).
Thử scan với Detect It Easy
cho chúng ta kết quả đây là binary viết bằng .NET.
Với .NET, chúng ta có một công cụ khác để có thể decompile source code, đó là dnSpy
:
Mở DLL này trong dnSpy chúng ta thấy ngay một class Flag
cùng với hàm GetFlag
:
using System;
using System.IO.Pipes;
using System.Text;
namespace FlareOn
{
// Token: 0x02000002 RID: 2
public class Flag
{
// Token: 0x06000001 RID: 1 RVA: 0x0000D078 File Offset: 0x0000C478
public string GetFlag(string password)
{
Decoder decoder = Encoding.UTF8.GetDecoder();
UTF8Encoding utf8Encoding = new UTF8Encoding();
string text = "";
byte[] array = new byte[64];
char[] array2 = new char[64];
byte[] bytes = utf8Encoding.GetBytes(password + "\0");
using (NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", "FlareOn", PipeDirection.InOut))
{
namedPipeClientStream.Connect();
namedPipeClientStream.ReadMode = PipeTransmissionMode.Message;
namedPipeClientStream.Write(bytes, 0, Math.Min(bytes.Length, 64));
int byteCount = namedPipeClientStream.Read(array, 0, array.Length);
int chars = decoder.GetChars(array, 0, byteCount, array2, 0);
text += new string(array2, 0, chars);
}
return text;
}
}
}
Hàm này có nhiệm vụ kết nối đến một NamedPipe
có tên là FlareOn
, sau đó truyền vào mật khẩu (password
là tham số của hàm này), và nhận flag trả về.
Nhưng flag ở đâu ra???
Hóa ra DLL này không chỉ có mỗi phần code .NET này mà còn có cả một phần code C++ nữa. Mở DLL này bằng IDA chúng ta thấy ngay một số những string rất đáng ngờ:
Các string này được dùng nhiều ở sub_100012F1
và đều theo một pattern giống nhau: là tham số cho sub_100014AE
Hàm sub_100014AE
có nhiệm vụ khá đơn giản, đó là thực hiện xor chuỗi tham số của hàm với 0x17
để giải mã:
Đến đây ta có thể giải mã các chuỗi trong IDA như sau: (chú ý ký tự h
thì xor tương ứng là \x7F
)
Vbc\x7Fxe~mvc~xy7Qv~{rs -> Authorization Failed
KK9Kg~grKQ{verXy -> \\.\pipe\FlareOn
T{xdr_vys{r -> CloseHandle
TxyyrtcYvzrsG~gr -> ConnectNamedPipe
TervcrYvzrsG~grV -> CreateNamedPipeA
TervcrC\x7Fervs -> CreateThread
S~dtxyyrtcYvzrsG~gr -> DisconnectNamedPipe
Q{bd\x7FQ~{rUbqqredQ~{rUbqqred -> FlushFileBuffers
Prc[vdcReexe -> GetLastError
PrcGextrdd_rvg -> GetProcessHeap
{dcetzgV -> lstrcmpA
ErvsQ~{r -> ReadFile
@e~crQ~{r -> WriteFile
Cũng có kết nối đến NamedPipe
, có vẻ đây chính là server dùng để giao tiếp với client ở trên rồi.
Nếu debug DLL này bằng x96dbg và đặt breakpoint ở 100012F1
và thực hiện trace, ta nhận thấy hàm này sẽ có nhiệm vụ giải mã chuỗi, đồng thời thực hiện resolve các hàm này thành các địa chỉ tương ứng trong thư viện Kernel32.dll
(ở tại sub_1000125C
) rồi lưu ở bảng IAT ở địa chỉ 100015A18
.
Tuy nhiên, có thể do cách chúng ta chạy DLL mà một số hàm khi thực hiện resolve address bị sai và kết quả địa chỉ trả về là eax = 0
.
Để giải quyết vấn đề này, chúng ta có thể lưu sẵn các địa chỉ của các hàm (vì mỗi lần chạy đều không đổi), đặt breakpoint ở cuối hàm này (địa chỉ 10001425
) rồi tiến hành fix IAT bằng tay như sau đây:
Sau khi thoát khỏi hàm này, chúng ta đến địa chỉ 1000116F
, tại đây DLL sẽ thực hiện tạo thread mới bằng hàm CreateThead
với thread entrypoint là 10001094
:
Đầu tiên ta restart lại DLL, chỉnh option của x96dbg như ảnh dưới để break khi có thread mới được tạo:
Để có thể debug được server, ta sẽ viết lại DLL này thành một console app như dưới:
using System;
using System.IO.Pipes;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Decoder decoder = Encoding.UTF8.GetDecoder();
UTF8Encoding uTF8Encoding = new UTF8Encoding();
string text = "";
string password = "hello?";
Console.WriteLine(password);
byte[] array = new byte[64];
char[] array2 = new char[64];
byte[] bytes = uTF8Encoding.GetBytes(password + "\0");
using (NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", "FlareOn", PipeDirection.InOut))
{
Console.WriteLine("Connect!");
namedPipeClientStream.Connect();
namedPipeClientStream.ReadMode = PipeTransmissionMode.Message;
Console.WriteLine("Write!");
namedPipeClientStream.Write(bytes, 0, Math.Min(bytes.Length, 64));
Console.WriteLine("Read!");
int byteCount = namedPipeClientStream.Read(array, 0, array.Length);
int chars = decoder.GetChars(array, 0, byteCount, array2, 0);
text += new string(array2, 0, chars);
Console.WriteLine(text);
}
}
}
}
Rồi chạy sẵn binary này lên:
Chạy lại DLL gốc, thực hiện fix IAT cho đến khi EIP dừng tại địa chỉ 10001184
(ret
sau khi thực thi xong hàm CreateThread
).
Bấm F9, breakpoint sẽ dừng ở RtlUserThreadStart
. Lúc này trên lý thuyết là x96dbg sẽ tạo breakpoint sẵn tại thread entrypoint 10001094
, chúng ta chỉ cần F9 là hit breakpoint. Nhưng lý thuyết là vậy còn thực tế của mình thì không 😒.
Để tránh bị miss breakpoint này, chúng ta sẽ đi thẳng đến 10001094
set Hardware breakpoint (execute)
và Set New Origin here
rồi F8. Nếu thấy bị quay lại ntdll.dll
thì lại tiếp tục back về 10001094
rồi lại Set New Origin here
, rồi lại F8. Khoảng 2 lần như vậy thì hardware breakpoint sẽ hit và chúng ta tiếp tục trace bằng F8 như bình thường 😂.
Dùng F8 trace qua hàm CreateNamedPipeA
chúng ta thấy phía client cũng sẽ có sự thay đổi:
Tiếp tục trace đến địa chỉ 10001124
, tại đây chuỗi chúng ta truyền vào sẽ trở thành tham số cho sub_100001000
:
Nhảy vào hàm này, ta thấy ngay một đoạn call đến hàm so sánh chuỗi lstrcmp
, breakpoint rồi trace đến đây sẽ thấy hiện ra password đúng:
MyV0ic3!
Sửa lại giá trị trên stack để pass qua đoạn so sánh chuỗi này, rồi trace đến địa chỉ 10001074
thì sau lệnh gọi đến sub_10001187
sẽ thấy output ra flag:
M1x3d_M0dE_4_l1f3@flare-on.com