INTENT CTF 2022 - Forensic Tea Party
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.
- File: Windows10x64_AliceInWonderland-b4365e16.vmem (~1GB)
- Hint: md5 hashes can be cracked by using rainbow tables https://crackstation.net/
- Author: @rotemsalinas
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 dat
s to their original filenames and fire up ILSpy and load TeaParty.exe
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.
So, we can infer:
- On Startup,
TeaParty.sys
is decoded (XOR with 0xAA) and copied toC:\Program Files\AliceInWonderlandTeaParty\TeaParty.sys
- 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?)
- 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.
- We check if the MD5 hash of the last input password is the same as the either the result of
GetHash1
orGetHash2
.GetHash1
runs the IOCtl (Input/Output Control) 2236424 of device driver\\.\TeaParty
and returns the resultGetHash2
runs the IOCtl (Input/Output Control) 2236428 of device driver\\.\TeaParty
and returns the result
- 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.exe
we are greeted with a GIF image from Disney’s Alice in Wonderland:
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 GetHash1
and 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:
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.
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:
- Read a registry value at HKEY_LOCAL_MACHINE\SOFTWARE\AliceInWonderlandTeaParty\CareForTea as a key
- Parse IOCtl value from IRP and switch code path accordingly
IOCTL_GETFLAG1
: 2236416 (0x222000)- Read the encrypted flag 0x74F814897D5AC9C05301FD9922C3AC84FDFB4312FF39AB49EE39E580C1F5160C
- Read the key from user provided memory
- 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)- Write the in
IOCTL_GETFLAG1
calculated value to the IRP struct
- Write the in
IOCTL_GETHASH1
: 2236424 (0x222008)- Read the encrypted hash 0x39D32A983EDF7AC36EDA25C03F8F79993DDE2B99328D78C73CDE7FC26ADE79C0
- Do a byte wise XOR operation on this encrypted hash with the cyclic key retrieved from the registry.
- Return the result via IRP struct
IOCTL_GETHASH2
: 2236428 (0x22200c)- Read the encrypted hash 0x32892DC73AD37EC2328E29943DD27AC33ADD2AC039D97FC03D8E7E9238DC7D95
- Do a byte wise XOR operation on this encrypted hash with the cyclic key retrieved from the registry.
- Return the result via IRP struct
- 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.
Make sure Virus & Threat Protection is still off and run TeaParty.exe.
Et Voila: When we type whothefuckisalice, the flag appears!
c
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:
- Memory Dump Analysis (volatility)
- .Net Assembly Analysis and Modification (DNSpy)
- Hash cracking using rainbow tables (crackstation.net)
- Windows Driver Model API I/O Control
- x64 Windows PE Binary Reversing (Ghidra)
- Windows Driver Signature Enforcement
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!