This is the last RE challenge of Flare On CTF #6. In the description, they gave us 2 files:

  • Full memory dump
  • Pcap file

and a message says something about persistent malware.

Currently, there should be many write-up of this one over the Internet. Original

For me, it is very practical and a good illustration of RE skill applied in the industry - great finish for the contest this year.

In summary about this challenge:

  • There are 2 loaded drivers (A + B) inside the kernel dump

  • One of them (named A) was loaded manually by hacker

  • During DriverEntry routine, A injects code (named X) in PID 876 - svchost.exe and spawns a thread at EntryPoint of X

  • Meanwhile, A registers many handlers to dispatch routine IRP_MJ_DEVICE_CONTROL (ioctl - can be called from user-mode)
    • Load new driver
    • Inject code into user process
    • Lookup injected processes by ID and run code
  • X listens on port 4444, and waits for commands from outside, and triggers above handlers of A

  • Driver B should be loaded from A in kernel space (through X - usermode)

  • B will register callouts in Filter Engine, which modifies (XOR encrypting with 8 bytes key) the network traffic of specific ports. Check out Windows Filtering Platform and here are the keys for each port:
Key decrypt 4444 (115c): 5D F3 4A 48 48 48 DD 23
Key decrypt 7777 (1e61): 4A 1F 4B 1C B0 D8 25 C7
Key decrypt 6666 (1A0a): D5 69 94 FA 25 EC DF DA
Key decrypt 8888 (22b8): F7 8F 78 48 47 1A 44 9C
  • Following the link-list of injected process in kernel dump, we can recover the code

Tip: driver A already removed PEHeader while doing injection, but you can try to search for them (or at least could recover the header) in the memdump. These code are received via network buffer, so they might be hidden somewhere in kernel pool or injected X space.

>>> Utils
PID 876 - svchost.exe - n.dll
-> network connection, help sending data to specific port

PID 1108 - dwm.exe - c.dll
-> encrypt data with RC4, username is key (include NULL byte)

>>>> Real evil
PID 2508 - procexp64.exe - f.dll
-> file utilities (find, read, delete) -> encrypted result -> port 6666

PID 1124 - explorer.exe - s.dll
-> screenshot -> bitmap -> port 7777

PID 1124 - explorer.exe - k.dll
-> keylogger -> encrypted data -> port 8888
  • Decrypt pcap data of these ports and combine with information extracted from full dump (file system / partial screenshot / procdump /… using Volatility), we can see that flag is in deleted KeePass database (can be recovered from pcap) with master password like this th1sisth33nd111 (also from pcap)

/assets/images/2019-09-28-flareon/session_1.WinSta0.Default.png


So is it done? No, master key is wrong!

To be honest, that is my progress so far using traditional RE methods. Shame on me, I didn’t check the keylogger code carefully enough to figure out what was missing in the “extracted” master key. After struggling with hashcat and other password recovery for few days (ofc they are failed so hard because I picked wrong charset …), I decided to give up and find another way: what if we could recover the KeePass passwords (flag) without the master key?

Searching around Internet, I found some ways to do evil stuff with KeePass 2.0:

https://github.com/denandz/KeeFarce

  • Assume KeePass process already open/unlock database
  • Inject code into KeePass process
  • Trigger ‘Export’ method
  • Get passwords

https://github.com/HarmJ0y/KeeThief

  • Assume KeePass process already open/unlock database
  • Inject code into KeePass process
  • Search for encrypted passwords in heap
  • Decrypt them

Looking at the last decrypted screenshot before kernel crashed:

/assets/images/2019-09-28-flareon/192.168.001.244.01637-192.168.001.243.07777.png

KeePass application is still running and unlocked by master key already. So maybe we could do something simillar? Ofc, This is not a live debugging session, therefore cannot inject anything to anything.

Digging in the source code of KeePass 1.37 (same version in full dump), we see that after decrypting database with right master key, password will be stored crypted in process memory. When we double click on Password column, password will be decrypted (somehow?) and cloned to clipboard for around 10 seconds.

You can try to approach by recompile it and but breakpoint on each steps to find out which method they use to store data in memory. We was too shine to do it:

  • Run KeePass 1.37 and create sample records.
  • Search for the decrypted password during clipboard time (10s)
  • See that decrypted password is always written at the same offset
  • Attach a debugger to that process, and put a hardware-write breakpoint there
  • Do backtrace and look for the signatures of each function (like WinAPI - SetClipboardData, … Const strings - "Field copied to clipboard.", …)
  • Check reference from these signatures and find out the algorithm in source code

\KeePass-1.37-Src\WinGUI\Util\WinUtil.cpp

/assets/images/2019-09-28-flareon/code1.PNG

\KeePass-1.37-Src\WinGUI\PwSafeDlg.cpp

/assets/images/2019-09-28-flareon/code2.PNG

\KeePass-1.37-Src\KeePassLibCpp\PwManager.cpp

/assets/images/2019-09-28-flareon/code3.PNG

\KeePassLibCpp\Crypto\MemoryProtectionEx.cpp

/assets/images/2019-09-28-flareon/code4.PNG

So in the end, KeePass uses 2 Windows API for encrypt/decrypt blob data (belong to DPAPI), which are

DPAPI - Data Protection Application Programming Interface is a cryptographic function provided by Windows. It gives developers a simple method to encrypt/decrypt an arbitrary block of data using a symmetric key, which is uniquely derived from user’s Windows logon credentials/session/… There are lots more than just 2 API above, like CryptProtectData, CryptUnprotectData. DPAPI is used in various part of Windows, and also browsers as well (for password storage).

That’s a summary of it, for more detail, lets check out from the masters:

Follow CryptProtectMemory and CryptUnprotectMemory from Crypt32.dll (from the documents),

/assets/images/2019-09-28-flareon/re1_crypt32.PNG

Lead to SystemFunction040 of advapi321.dll –> cryptbase.dll

/assets/images/2019-09-28-flareon/re2_cryptbase.PNG

It calls NtDeviceIoControllFile to \\Device\\KsecDD - driver ksecdd.sys. The control code is 0x39000E - because KeePass uses CRYPTPROTECTMEMORY_SAME_PROCESS flag

/assets/images/2019-09-28-flareon/re3_cryptbase.PNG

In the IO dispatcher of ksecdd.sys, turn out it uses a registered extension to handle other IO code gKsecpDeviceControlExtension via 0x390038 code

/assets/images/2019-09-28-flareon/re4_ksecdd.PNG

We can check the kernel dump to continue digging. Our next target is cng!CngDeviceControl -> cng.sys

kd> dp ksecdd!gKsecpDeviceControlExtension
fffff880`01210760  fffff880`01068148 fffff880`01067ff8
fffff880`01210770  fffff880`01068030 00000000`00000083
fffff880`01210780  fffff880`0120dfe0 00000000`00000000
fffff880`01210790  00000000`00000000 00000000`00000000
fffff880`012107a0  fffff880`015991a0 00000000`00000001
fffff880`012107b0  00000001`00000101 00000000`00000000
fffff880`012107c0  00000000`00000000 00000000`00000000
fffff880`012107d0  00000000`00000000 00000000`00000000
kd> dp fffff880`01068148
fffff880`01068148  fffff880`0100ad10 fffff880`0102d160
fffff880`01068158  0000008c`80140001 00000014`0000009c
fffff880`01068168  001c0002`00000030 00140011`00000001
fffff880`01068178  00000101`00000001 00001000`10000000
fffff880`01068188  00000004`005c0002 001201bf`00140000
fffff880`01068198  01000000`00000101 00140000`00000000
fffff880`010681a8  00000101`001f01ff 00000012`05000000
fffff880`010681b8  001f01ff`00180000 05000000`00000201
kd> u fffff880`0100ad10
cng!CngDeviceControl:
fffff880`0100ad10 48895c2410      mov     qword ptr [rsp+10h],rbx
fffff880`0100ad15 48896c2418      mov     qword ptr [rsp+18h],rbp
fffff880`0100ad1a 56              push    rsi
fffff880`0100ad1b 57              push    rdi
fffff880`0100ad1c 4154            push    r12
fffff880`0100ad1e 4155            push    r13
fffff880`0100ad20 4156            push    r14
fffff880`0100ad22 4881ecd0000000  sub     rsp,0D0h

/assets/images/2019-09-28-flareon/re5_cng.PNG

CngEncryptMemory will GenerateAESKey and use AES128-CBC-Mode with IV == RandomSalt to encrypt our blob data

/assets/images/2019-09-28-flareon/re6_CngEncryptMemory.PNG

IV == RandomSalt is known and unchanged

kd> db cng!RandomSalt L10
fffff880`01068570  35 3a d5 b5 19 db b2 64-ba e3 85 9e d7 b1 02 e8  5:.....d........

GenerateAESKey is SHA1 of random 24 bytes and specific 12 bytes key of each user process (Cookie - by ZwQueryInformationProcess and CreateTime - by PsGetProcessCreateTimeQuadPart)

/assets/images/2019-09-28-flareon/re7_GenerateAESKey.PNG

/assets/images/2019-09-28-flareon/re8_SHAHash.PNG

Basicly, the SHAContext struct of first 24 random bytes is also known and unchange

kd> db cng!g_ShaHash L60
fffff880`01068510  1c 81 87 7b 81 73 be 1b-99 da 11 35 10 43 4e da  ...{.s.....5.CN.
fffff880`01068520  97 9e b0 0e 37 cd 31 2b-00 00 00 00 00 00 00 00  ....7.1+........
fffff880`01068530  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff880`01068540  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff880`01068550  01 23 45 67 89 ab cd ef-fe dc ba 98 76 54 32 10  .#Eg........vT2.
fffff880`01068560  f0 e1 d2 c3 00 00 00 00-18 00 00 00 00 00 00 00  ................

Also, we could get the information of user process by looking for EPROCESS struct of them.

kd> !process 0 0 KeePass.exe
PROCESS fffffa80026070b0
    SessionId: 1  Cid: 0a58    Peb: 7efdf000  ParentCid: 0464
    DirBase: 070ab000  ObjectTable: fffff8a003067a20  HandleCount: 129.
    Image: KeePass.exe

kd> dt nt!_EPROCESS fffffa80026070b0
   ...
   +0x168 CreateTime       : _LARGE_INTEGER 0x01d5493f`c578c885
   ...
   +0x278 Cookie           : 0x14c044f5
   ...

At this point, I think you get the idea, last thing we need to finalize everything is our ciphertext - encrypted flag. Follow the _PW_ENTRY structure in KeePass source code for the glory :)

/assets/images/2019-09-28-flareon/re9_entry.PNG

kd> .process fffffa80026070b0
Implicit process is now fffffa80`026070b0
kd> s -a 0 L10000000 "The flag"
00000000`00fb11b0  54 68 65 20 66 6c 61 67-00 00 00 00 10 ee 21 00  The flag......!.
kd> s -d 0 L1000000 00fb11b0
00000000`0021efbc  00fb11b0 334b4745 80000000 000000a2  ....EGK3........
00000000`00f100c0  00fb11b0 00000023 00f1bea8 00f65f40  ....#.......@_..
00000000`00f16824  00fb11b0 335157f8 8c000000 20746f4e  .....WQ3....Not 
00000000`00f17ea4  00fb11b0 335154c8 80000000 000001ec  .....TQ3........
00000000`00f1ab0c  00fb11b0 0021eeb0 00fb1180 0021ed70  ......!.....p.!.
00000000`00f1f4c4  00fb11b0 335146a4 80000000 014c00df  .....FQ3......L.
00000000`00f2121c  00fb11b0 0021ee60 00fb10f0 0021ee38  ....`.!.....8.!.
00000000`00f21234  00fb11b0 0021ecd0 00fb1180 33513a35  ......!.....5:Q3
00000000`00f2394c  00fb11b0 00f60074 0021efa0 00000001  ....t.....!.....
00000000`00f5f4ec  00fb11b0 0021ecd0 00fb1180 0021ed20  ......!..... .!.
00000000`00f66124  00fb11b0 33827888 8f001c60 00320000  .....x.3`.....2.
00000000`00f66294  00fb11b0 338278fe 80000000 0021004a  .....x.3....J.!.
00000000`00f76c3c  00fb11b0 338258c5 800011f8 00f70044  .....X.3....D...
00000000`00f7e674  00fb11b0 0021ecd0 00fb1180 08000008  ......!.........
00000000`00f80844  00fb11b0 0021ecd0 00fb1180 0021ed20  ......!..... .!.
00000000`00fb3124  00fb11b0 0021ecd0 00fb1180 0021ed20  ......!..... .!.
kd> db 00f1bea8 L23
00000000`00f1bea8  d8 07 d0 33 07 c3 6a c3-ca 1b 75 8c 15 9f cd 5a  ...3..j...u....Z
00000000`00f1beb8  11 5d 9b 57 ae bc 53 03-f9 ce 2e 98 69 5b 47 50  .].W..S.....i[GP
00000000`00f1bec8  7b 1d 2b                                         {.+
kd> db 00f1bea8 L30
00000000`00f1bea8  d8 07 d0 33 07 c3 6a c3-ca 1b 75 8c 15 9f cd 5a  ...3..j...u....Z
00000000`00f1beb8  11 5d 9b 57 ae bc 53 03-f9 ce 2e 98 69 5b 47 50  .].W..S.....i[GP
00000000`00f1bec8  7b 1d 2b c9 e5 67 d7 80-2c 23 b1 e4 7e 09 ec f7  {.+..g..,#..~...

I’m having a Windows 7 VM, so no need to do any fancy thing:

  • attach virtualKD
  • set breakpoint before cng!GenerateAESKey in kernel
  • trigger CryptUnprotectMemory
  • and edit 12 bytes keys, 0x60 bytes ShaHash and 0x10 bytes RandomSalt :)
#include <windows.h>
#include <wincrypt.h>
#include <stdio.h>
#pragma comment(lib, "crypt32.lib")

int __cdecl main(int, char**) {
	char b[] = "\xD8\x07\xD0\x33\x07\xC3\x6A\xC3\xCA\x1B\x75\x8C\x15\x9F\xCD\x5A\x11\x5D\x9B\x57\xAE\xBC\x53\x03\xF9\xCE\x2E\x98\x69\x5B\x47\x50\x7B\x1D\x2B\xC9\xE5\x67\xD7\x80\x2C\x23\xB1\xE4\x7E\x09\xEC\xF7";
	DWORD l = 48;

	CryptUnprotectMemory(b, l, CRYPTPROTECTMEMORY_SAME_PROCESS);
	for (DWORD i = 0; i < l; ++i) {
		printf_s("%02x ", (unsigned char)b[i]);
	}
	printf_s("\n%s\n", b);
}

/assets/images/2019-09-28-flareon/re10_kernel.PNG

Breakpoint 0 hit
cng! ?? ::FNODOBFM::`string'+0x29f6:
fffff880`01150686 e875bf0000      call    cng!GenerateAESKey (fffff880`0115c600)
1: kd> eb [rsp+360] F5 44 C0 14 85 C8 78 C5 3F 49 D5 01
1: kd> eb cng!RandomSalt 35 3a d5 b5 19 db b2 64 ba e3 85 9e d7 b1 02 e8
1: kd> eb cng!g_ShaHash 1c 81 87 7b 81 73 be 1b 99 da 11 35 10 43 4e da 97 9e b0 0e 37 cd 31 2b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10 f0 e1 d2 c3 00 00 00 00 18 00 00 00 00 00 00 00
1: kd> g

/assets/images/2019-09-28-flareon/re11_kernel.PNG


Last word, I would like to thank FireEye Lab for maintaing an excellent contest every year, eventhough I wasn’t really into RE this year, it really keeps me up (maybe others) to spend more good/fun time on doing and gain something new in this field. I appreciate and respect all the hard work/contribution of the team for the community. Can’t wait to receive awesome medal, and looking forward to next year :)