⚠ 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):

Extracted contents

IR chat log.txt

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.

Detect it easy result

Với .NET, chúng ta có một công cụ khác để có thể decompile source code, đó là dnSpy:

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ờ:

Strange strings

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

sub_100012F1

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ã:

sub_100014AE

Đế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.

Resolve address

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.

Bad eax

Để 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:

Manual fix IAT

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:

Create Thread

Đầ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:

Breakpoint options

Để 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:

Client App

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)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 😂.

Trace at new thread

Dùng F8 trace qua hàm CreateNamedPipeA chúng ta thấy phía client cũng sẽ có sự thay đổi:

Change in client

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:

Final function

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:

Final function

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:

This is the flag

This is the flag too

M1x3d_M0dE_4_l1f3@flare-on.com