For this challenge we are provided with two files, “challenge.exe” and “X-MAS_kernel_crackme.sys”.
I first looked at challenge.exe, but nothing particularly interesting is happening in there. It simply creates a service which is used to load the driver, asks for a license key, and then communicates with the driver making it do most of the work.
Looking at the image above it is evident that challenge.exe reads in a license key (I suppose this will be the flag), then sends it off to the driver using WriteFile, and we can guess that after the driver has done its work, challenge.exe uses ReadFile to read in the modified buffer, which is then used in a comparison with the enc_flag string.
And so my journey with the kernel driver begun…
Drivers communicate using I/O Request Packets (IRP) in order to perform certain functionality (you can read more about it here). Since we are using WriteFile and ReadFile, we will need to look what the driver does when these requests are made. After opening the driver in IDA, I quickly found the major function table:
The table simply tells the driver what to do depending on the request. When we call WriteFile into the driver from a user-mode application, we will perform an IRP_MJ_WRITE request, and when we call ReadFile, we will be performing an IRP_MJ_READ request. The values of those requests in the table are 0x4 and 0x3 respectively, hence we will need to focus on what happens when the following functions are used:
DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)sub_1400015C0;
DriverObject->MajorFunction[4] = (PDRIVER_DISPATCH)sub_140001720;
I first considered what happens when an IRP_MJ_WRITE (0x4) request is made. After looking a bit around, I figured the driver simply takes the license key as input, and does something to it… I found two interesting functions, sub_1400024CC and sub_140001D68.
I poked a bit around sub_1400024CC and figured that it was doing something to a predefined buffer unk_14000B2F0, and set the modified values in unk_14000C540 (which I found to be referenced all over different function calls in the next function).
I then moved to the next function, and taking a look at sub_140001D68 gave me a big scare:
It is a massive jump table and was a bit spooky. So I started to look into the particular functions that the jump table referenced and found out that it repeats certain operations in multiple rounds, all of which are done on 16 bytes of the license key at a time. My dumb brain didn't recognize this pattern and so I attempted at writing a script which would reverse the operations, but I got slightly stuck on this one:
After hours of getting nowhere, I received help from my friend Mev, who made me realize this was AES in ECB mode. And so the weird function we saw at first (sub_1400024CC) which set the unk_14000C540 buffer is simply the AES key expansion algorithm. Now, because that buffer is set using a predefined value stored in the driver, we already have the key!
Taking a quick look at the IRP_MJ_READ (0x3) request, it was evident that the driver simply writes back the encrypted flag to challenge.exe running in user-mode. And like I suspected, it is compared to a weird sequence of bytes which I assumed to be the encrypted flag.
So I pulled out the key from unk_14000B2F0 in the kernel driver, and the flag from enc_flag in the user-mode application:
key: KaPdSgVkXp2s5v8y
enc_flag: F2 63 69 4F F5 CB FB F4 98 19 C2 FD 39 ED F9 CC 5D EC D9 EC 66 A5 30 D1 82 46 7D A9 FD 5B 3C BF 1C 3D BD 70 26 00 6A 43 C4 0A 47 4C B7 56 2D 50
I then went straight to CyberChef, used AES Decrypt in ECB mode using the above values, and got the flag: X-MAS{060b192f0063cc9ab6361c2329687506f50321d8}.
Lessons learned: Think twice before starting to reverse a massive jump table, there is probably something you are missing.
All in all, I guess I'll have an easier time recognizing AES in the future 😩