The following challenge was part of the Alice in Wonderland themed INTENT CTF 2022 and was tagged as Forensics and Reverse Engineering challenge. This was the first time I did memory dump analysis, and really enjoyed the different “layers” to uncover and reverse. So please bear with my forced puns and references to Alice in Wonderland and read on…

Challenge Description

Analyze the memory dump to find any suspicious processes and find the flag.

Crawling Into The Rabbit Hole: First Memory Analysis and Data Extraction

As the file utility does not recognize any file structure in our VMEM file, we found through online research that a vmem file is a raw dump of virtual machine memory. It can be a page file or (most likely in our case) a full memory dump converted from a VM snapshot.

Analyzing memory snapshots can be done with volatility. It comes in a 2.x and 3.x branch, the latter a python3 rewrite which hasn’t achieved feature parity yet. I chose the 3.x branch as I didn’t want to mess with python2, but in retrospect I could have used a precompiled binary version of both branches.

Let’s verify what we have in that memory dump.

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.info
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                                                                                              
Variable	Value

Kernel Base	0xf80378e83000
DTB	0x1ab000
Symbols	file:///usr/lib/python3.10/site-packages/volatility3/symbols/windows/ntkrnlmp.pdb/CB2D765201C348A8B9FB66294E6614F7-1.json.xz
Is64Bit	True
IsPAE	False
layer_name	0 WindowsIntel32e
memory_layer	1 FileLayer
KdVersionBlock	0xf803791d8720
Major/Minor	15.16299
MachineType	34404
KeNumberProcessors	2
SystemTime	2022-09-02 22:32:31
NtSystemRoot	C:\Windows
NtProductType	NtProductWinNt
NtMajorVersion	10
NtMinorVersion	0
PE MajorOperatingSystemVersion	10
PE MinorOperatingSystemVersion	0
PE Machine	34404
PE TimeDateStamp	Tue Apr  2 04:29:15 2019

OK, as expected we have a Windows 10 64-bit machine. Let’s have a look at the processes that were running during snapshot.

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.pslist
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
PID	PPID	ImageFileName	Offset(V)	Threads	Handles	SessionId	Wow64	CreateTime	ExitTime	File output

4	0	System	0xa08ce048c440	123	-	N/A	False	2022-09-02 22:01:11.000000 	N/A	Disabled
280	4	smss.exe	0xa08ce1772040	3	-	N/A	False	2022-09-02 22:01:11.000000 	N/A	Disabled
404	396	csrss.exe	0xa08ce1bb2580	11	-	0	False	2022-09-02 22:01:12.000000 	N/A	Disabled
476	280	smss.exe	0xa08ce1bb4580	0	-	1	False	2022-09-02 22:01:12.000000 	2022-09-02 22:01:12.000000 	Disabled
484	396	wininit.exe	0xa08ce1fad080	5	-	0	False	2022-09-02 22:01:12.000000 	N/A	Disabled
492	476	csrss.exe	0xa08ce1fbb080	12	-	1	False	2022-09-02 22:01:12.000000 	N/A	Disabled
576	476	winlogon.exe	0xa08ce1ae6480	6	-	1	False	2022-09-02 22:01:12.000000 	N/A	Disabled
616	484	services.exe	0xa08ce204a080	11	-	0	False	2022-09-02 22:01:12.000000 	N/A	Disabled
632	484	lsass.exe	0xa08ce2051080	14	-	0	False	2022-09-02 22:01:12.000000 	N/A	Disabled
740	616	svchost.exe	0xa08ce1a0e580	32	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
752	484	fontdrvhost.ex	0xa08ce1a44080	5	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
760	576	fontdrvhost.ex	0xa08ce1a46080	5	-	1	False	2022-09-02 22:01:13.000000 	N/A	Disabled
864	616	svchost.exe	0xa08ce1abc580	15	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
960	576	dwm.exe	0xa08ce20d4080	11	-	1	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1020	616	svchost.exe	0xa08ce20f2340	74	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
432	616	svchost.exe	0xa08ce2119280	27	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
396	616	svchost.exe	0xa08ce211f580	44	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
856	616	svchost.exe	0xa08ce2148580	20	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1092	616	svchost.exe	0xa08ce218a580	28	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1328	616	svchost.exe	0xa08ce21e3580	33	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1356	4	MemCompression	0xa08ce23e9280	18	-	N/A	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1524	616	svchost.exe	0xa08ce048f340	11	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1572	616	svchost.exe	0xa08ce04bc580	5	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1580	616	svchost.exe	0xa08ce04b4580	15	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1672	616	svchost.exe	0xa08ce04f9580	7	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1700	616	spoolsv.exe	0xa08ce04fb580	13	-	0	False	2022-09-02 22:01:13.000000 	N/A	Disabled
1936	616	svchost.exe	0xa08ce1d28580	12	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
1980	616	SecurityHealth	0xa08ce1d4e580	11	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
2004	616	vmtoolsd.exe	0xa08ce1d59580	13	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
2012	616	VGAuthService.	0xa08ce1d57580	4	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
2028	616	vm3dservice.ex	0xa08ce1d62580	5	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
1340	616	MsMpEng.exe	0xa08ce1d7d580	34	-	0	False	2022-09-02 22:01:14.000000 	N/A	Disabled
2088	2028	vm3dservice.ex	0xa08ce1d94580	4	-	1	False	2022-09-02 22:01:14.000000 	N/A	Disabled
2668	616	dllhost.exe	0xa08ce25de080	14	-	0	False	2022-09-02 22:01:15.000000 	N/A	Disabled
2788	740	WmiPrvSE.exe	0xa08ce26af580	15	-	0	False	2022-09-02 22:01:15.000000 	N/A	Disabled
2972	1020	sihost.exe	0xa08ce274b580	18	-	1	False	2022-09-02 22:01:15.000000 	N/A	Disabled
2984	616	svchost.exe	0xa08ce273b580	24	-	1	False	2022-09-02 22:01:15.000000 	N/A	Disabled
3052	1020	taskhostw.exe	0xa08ce275b580	9	-	1	False	2022-09-02 22:01:15.000000 	N/A	Disabled
2392	856	ctfmon.exe	0xa08ce27de580	9	-	1	False	2022-09-02 22:01:15.000000 	N/A	Disabled
2460	576	userinit.exe	0xa08ce284b340	0	-	1	False	2022-09-02 22:01:15.000000 	2022-09-02 22:01:37.000000 	Disabled
2700	2460	explorer.exe	0xa08ce2853580	77	-	1	False	2022-09-02 22:01:15.000000 	N/A	Disabled
3344	616	SearchIndexer.	0xa08ce28f3300	16	-	0	False	2022-09-02 22:01:16.000000 	N/A	Disabled
3732	740	ShellExperienc	0xa08ce2a59080	28	-	1	False	2022-09-02 22:01:17.000000 	N/A	Disabled
3928	616	msdtc.exe	0xa08ce2b61580	12	-	0	False	2022-09-02 22:01:17.000000 	N/A	Disabled
4028	740	SearchUI.exe	0xa08ce2baf580	30	-	1	False	2022-09-02 22:01:17.000000 	N/A	Disabled
4088	740	RuntimeBroker.	0xa08ce2ce3080	9	-	1	False	2022-09-02 22:01:17.000000 	N/A	Disabled
3196	740	RuntimeBroker.	0xa08ce2d60580	20	-	1	False	2022-09-02 22:01:18.000000 	N/A	Disabled
4660	740	SkypeBackgroun	0xa08ce308b080	4	-	1	False	2022-09-02 22:01:19.000000 	N/A	Disabled
5104	616	NisSrv.exe	0xa08ce2e82580	11	-	0	False	2022-09-02 22:01:21.000000 	N/A	Disabled
3232	2700	MSASCuiL.exe	0xa08ce2db8580	4	-	1	False	2022-09-02 22:01:29.000000 	N/A	Disabled
5912	2700	vmtoolsd.exe	0xa08ce304e080	8	-	1	False	2022-09-02 22:01:29.000000 	N/A	Disabled
6100	2700	OneDrive.exe	0xa08ce2eb1080	23	-	1	False	2022-09-02 22:01:31.000000 	N/A	Disabled
6164	6100	Microsoft.Shar	0xa08ce30c1580	0	-	1	False	2022-09-02 22:01:32.000000 	2022-09-02 22:01:43.000000 	Disabled
6392	740	WmiPrvSE.exe	0xa08ce34ae580	8	-	0	False	2022-09-02 22:01:34.000000 	N/A	Disabled
6668	616	svchost.exe	0xa08ce3004580	10	-	0	False	2022-09-02 22:01:39.000000 	N/A	Disabled
4080	616	sedsvc.exe	0xa08ce2c4e080	4	-	0	False	2022-09-02 22:03:15.000000 	N/A	Disabled
6616	616	svchost.exe	0xa08ce3515080	19	-	0	False	2022-09-02 22:04:14.000000 	N/A	Disabled
2816	740	smartscreen.ex	0xa08ce2cea080	9	-	1	False	2022-09-02 22:09:43.000000 	N/A	Disabled
4428	1524	audiodg.exe	0xa08ce30af580	6	-	0	False	2022-09-02 22:09:44.000000 	N/A	Disabled
4484	2700	TeaParty.exe	0xa08ce26aa080	9	-	1	True	2022-09-02 22:26:18.000000 	N/A	Disabled
6896	616	svchost.exe	0xa08ce2848580	6	-	0	False	2022-09-02 22:32:02.000000 	N/A	Disabled
6356	616	sppsvc.exe	0xa08ce38cd580	9	-	0	False	2022-09-02 22:32:03.000000 	N/A	Disabled
2932	740	SppExtComObj.E	0xa08ce2fef080	7	-	0	False	2022-09-02 22:32:03.000000 	N/A	Disabled
5092	2932	slui.exe	0xa08ce216d2c0	7	-	0	False	2022-09-02 22:32:08.000000 	N/A	Disabled
3244	740	slui.exe	0xa08ce2983580	7	-	1	False	2022-09-02 22:32:30.000000 	N/A	Disabled
2316	2004	cmd.exe	0xa08ce2fb6580	0	-	0	False	2022-09-02 22:32:31.000000 	2022-09-02 22:32:31.000000 	Disabled
6640	2316	conhost.exe	0xa08ce1ead580	0	-	0	False	2022-09-02 22:32:31.000000 	2022-09-02 22:32:31.000000 	Disabled

Having the Alice in Wonderland theme and the challenge name Forensic Tea Party in mind, the process TeaParty.exe with PID 4484 looks very suspicious!

4484	2700	TeaParty.exe	0xa08ce26aa080	9	-	1	True	2022-09-02 22:26:18.000000 	N/A	Disabled

We can have a look at how the executable was called and where it resided on disk.

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.cmdline --pid 4484
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
PID	Process	Args

4484	TeaParty.exe	"C:\Users\TheMadHatter\Desktop\TeaParty.exe" 

I did try to locate other processes started from C:\Users\TheMadHatter\Desktop\TeaParty.exe (omitting the pid argument), but there were none. I also wasn’t able to dump the open file handles of the process - I don’t really know why.

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.handles --pid 4484
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
PID	Process	Offset	HandleValue	Type	GrantedAccess	Name


Volatility was unable to read a requested page:
Page error 0xf80379570ff0 in layer layer_name (Page Fault at entry 0x5b5e00002064 in page entry)

	* Memory smear during acquisition (try re-acquiring if possible)
	* An intentionally invalid page lookup (operating system protection)
	* A bug in the plugin/volatility3 (re-run with -vvv and file a bug)

No further results will be produced

So, let’s dump the process memory and file for further analysis

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.memmap --pid 4484 --dump

we retrieve 323 Megabyte of process memory in file pid.4484.dmp

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.filescan | grep TeaParty
0xa08ce2930820.0\Users\TheMadHatter\Desktop\TeaParty.exe	216
0xa08ce30512e0	\Users\TheMadHatter\Desktop\TeaParty.exe	216
0xa08ce34c31c0	\Users\TheMadHatter\Desktop\TeaParty.exe	216
0xa08ce38c74b0	\Program Files\AliceInWonderlandTeaParty\TeaParty.sys	216

What a surprise :) TeaParty.sys will also play a part in this write-up later. Let’s dump both files.

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.dumpfiles --virtaddr 0xa08ce34c31c0
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
Cache	FileObject	FileName	Result

DataSectionObject	0xa08ce34c31c0	TeaParty.exe	file.0xa08ce34c31c0.0xa08ce34fb1b0.DataSectionObject.TeaParty.exe.dat
ImageSectionObject	0xa08ce34c31c0	TeaParty.exe	file.0xa08ce34c31c0.0xa08ce2b1e2e0.ImageSectionObject.TeaParty.exe.img

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.dumpfiles --virtaddr 0xa08ce38c74b0
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
Cache	FileObject	FileName	Result

DataSectionObject	0xa08ce38c74b0	TeaParty.sys	file.0xa08ce38c74b0.0xa08ce2ebde50.DataSectionObject.TeaParty.sys.dat
ImageSectionObject	0xa08ce38c74b0	TeaParty.sys	file.0xa08ce38c74b0.0xa08ce3537de0.ImageSectionObject.TeaParty.sys.img

For each file we get two versions: A dat data file representing the executable data (i.e. the executable file on disk that was started by the process) and am img image file (i.e. the executable file loaded as it is loaded in RAM, with absolute addresses and all segments).

Follow the Rabbit: Examining and Reversing the Executable

The file command makes me rejoice: It’s a .net assembly executable, which means that we can easily obtain and even manipulate the source in a high level language.

❯ file file.*
file.0xa08ce34c31c0.0xa08ce2b1e2e0.ImageSectionObject.TeaParty.exe.img: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
file.0xa08ce34c31c0.0xa08ce34fb1b0.DataSectionObject.TeaParty.exe.dat:  PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
file.0xa08ce38c74b0.0xa08ce2ebde50.DataSectionObject.TeaParty.sys.dat:  PE32+ executable (native) x86-64, for MS Windows
file.0xa08ce38c74b0.0xa08ce3537de0.ImageSectionObject.TeaParty.sys.img: PE32+ executable (native) x86-64, for MS Windows

So let’s rename the dats to their original filenames and fire up ILSpy and load TeaParty.exe

image-20221217193851517

We can reverse the application code flow out of the TeaParty class (excerpt - missing code is kernel32.dll, user32.dll, or advapi32.dll windows API)

private static string CalculateMd5HexDigest(string data)
{
	using MD5 mD = MD5.Create();
	return BitConverter.ToString(mD.ComputeHash(Encoding.ASCII.GetBytes(data))).Replace("-", string.Empty).ToLower();
}

private static string GetHash1()
{
	IntPtr intPtr = CreateFileA("\\\\.\\TeaParty", 1073741824u, 2u, IntPtr.Zero, 3u, 0u, IntPtr.Zero);
	IntPtr intPtr2 = Marshal.AllocHGlobal(32);
	int lpBytesReturned = 0;
	bool flag = DeviceIoControl(intPtr, 2236424u, IntPtr.Zero, 0u, intPtr2, 32u, out lpBytesReturned, IntPtr.Zero);
	byte[] array = new byte[32];
	Marshal.Copy(intPtr2, array, 0, 32);
	Marshal.FreeHGlobal(intPtr2);
	CloseHandle(intPtr);
	return Encoding.ASCII.GetString(array);
}

private static bool CheckDebugging()
{
	if (Environment.ProcessorCount < 2)
	{
		return true;
	}
	if (IsDebuggerPresent() || Debugger.IsAttached)
	{
		return true;
	}
	return false;
}

private static string GetHash2()
{
	if (CheckDebugging())
	{
		Environment.FailFast("Suspecting Anti-Analysis Environment");
	}
	IntPtr intPtr = CreateFileA("\\\\.\\TeaParty", 1073741824u, 2u, IntPtr.Zero, 3u, 0u, IntPtr.Zero);
	IntPtr intPtr2 = Marshal.AllocHGlobal(32);
	int lpBytesReturned = 0;
	bool flag = DeviceIoControl(intPtr, 2236428u, IntPtr.Zero, 0u, intPtr2, 32u, out lpBytesReturned, IntPtr.Zero);
	byte[] array = new byte[32];
	Marshal.Copy(intPtr2, array, 0, 32);
	Marshal.FreeHGlobal(intPtr2);
	CloseHandle(intPtr);
	return Encoding.ASCII.GetString(array);
}

private static string GetFlag(string passcode)
{
	IntPtr intPtr = CreateFileA("\\\\.\\TeaParty", 1073741824u, 2u, IntPtr.Zero, 3u, 0u, IntPtr.Zero);
	IntPtr intPtr2 = Marshal.AllocHGlobal(512);
	int lpBytesReturned = 0;
	IntPtr intPtr3 = Marshal.AllocHGlobal(passcode.Length + 1);
	Marshal.Copy(Encoding.ASCII.GetBytes(passcode + "\0"), 0, intPtr3, passcode.Length + 1);
	bool flag = DeviceIoControl(intPtr, 2236416u, intPtr3, (uint)(passcode.Length + 1), IntPtr.Zero, 0u, out lpBytesReturned, IntPtr.Zero);
	flag = DeviceIoControl(intPtr, 2236420u, IntPtr.Zero, 0u, intPtr2, 512u, out lpBytesReturned, IntPtr.Zero);
	byte[] array = new byte[lpBytesReturned];
	Marshal.Copy(intPtr2, array, 0, lpBytesReturned);
	MessageBox.Show(Encoding.ASCII.GetString(array));
	Marshal.FreeHGlobal(intPtr2);
	Marshal.FreeHGlobal(intPtr3);
	CloseHandle(intPtr);
	return Encoding.ASCII.GetString(array);
}

private static int passcodeLength = 17;
private static List<string> buffers = new List<string>();

private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
	if (nCode >= 0 && wParam == (IntPtr)256)
	{
		int num = Marshal.ReadInt32(lParam);
		for (int i = 0; i < 
             passcodeLength; i++)
		{
			
            buffers[i] += (char)num;
		}
		if (CalculateMd5HexDigest(buffers.Last()) == GetHash1() || CalculateMd5HexDigest(buffers.Last()) == GetHash2())
		{
			UnhookWindowsHookEx(hookId);
			MessageBox.Show("You Got the FLAG!");
			GetFlag(buffers.Last().ToLower());
			Application.Exit();
		}
		buffers.RemoveAt(buffers.Count - 1);
		buffers.Insert(0, "");
	}
	return CallNextHookEx(hookId, nCode, wParam, lParam);
}

public static bool ResourceExists(string resourceName)
{
	return Assembly.GetCallingAssembly().GetManifestResourceNames().Contains(resourceName);
}

public static byte[] ReadResource(string resourceName)
{
	byte[] array = null;
	try
	{
		using Stream stream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
		array = new byte[(int)stream.Length];
		stream.Read(array, 0, (int)stream.Length);
	}
	catch (NullReferenceException ex)
	{
		throw ex;
	}
	return array;
}


private static byte[] GetResource(string resourceName)
{
	resourceName = Assembly.GetCallingAssembly().GetName().Name + "." + resourceName;
	if (ResourceExists(resourceName))
	{
		return ReadResource(resourceName);
	}
	return null;
}

private static byte[] decodeResource(byte[] data)
{
	byte[] array = new byte[data.Length];
	for (int i = 0; i < data.Length; i++)
	{
		array[i] = (byte)(data[i] ^ 0xAAu);
	}
	return array;
}

private static void InstallDriver(string driverName, string driverPath)
{
	IntPtr intPtr = IntPtr.Zero;
	byte[] resource = GetResource(driverName + ".sys");
	if (resource != null)
	{
		resource = decodeResource(resource);
		if (!File.Exists(driverPath))
		{
			if (!Directory.Exists(Path.GetDirectoryName(driverPath)))
			{
				Directory.CreateDirectory(Path.GetDirectoryName(driverPath));
			}
			File.WriteAllBytes(driverPath, resource);
		}
	}
	IntPtr intPtr2 = OpenSCManager(null, null, 983103u);
	if (intPtr2 != IntPtr.Zero)
	{
		intPtr = OpenService(intPtr2, driverName, 983551u);
		if (!(intPtr != IntPtr.Zero))
		{
			intPtr = CreateServiceW(intPtr2, "TeaParty", "AliceInWonderlandTeaParty", 983103u, 1u, 3u, 1u, driverPath, null, IntPtr.Zero, string.Empty, null, string.Empty);
		}
	}
	StartServiceW(intPtr, 0u, IntPtr.Zero);
	CloseServiceHandle(intPtr);
	CloseServiceHandle(intPtr2);
}

private static HookProc hookProc = HookCallback;

private void Form1_Load(object sender, EventArgs e)
{
	InstallDriver("TeaParty", "C:\\Program Files\\AliceInWonderlandTeaParty\\TeaParty.sys");
	if (CheckDebugging())
	{
		Environment.FailFast("Suspecting Anti-Analysis Environment");
	}
	hookId = SetHook(hookProc);
	for (int i = 0; i < passcodeLength; i++)
	{
		buffers.Add("");
	}
}

We also can dump the XOR encrypted TeaParty.sys, although we don’t really need to as we already have extracted the unencrypted version out of the memory dump.

image-20221226215851318

So, we can infer:

  1. On Startup, TeaParty.sys is decoded (XOR with 0xAA) and copied to C:\Program Files\AliceInWonderlandTeaParty\TeaParty.sys
  2. As Anti-Debugging measure, it is checked if a Debugger is present or we run on a machine with a single CPU core (this may be targeting VMs?)
  3. We save the last 17 characters typed in a buffer, the last 16 in another, the last 15 in another… and so on. Why? I don’t really know, only the full one is used.
  4. We check if the MD5 hash of the last input password is the same as the either the result of GetHash1 or GetHash2.
  5. If we pass this check, we retrieve a confirmation dialog and another dialog that outputs the flag:
    • GetFlag runs the IOCtls (Input/Output Control) 2236416 and 2236420 of device driver \\.\TeaParty and returns the result each time. The last result is displayed in a Message Box.

When running TeaParty.exewe are greeted with a GIF image from Disney’s Alice in Wonderland:

image-20221226223154580

So, the interesting bits - Hashes and flag - are hidden in the TeaParty.sys device driver and are queried from the device at runtime.

Drowning In My Own Tears: Retrieve the Flag Without Reverse Engineering the Driver?

Being a bit intimidated by the outlook of having to reverse a windows driver and keeping the hint about rainbow tables in mind, I thought about staying in my comfort zone and just reversing one of the 2 hashes supplied by the GetHash1and GetHash2 functions.

If we dumped the memory of a running and unlocked TeaParty.exe we should see the hash in memory, right? It’s quite likely these functions not only generate a hex digest of the MD5 checksums, but keep it in memory after the flag is displayed.

Let’s enumerate all strings in our process dump that could be valid MD5 hashes:

  • 32 characters long
  • Only contains lowercase and characters and digits (Note: uppercase characters would be valid hashes but as we compare against a ToLower()ed string we can assume the target hashes are also lowercase).
❯ strings -n 32 pid.4484.dmp | grep --color=never -E "^[0-9a-f]{32}$" | sort -u
142061edd8b57809b8ff617c4556c787
286954fbe19a4de865789fdf75cca5ea
33333333333333333333333333330000
33333333333333333333333333333333
6675b1f9603ec9e014cdcf6c3be6c860
85df50e08a81b2e0942b3b323435278f
9b1f18bc9e5569fb166a22ca6eb337a4
acd76752011afb91f5ca6f707066b350
f5c18c5029e778b383c1ec34f156cf6b

Let’s feed those hashes into crackstation.net And we are lucky indeed:

image-20221227032857926

This is in line with the challenge lore and should give us the flag! Let’s start TeaParty.exe and type whothefuckisalice!

And then… …nothing happens?!

This can’t be right, this looks so much like the correct solution it isn’t even funny. So, maybe those Anti-Debugging measures are interfering somehow, even if we have more than a single core available and no debugger attached? Let’s modify the .Net assembly to not waste time with key scanning and hash comparison.

dnSpy is ILSpy on steroids providing the reversing capabilities, but also:

  • Allows modification of the reversed code and rewriting the assembly (Isn’t that cool!?)
  • Allows attachment of a debugger to .Net executables, providing Visual Studio like debugging experience
  • Looks sleek and features a dark mode color scheme
  • Probably has a lot of more advanced functionality I don’t event basically understand

This comes with the drawbacks, that it doesn’t have a Linux native version available and isn’t actively developed and maintained anymore since December 2020, which keeps me from using it instead of ILSpy by default.

image-20221227034440569

As you see, the Form1_Load function is already rewritten to concentrate on the absolutely necessary

private void Form1_Load(object sender, EventArgs e)
{
	TeaParty.InstallDriver("TeaParty", "C:\\Program Files\\AliceInWonderlandTeaParty\\TeaParty.sys");
	TeaParty.GetFlag("whothefuckisalice");
}

And that changes exactly nothing. Still no flag.

Well, time to leave the comfort zone and get our hands dirty on the TeaParty.sys device driver.

We’re All Mad Here: What’s a device driver anyway?

To analyze the TeaParty.sys in Ghidra, it is really helpful to import the ntddk64 symbols, as it will allow to use the needed Windows API types when recreating structs and types. I’ll spare you the detailed reversing process and just describe the recreated code path.

We start at the entry function, which receives a pointer to a DRIVER_OBJECT struct and forwards it to another method I named driverEntry.

void entry(PDRIVER_OBJECT driverObject)

{
  FUN_fffff80944e0502c();
  driverEntry(driverObject);
  return;
}

The driverEntry function initializes an IO Device at \Device\TeaParty and \DosDevice\TeaParty (I think for legacy reasons). It then initializes the IRP Function Table, which maps functions to IRP operations. These are mostly defined to an almost empty NOP function, supposedly out of necessity. But the IRP function for Device Control is defined to a rich function used to dispatch the IO Controls provided by the calls in our .Net program. There is also a short unload routine which does pretty much the expected, unloading the defined device.

undefined8 driverEntry(PDRIVER_OBJECT driverObject)

{
  _UNICODE_STRING deviceName;
  _UNICODE_STRING deviceAlias;
  
  DbgPrint("Hello World!\n");
  RtlInitUnicodeString(&deviceName,L"\\Device\\TeaParty");
  RtlInitUnicodeString(&deviceAlias,L"\\DosDevices\\TeaParty");
  IoCreateDevice(driverObject,0,&deviceName,FILE_DEVICE_UNKNOWN,0,'\0',&DEVICE_OBJECT);
  IoCreateSymbolicLink(&deviceAlias,&deviceName);
                    /* IRP_MJ_CREATE */
  driverObject->MajorFunction[0] = driverNOP;
                    /* IRP_MJ_CLOSE */
  driverObject->MajorFunction[IRP_MJ_CLOSE] = driverNOP;
                    /* IRP_MJ_READ */
  driverObject->MajorFunction[IRP_MJ_READ] = driverNOP;
                    /* IRP_MJ_WRITE */
  driverObject->MajorFunction[IRP_MJ_WRITE] = driverNOP;
                    /* IRP_MJ_DEVICE_CONTROL */
  driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driverDispatch;
  driverObject->DriverUnload = driverUnload;
  return 0;
}

The driverDispatch function is where the magic happens. Let’s have a look and analyze it.

undefined (*) [16] decrypt_aes(PUCHAR param_1,ULONG param_2)

{
  NTSTATUS NVar1;
  undefined (*pbKeyObject) [16];
  undefined (*pbIV) [16];
  undefined (*pbOutput) [16];
  undefined (*pauVar2) [16];
  undefined (*pauVar3) [16];
  uint local_res18 [2];
  uint local_res20 [2];
  uint local_58;
  ULONG local_54;
  BCRYPT_HANDLE local_50;
  BCRYPT_KEY_HANDLE local_48 [2];
  
  pbIV = (undefined (*) [16])0x0;
  local_50 = (BCRYPT_HANDLE)0x0;
  local_48[0] = (BCRYPT_KEY_HANDLE)0x0;
  local_res20[0] = 0;
  local_54 = 0;
  local_58 = 0;
  local_res18[0] = 0;
  pauVar3 = (undefined (*) [16])0x0;
  pauVar2 = (undefined (*) [16])0x0;
  NVar1 = BCryptOpenAlgorithmProvider(&local_50,L"AES",(LPCWSTR)0x0,0);
  pbOutput = pbIV;
  pbKeyObject = pbIV;
  if (-1 < NVar1) {
    NVar1 = BCryptGetProperty(local_50,L"ObjectLength",(PUCHAR)&local_58,8,&local_54,0);
    pbIV = pauVar2;
    pbOutput = pauVar3;
    pbKeyObject = (undefined (*) [16])0x0;
    if (-1 < NVar1) {
      pbKeyObject = (undefined (*) [16])
                    ExAllocatePoolWithTag(PagedPool,(ulonglong)local_58,0xaabbccdd);
      if (pbKeyObject != (undefined (*) [16])0x0) {
        NVar1 = BCryptGetProperty(local_50,L"BlockLength",(PUCHAR)local_res18,8,&local_54,0);
        if ((-1 < NVar1) && (local_res18[0] < 0x11)) {
          pbIV = (undefined (*) [16])
                 ExAllocatePoolWithTag(PagedPool,(ulonglong)local_res18[0],0xaabbccdd);
          if (pbIV != (undefined (*) [16])0x0) {
            memmove(pbIV,&AES_IV,(ulonglong)local_res18[0]);
            NVar1 = BCryptSetProperty(local_50,L"ChainingMode",(PUCHAR)L"ChainingModeCBC",0x20,0);
            if (-1 < NVar1) {
              NVar1 = BCryptGenerateSymmetricKey
                                (local_50,local_48,(PUCHAR)pbKeyObject,local_58,&AES_KEY,0x20,0);
              if (-1 < NVar1) {
                NVar1 = BCryptDecrypt(local_48[0],param_1,param_2,(void *)0x0,(PUCHAR)pbIV,
                                      local_res18[0],(PUCHAR)0x0,0,local_res20,1);
                if (-1 < NVar1) {
                  pbOutput = (undefined (*) [16])
                             ExAllocatePoolWithTag(PagedPool,(ulonglong)local_res20[0],0xaabbccdd);
                  FUN_fffff80944e01c00(pbOutput,0,(ulonglong)local_res20[0]);
                  if (pbOutput != (undefined (*) [16])0x0) {
                    BCryptDecrypt(local_48[0],param_1,param_2,(void *)0x0,(PUCHAR)pbIV,
                                  local_res18[0],(PUCHAR)pbOutput,local_res20[0],local_res20,1);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  if (local_50 != (BCRYPT_ALG_HANDLE)0x0) {
    BCryptCloseAlgorithmProvider(local_50,0);
  }
  if (local_48[0] != (BCRYPT_KEY_HANDLE)0x0) {
    BCryptDestroyKey(local_48[0]);
  }
  if (pbKeyObject != (undefined (*) [16])0x0) {
    ExFreePoolWithTag(pbKeyObject,0xaabbccdd);
  }
  if (pbIV != (undefined (*) [16])0x0) {
    ExFreePoolWithTag(pbIV,0xaabbccdd);
  }
  return pbOutput;
}

NTSTATUS driverDispatch(PDEVICE_OBJECT deviceObject,PIRP irp)
{
  _IO_STACK_LOCATION *p_Var1;
  undefined4 *puVar2;
  undefined4 uVar3;
  undefined4 uVar4;
  undefined4 uVar5;
  NTSTATUS NVar6;
  uint newpos;
  size_t _Size;
  longlong bufpos;
  undefined auStack_98 [32];
  char regkey_val [4];
  undefined local_70 [8];
  undefined4 uStack_68;
  undefined4 uStack_64;
  undefined local_60 [8];
  undefined4 uStack_58;
  undefined4 uStack_54;
  undefined buf [8];
  undefined4 uStack_48;
  undefined4 uStack_44;
  undefined local_40 [8];
  undefined4 uStack_38;
  undefined4 uStack_34;
  char key [4];
  ulonglong local_10;
  ULONG ioctl;
  PCSTR key_in;
  uint keypos;
  undefined (*pResult) [16];
  
  local_10 = DAT_fffff80944e03000 ^ (ulonglong)auStack_98;
  p_Var1 = (irp->Tail).Overlay.field3_0x30.field1_0x10.CurrentStackLocation;
  regkey_val = (char  [4])0x0;
  _buf = ZEXT816(0);
  _local_40 = ZEXT816(0);
  _local_70 = ZEXT816(0);
  _local_60 = ZEXT816(0);
                    /* This should retrieve 0xa11ceb0b */
  read_registry_key(L"\\Registry\\Machine\\SOFTWARE\\AliceInWonderlandTeaParty",L"CareForTea",
                    regkey_val);
  pResult = DECRYPTED_FLAG;
  ioctl = (p_Var1->Parameters).QueryDirectory.FileIndex;
  key = regkey_val;
  if (ioctl == IOCTL_GETFLAG1) {
    key_in = *(PCSTR *)((longlong)&irp->AssociatedIrp + 4);
    DbgPrint(key_in);
    _Size = 0xffffffffffffffff;
    _AES_KEY = ZEXT816(0);
    _DAT_fffff80944e03068 = ZEXT816(0);
    do {
      _Size = _Size + 1;
    } while (key_in[_Size] != '\0');
    memmove(&AES_KEY,key_in,_Size);
    DECRYPTED_FLAG = decrypt_aes(&ENCRYPTED_FLAG,0x30);
    DbgPrint((PCSTR)DECRYPTED_FLAG);
  }
  else if (ioctl == IOCTL_GETFLAG2) {
    puVar2 = *(undefined4 **)((longlong)&(irp->AssociatedIrp).MasterIrp + 4);
    (p_Var1->Parameters).Read.Key = 8;
    uVar3 = *(undefined4 *)((longlong)*pResult + 4);
    uVar4 = *(undefined4 *)((longlong)*pResult + 8);
    uVar5 = *(undefined4 *)((longlong)*pResult + 0xc);
    *puVar2 = *(undefined4 *)*pResult;
    puVar2[1] = uVar3;
    puVar2[2] = uVar4;
    puVar2[3] = uVar5;
    uVar3 = *(undefined4 *)(pResult[1] + 4);
    uVar4 = *(undefined4 *)(pResult[1] + 8);
    uVar5 = *(undefined4 *)(pResult[1] + 0xc);
    puVar2[4] = *(undefined4 *)pResult[1];
    puVar2[5] = uVar3;
    puVar2[6] = uVar4;
    puVar2[7] = uVar5;
    uVar3 = *(undefined4 *)(pResult[2] + 4);
    uVar4 = *(undefined4 *)(pResult[2] + 8);
    uVar5 = *(undefined4 *)(pResult[2] + 0xc);
    puVar2[8] = *(undefined4 *)pResult[2];
    puVar2[9] = uVar3;
    puVar2[10] = uVar4;
    puVar2[0xb] = uVar5;
    ExFreePoolWithTag(pResult,0xaabbccdd);
  }
  else {
    if (ioctl == IOCTL_GETHASH1) {
      pResult = *(undefined (**) [16])((longlong)&irp->AssociatedIrp + 4);
      (p_Var1->Parameters).Read.Key = 0x20;
      bufpos = 0;
      keypos = 0;
      do {
        newpos = keypos + 1;
                    /* "&3" == "%4" */
        buf[bufpos] = key[keypos & 3] ^ (&ENCRYPTED_HASH1)[bufpos];
        bufpos = bufpos + 1;
        keypos = newpos;
      } while ((int)newpos < 0x20);
      local_70._0_4_ = buf._0_4_;
      local_70._4_4_ = buf._4_4_;
      uStack_68 = uStack_48;
      uStack_64 = uStack_44;
      local_60._0_4_ = local_40._0_4_;
      local_60._4_4_ = local_40._4_4_;
      uStack_58 = uStack_38;
      uStack_54 = uStack_34;
    }
    else {
      if (ioctl != IOCTL_GETHASH2) goto LAB_fffff80944e01469;
      pResult = *(undefined (**) [16])((longlong)&irp->AssociatedIrp + 4);
      (p_Var1->Parameters).Read.Key = 0x20;
      bufpos = 0;
      keypos = 0;
      do {
        newpos = keypos + 1;
        local_70[bufpos] = key[keypos & 3] ^ (&ENCRYPTED_HASH2)[bufpos];
        bufpos = bufpos + 1;
        keypos = newpos;
      } while ((int)newpos < 0x20);
    }
    *pResult = CONCAT412(uStack_64,CONCAT48(uStack_68,CONCAT44(local_70._4_4_,local_70._0_4_)));
    pResult[1] = CONCAT412(uStack_54,CONCAT48(uStack_58,CONCAT44(local_60._4_4_,local_60._0_4_)));
  }
LAB_fffff80944e01469:
  (irp->IoStatus).field0_0x0.Status = 0;
  (irp->IoStatus).Information = 0x30;
  IofCompleteRequest(irp,'\0');
  NVar6 = FUN_fffff80944e017a0(local_10 ^ (ulonglong)auStack_98);
  return NVar6;
}

The driverDispatch function basically does the following:

  1. Read a registry value at HKEY_LOCAL_MACHINE\SOFTWARE\AliceInWonderlandTeaParty\CareForTea as a key
  2. Parse IOCtl value from IRP and switch code path accordingly
    • IOCTL_GETFLAG1: 2236416 (0x222000)
      1. Read the encrypted flag 0x74F814897D5AC9C05301FD9922C3AC84FDFB4312FF39AB49EE39E580C1F5160C
      2. Read the key from user provided memory
      3. Looking at the decrypt_aes function (omitted for brevity) we can look at the code and used strings to infer the used initialization vector and algorithm: It’s AES-CBC with an IV of concatenated hex values from 0x00 to 0x0F. The key needs to be provided as 32 byte string.
    • IOCTL_GETFLAG2: 2236420 (0x222004)
      1. Write the in IOCTL_GETFLAG1 calculated value to the IRP struct
    • IOCTL_GETHASH1 : 2236424 (0x222008)
      1. Read the encrypted hash 0x39D32A983EDF7AC36EDA25C03F8F79993DDE2B99328D78C73CDE7FC26ADE79C0
      2. Do a byte wise XOR operation on this encrypted hash with the cyclic key retrieved from the registry.
      3. Return the result via IRP struct
    • IOCTL_GETHASH2 : 2236428 (0x22200c)
      1. Read the encrypted hash 0x32892DC73AD37EC2328E29943DD27AC33ADD2AC039D97FC03D8E7E9238DC7D95
      2. Do a byte wise XOR operation on this encrypted hash with the cyclic key retrieved from the registry.
      3. Return the result via IRP struct
  3. Return the result buffer via IRP struct

So, how do we get the value of the registry key HKEY_LOCAL_MACHINE\SOFTWARE\AliceInWonderlandTeaParty\CareForTea? Volatility allows us to search the registry keys opened during memory image dump:

❯ vol -f Windows10x64_AliceInWonderland-b4365e16.vmem windows.registry.printkey --key "AliceInWonderlandTeaParty" --recurse
Volatility 3 Framework 2.0.1
Progress:  100.00		PDB scanning finished                        
Last Write Time	Hive Offset	Type	Key	Name	Data	Volatile

-	0xde88d0023000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d003d000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d0078000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d77e2000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d6119000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d7dc6000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d7d21000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d7d06000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d7bae000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d716f000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d6a54000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d692b000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d6335000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d62ff000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d6177000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d606c000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d5ff3000	Key	?\AliceInWonderlandTeaParty	-		-
-	0xde88d0c29000	Key	?\AliceInWonderlandTeaParty	-		-
2022-09-02 22:32:25.000000 	0xde88d0ae4000	REG_DWORD	\SystemRoot\System32\Config\SOFTWARE\AliceInWonderlandTeaParty	CareForTea	2703026955	False

It is the DWORD (4 byte) 2703026955, which looks unsuspicious until you convert it to hex: 0xa11ceb0b

We can verify our suspected MD5 hash 286954fbe19a4de865789fdf75cca5ea is the result of IOCTL_GETHASH1 and obtain 9b1f18bc9e5569fb166a22ca6eb337a4 as result for IOCTL_GETHASH2 with CyberChef. If we were really confident about our guess we could even have reversed the encryption key instead of getting it from the memory dump!

Although not necessary we tried to reverse the hash 9b1f18bc9e5569fb166a22ca6eb337a4. It wasn’t available in any rainbow table we found, but we found the corresponding cleartext WHOTHEFUCKISALICE by chance. Looks like the passcode is meant to be case insensitive but case consistent.

Now we have everything we need to get the flag and write a little python script:

from pwn import *

h1 = unhex('39D32A983EDF7AC36EDA25C03F8F79993DDE2B99328D78C73CDE7FC26ADE79C0')
print(xor(h1, p32(0xA11CEB0B)))
# 286954fbe19a4de865789fdf75cca5ea

h2 = unhex('32892DC73AD37EC2328E29943DD27AC33ADD2AC039D97FC03D8E7E9238DC7D95')
print(xor(h2, p32(0xA11CEB0B)))
# 9b1f18bc9e5569fb166a22ca6eb337a4

passcode = b'whothefuckisalice'

key = passcode.ljust(32, b'\x00')
ciphertext = unhex('74F814897D5AC9C05301FD9922C3AC84FDFB4312FF39AB49EE39E580C1F5160C')
iv = unhex('00102030405060708090A0B0C0D0E0F')

from Crypto.Cipher import AES
print(key)

aes = AES.new(key, AES.MODE_CBC, iv)
print(aes.decrypt(ciphertext))
# INTENT{0ff_w1th_7h31r_H34ds}

Off With Their Heads: Disable Windows Security Features

Now, we already got the flag through reverse engineering the device driver… but why didn’t it work like advertised? Why didn’t we get the flag when running the executable?

Turns out, there is a thing calles Driver Signature Enforcement that prevents loading device not signed by Microsoft. This may indeed be a smart move generally, but as our TeaParty.sys is not signed, we have to disable it.

The failed signature and integrity checks are written to the Windows event log and can be viewed with the Event Viewer in the Audit log category.

In a Command Prompt (cmd.exe), started with System privileges (Right click, run as administrator), execute:

bcdedit -set TESTSIGNING OFF

and restart Windows. After a reboot, there should be the following Test Mode watermark visible.

image-20221227023833349

Make sure Virus & Threat Protection is still off and run TeaParty.exe.

Et Voila: When we type whothefuckisalice, the flag appears!

cimage-20221227024123467

And we even could get it without fully reversing TeaParty.sys, inferring the password from the process memory and modifying the .Net assembly like we tried above. Neat!

Leaving Wonderland: Summary

I really liked this challenge as it needed quite a broad skill set to obtain the flag and introduced a couple of techniques and tools one could use:

I really enjoyed this challenge and was really elated to be able to solve this challenge just at the last our of INTENT CTF - of course with generous help of my team mates :)

Kudos to @rotemsalinas for this great challenge!