[Reverse] CyBRICS CTF Quals 2019 - Hidden Flag

Hidden Flag 220

Description:
Hidden Flag (Reverse, Hard, 220 pts) Author: Khanov Artur (awengar)
Somebody hides flag in RAM. Catch it
Raw dump: https://cybrics.net/files/20190717.zip.torrent

I didn’t solve this challenge during the ctf, one of the main reasons was because the challenge was a mix of a forensics/reverse, I got stuck on the forensics part, mostly because I don’t have much experience looking at memory dumps, the reversing part was pretty easy after finding the “malicious” binary.

Forensics part

So we start with a memdump and we somehow need to find the flag in memory, I used volatility for this part. The first thing we have to do is to find the most suitable profile for the dump, volatility has a command named imageinfo which can help us with that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ volatility -f 20190717.mem imageinfo
Volatility Foundation Volatility Framework 2.6
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win2016x64_14393, Win10x64_14393, Win10x64_16299, Win10x64_17134, Win10x64_15063
AS Layer1 : SkipDuplicatesAMD64PagedMemory (Kernel AS)
AS Layer2 : FileAddressSpace (/home/fucker/Downloads/volq/20190717.mem)
PAE type : No PAE
DTB : 0x1ad002L
KDBG : 0xf8005b5a3520L
Number of Processors : 2
Image Type (Service Pack) : 0
KPCR for CPU 0 : 0xfffff8005a4ee000L
KPCR for CPU 1 : 0xffff800121420000L
KUSER_SHARED_DATA : 0xfffff78000000000L
Image date and time : 2019-07-17 23:48:54 UTC+0000
Image local date and time : 2019-07-17 16:48:54 -0700

Volatility suggests 5 profiles, Win10x64_17134 is the most updated so I opted to use that one. This is the part where I got stuck I kept insisting on looking only at processes during the ctf, I used comands like pslist, psxview etc and I found some interesting processes that weren’t listed in pslist :

1
2
3
4
5
6
7
8
9
10
$ volatility -f 20190717.mem --profile Win10x64_17134 psxview
Volatility Foundation Volatility Framework 2.6
Offset(P) Name PID pslist psscan thrdproc pspcid csrss sessiodn deskthrd ExitTime
------------------ -------------------- ------ ------ ------ -------- ------ ----- ------- -------- --------
... truncated ...
0x000000002736c580 dllhost.exe 5024 False True True False True False True
... truncated ...
0x0000000070b46a20 32...2 False False False False False False True
... truncate ...
0x000000001ff17580 dllhost.exe 240 False True True False False False False

As you can see this 3 processes are not shown in pslist one of them doesn’t even have a name or a valid PID, I tried to dump the memory of this 3 but I was not able to do it, for some reason volatility was spiting errors, I also dumped part of the registry too trying to find something useful but that also failed.
Much later I decided to view the list of kernel drivers loaded on the system, for this I used the modules command. This command walks the doubly-linked list of LDR_DATA_TABLE_ENTRY structures pointed to PsLoadedModuleList. However this cannot find hidden/unlinked kernel drivers but if this doesn’t work we can also try to do a modscan which serves that purpose.

The output of the modules command:

1
2
3
4
5
6
7
8
9
10
volatility -f 20190717.mem --profile Win10x64_17134 modules
Volatility Foundation Volatility Framework 2.6
Offset(V) Name Base Size File
------------------ -------------------- ------------------ ------------------ ----
... truncated...
0xffffd88ec1e8e010 vmhgfs.sys 0xfffff8005da80000 0x2b000 \SystemRoot\system32\DRIVERS\vmhgfs.sys
0xffffd88ec1fdd8c0 condrv.sys 0xfffff8005dab0000 0x12000 \SystemRoot\System32\drivers\condrv.sys
0xffffd88ec45fb240 WdNisDrv.sys 0xfffff8005dad0000 0x12000 \SystemRoot\system32\drivers\wd\WdNisDrv.sys
0xffffd88eda5c83d0 Flagostor.sys 0xfffff8005daf0000 0x7000 \??\C:\t4est\Flagostor.sys
0xffffd88ec2981480 RamCaptur...er64.SYS 0xfffff8005db00000 0x7000 \??\C:\Users\test\Desktop\RamCapturer\x64\RamCaptureDriver64.SYS

We didn’t even ran modscan and we already find two weird drivers that are loaded directly from a strange path C:\t4est and C:\test, this isn’t normal, the normal thing to happen is to be loaded from the system32 folder… Obviously RamCaptureDriver64.sys is probably what the creator used to dump this memory, so we are left with flagostor.sys which already has a weird name and also hints for flag after this I used moddump to extract this driver:

1
2
3
4
5
$ volatility -f 20190717.mem --profile Win10x64_17134 moddump -b 0xfffff8005daf0000 -D .
Volatility Foundation Volatility Framework 2.6
Module Base Module Name Result
------------------ -------------------- ------
0xfffff8005daf0000 Flagostor.sys OK: driver.fffff8005daf0000.sys

Checking what kind of file:

1
2
$ file driver.fffff8005daf0000.sys
driver.fffff8005daf0000.sys: PE32+ executable (native) x86-64, for MS Windows

And this is it, the forensics part is over lets go for the reversing part.

Reversing part

We have now a PE executable, I used both IDA and Ghidra, this time Ghidra actually helped me decoding the final part of the decryption function (Pseudo c code was somehow presented better than ida at least from my perspective). I first started analysing with IDA, the main function sub_FFFFF8005DAF1000 which I renamed later to printLoader in the picture below:

So lets check the next function:

Dumping the global variable (Double click on unk_FFFFF8005DAF3000):

Analysing sub_FFFFF8005DAF1440:

Since we can convert assembly to pseudo code c in IDA we can easily convert this function into python:

Creating a python script for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def generate_table(key):
table = []
for i in xrange(256):
table.append(i)
v6 = 0
for j in xrange(256):
iVar1 = ord(key[j%len(key)]) + table[j] + v6
v6 = ((iVar1&0xff) + iVar1) - iVar1

# swap
aux = table[j]
table[j] = table[v6]
table[v6] = aux
##########
return table

The same thing can be done for the decryptFlag function in this case I chose to use GHIDRA which the pseudo code was a bit more understandable for me at least:

Once again this could easily be converted to python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def decrypt_flag(table,flagB):
l = len(flagB)
flag = []
for x in xrange(l):
flag.append('x')

local_38 = 0;
local_34 = 0;
local_28 = 0;
local_20 = l;
while (local_28 < local_20):
local_38 = local_38 + 1 & 0xff
local_34 = local_34 + table[local_38] & 0xff

# swap
aux = table[local_38]
table[local_38] = table[local_34]
table[local_34] = aux
##########

flag[local_28] = table[table[local_38]+table[local_34] & 0xff] ^ ord(flagB[local_28])

local_28 = local_28 + 1;


return ''.join([chr(x) for x in flag])

Obviously there was no need to rewrite all of this in python if you manage to run the binary on your computer in my case I wasn’t able to do it in my virtual machine due to some errors that I don’t know, if you could run it you just needed to put a break point in the end and watch the stored values obtaining the flag with ease. The final python script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
flagB = '\x2D\xFB\x9B\xA8\x21\xF8\xB0\xB5\xFA\xEC\x58\xC5\xF9\x35\x57\xFA\xE1\x62\x0E\x19\x45\x7D\x33\x58\x6F\xC9\x88\x4F\x70\x82'
key = 'qweasdzxc'

def generate_table(key):
table = []
for i in xrange(256):
table.append(i)
v6 = 0
for j in xrange(256):
iVar1 = ord(key[j%len(key)]) + table[j] + v6
v6 = ((iVar1&0xff) + iVar1) - iVar1

# swap
aux = table[j]
table[j] = table[v6]
table[v6] = aux
##########
return table

def decrypt_flag(table,flagB):
l = len(flagB)
flag = []
for x in xrange(l):
flag.append('x')

local_38 = 0;
local_34 = 0;
local_28 = 0;
local_20 = l;
while (local_28 < local_20):
local_38 = local_38 + 1 & 0xff
local_34 = local_34 + table[local_38] & 0xff

# swap
aux = table[local_38]
table[local_38] = table[local_34]
table[local_34] = aux
##########

flag[local_28] = table[table[local_38]+table[local_34] & 0xff] ^ ord(flagB[local_28])

local_28 = local_28 + 1;


return ''.join([chr(x) for x in flag])



print decrypt_flag(generate_table(key), flagB)

Getting the flag:

1
2
$ python hiddenFlag/hidden.py 
cybrics{H1DD3N_D33P_1N_NTKRNL}

The flag was cybrics{H1DD3N_D33P_1N_NTKRNL}