Objective

Krampus is pleased I recovered the note scraps and re-assembled them, but has further tasks:

Wow! We’ve uncovered quite a nasty plot to destroy the holiday season.

We’ve gotta stop whomever is behind it!

I managed to find this protected document on one of the compromised machines in our environment.

I think our attacker was in the process of exfiltrating it.

I’m convinced that it is somehow associated with the plan to destroy the holidays. Can you decrypt it?

There are some smart people in the NetWars challenge room who may be able to help us.

Terminal - Mongo Pilfer

Challenge

I steam tunnels to the NetWars room, where I’m greeted by a Final Countdown remix and Holly Evergreen:

Hey! It’s me, Holly Evergreen! My teacher has been locked out of the quiz database and can’t remember the right solution.

Can we help get back in to find that solution?

I tried lsof -i, but that tool doesn’t seem to be installed.

I think there’s a tool like ps that’ll help too. What are the flags I need?

Either way, you’ll need to know a teensy bit of Mongo once you’re in.

Pretty please find us the solution to the quiz!

Jumping into the terminal, I’m at a Linux command prompt:

'...',...'::'''''''''cdc,',,,,,,,cxo;,,,,,,,,:dl;,;;:;;;;;l:;;;cx:;;:::::lKXkc::
oc;''.',coddol;''';ldxxxxoc,,,:oxkkOkdc;,;:oxOOOkdc;;;:lxO0Oxl;;;;:lxOko::::::cd
ddddocodddddddxxoxxxxxkkkkkkxkkkkOOOOOOOxkOOOOOOO00Oxk000000000xdk00000K0kllxOKK
coddddxxxo::ldxxxxxxdl:cokkkkkOkxl:lxOOOOOOOkdlok0000000Oxok00000000OkO0KKKKKKKK
'',:ldl:,'''',;ldoc;,,,,,,:oxdc;,,,;;;cdOxo:;;;;;:ok0kdc;;;;:ok00kdc:::lx0KK0xoc
oc,''''';cddl:,,,,,;cdkxl:,,,,,;lxOxo:;;;;;:ldOxl:;;:;;:ldkoc;;::;;:oxo:::ll::co
xxxdl:ldxxxxkkxocldkkkkkkkkocoxOOOOOOOkdcoxO000000kocok000000kdccdk00000ko:cdk00
oxxxxxxxxkddxkkkkkkkkkdxkkkkOOOOOOxOOOOO00OO0Ok0000000000OO0000000000O0000000000
',:oxkxoc;,,,:oxkkxo:,,,;ldkOOkdc;;;cok000Odl:;:lxO000kdc::cdO0000xoc:lxO0000koc
l;'',;,,,;lo:,,,;;,,;col:;;;c:;;;col:;;:lc;;:loc:;:co::;:oo:;;col:;:lo:::ldl:::l
kkxo:,:lxkOOOkdc;;ldOOOOOkdc;:lxO0000ko:;:oxO000Oxl::cdk0000koc::ox0KK0ko::cok0K
kkkkOkOOOOOkOOOOOOOOOOOOOOOOOO0000000000O0000000000000000000000O000KKKKKK0OKKKKK
,:lxOOOOxl:,:okOOOOkdl;:lxO0000Oxl:cdk00000Odlcok000000koclxO00000OdllxOKKKK0kol
l;,,;lc;,,;c;,,;lo:;;;cc;;;cdoc;;;l:;;:oxoc::cc:::lxxl:::l:::cdxo:::lc::ldxoc:cl
KKOd:,;cdOXXXOdc;;:okKXXKko:;;cdOXNNKxl:::lkKNNXOo:::cdONNN0xc:::oOXNN0xc::cx0NW
XXXXX0KXXXXXXXXXK0XXXXXXNNNX0KNNNNNNNNNX0XNNNNNNNNN0KNNNNNNNNNK0NNNNNNNWNKKWWWWW
:lxKXXXXXOdcokKXXXXNKkolxKNNNNNN0xldOXNNNNNXOookXNNNNWN0xokKNNNNNNKxoxKWWNWWXOod
:;,,cdxl;,;:;;;cxOdc;;::;;:dOOo:;:c:::lk0xl::cc::lx0ko:::c::cd0Odc::c::cx0ko::lc
OOxl:,,;cdk0Oxo:;;;:ok00Odl:;;:lxO00koc:::ldO00kdl:::cok0KOxl:::cok0KOxl:::lx0KK
00000kxO00000000OxO000000000kk000000000Ok0KK00KKKK0kOKKKKKKKK0kOKKKKKKKK0k0KKKKK
:cok00000OxllxO000000koldO000000Odlok0KKKKKOxoox0KKKKK0koox0KKKKK0xoox0KKKKKkdld
;:,,:oxoc;;;;;;cokdl:;;:;;coxxoc::c:::lxkdc::c:::ldkdl::cc::ldkdl::lc::lxxoc:loc
OOkdc;;;:oxOOkoc;;;:lxO0Odl:;::lxO00koc:::lxO00kdl:::lxO00Odl::cox0KKOdl:cox0KK0
OOOOOOxk00000000Oxk000000000kk000000000Ok0KK0000KK0k0KKKKKKKK0OKKKKKKKKK00KKK0KK
c:ldOOOO0Oxoldk000000koldk000000kdlox0000K0OdloxOKK0K0kdlox0KKKK0xocok0KKK0xocld
;l:;;cooc;;;c:;:lddl:;:c:::ldxl:::lc::cdxo::coc::cddl::col::cddl:codlccldlccoxdc
000Odl;;:ok000koc;;cok0K0kdl::cdk0KKOxo::ldOKKK0xoccox0KKK0kocldOKKKK0xooxOKKKKK
0000000O0000000000O0KKK0KKKK00KKKK0KKKKK0KKKK0KKKKKKKKKK0KKKKKKKKKO0KKKKKKKKOkKK
c::ldO000Oxl:cok0KKKOxl:cdk0KKKOdl:cok0KK0kdl:cok0KK0xoccldk0K0kocccldOK0kocccco
;;;;;;cxl;;;;::::okc::::::::dxc::::::::odc::::::::ol:ccllcccclcccodocccccccdkklc
Hello dear player!  Won't you please come help me get my wish!
I'm searching teacher's database, but all I find are fish!
Do all his boating trips effect some database dilution?
It should not be this hard for me to find the quiz solution!
Find the solution hidden in the MongoDB on this system.
elf@68c111163dac:~$ Solution Find Mongo The first thing I need to do is find the Mongo database. If I just run mongo, it will try to connect to the default port, 27017. This fails: elf@7e71c25338c3:~$ mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
2020-01-03T17:51:06.302+0000 W NETWORK  [thread1] Failed to connect to 127.0.0.1:27017, in(checking socket for error after poll), reason: Connection refused
2020-01-03T17:51:06.304+0000 E QUERY    [thread1] Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed :
connect@src/mongo/shell/mongo.js:251:13
@(connect):1:6
exception: connect failed
Hmm... what if Mongo isn't running on the default port?


There’s a few ways to find the port. I can check the netstat, which can’t show me the process (I can’t do -p as non-root user), but will show the only listening port is 12121:

elf@7e71c25338c3:~$netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:12121 0.0.0.0:* LISTEN  But more definitively, I can check out the process list: elf@7e71c25338c3:~$ ps auxww
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
elf          1  0.0  0.0  18508  3484 pts/0    Ss   17:51   0:00 /bin/bash
mongo        9  1.3  0.0 1015616 58700 ?       Sl   17:51   0:01 /usr/bin/mongod --quiet --fork --port 12121 --bind_ip 127.0.0.1 --logpath=/tmp/mongo.log
elf         54  0.0  0.0  34400  2828 pts/0    R+   17:52   0:00 ps auxww


I see mongod running with --port 12121.

Connect and Enumerate

Now that I know the port, I can connect to the DB using the --port flag:

elf@68c111163dac:~$mongo --port 12121 MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:12121/ MongoDB server version: 3.6.3 Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ Questions? Try the support group http://groups.google.com/group/mongodb-user Server has startup warnings: 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2020-01-03T17:46:03.603+0000 I CONTROL [initandlisten] >  I can list the databases: > show dbs admin 0.000GB elfu 0.000GB local 0.000GB test 0.000GB  I’ll start with elfu, since that’s what likely has the test answers I’m looking for. I can list the collections in that db: > use elfu switched to db elfu > show collections bait chum line metadata solution system.js tackle tincan  As there’s a collection named solution, that seems like a good place to start: > db.solution.find() { "_id" : "You did good! Just run the command between the stars: ** db.loadServerScripts();displaySolution(); **" }  I can run the command given, and it solves the challenge:  . __/ __ / /.'*'. .o.'. .'.'o'. *'.o.'.*. .'.*.'.'.*. .o.'.o.'.o.'. [_____] ___/ Congratulations!!  Hints On solving, Holly gives me a hint to check out Rom Bowes talk, Reversing Crypto the Easy Way: Woohoo! Fantabulous! I’ll be the coolest elf in class. On a completely unrelated note, digital rights management can bring a hacking elf down. That ElfScrow one can really be a hassle. It’s a good thing Ron Bowes is giving a talk on reverse engineering! That guy knows how to rip a thing apart. It’s like he breathes opcodes! Recover Cleartext Document Files I’ve got three files from the challenge prompt in the badge, an exe, the debug symbols, and the encrypted document: $ file *
elfscrow.exe:                                               PE32 executable (console) Intel 80386, for MS Windows
elfscrow.pdb:                                               MSVC program database ver 7.00, 1024*291 bytes
ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf.enc: data


The .exe file is the binary. The .pdb file is the program database, which will bring in various things like variable and function names, and the .pdf.enc file is random data that I need to decrypt, which based on the file name will decrypt to a PDF document.

Dynamic Analysis

Algorithm Identification

I created a dummy file with 18A in it:

$cat 18A.txt AAAAAAAAAAAAAAAAAA  Then I encrypted it: E:\10>.\elfscrow.exe --encrypt 18A.txt 18A.txt.enc Welcome to ElfScrow V1.01, the only encryption trusted by Santa! Our miniature elves are putting together random bits for your secret key! Seed = 1578087110 Generated an encryption key: c63db89447cb198e (length: 8) Elfscrowing your key... Elfscrowing the key to: elfscrow.elfu.org/api/store Your secret id is 452a6f04-708e-40fb-8d5c-fc0c68b0278d - Santa Says, don't share that key with anybody! File successfully encrypted! ++=====================++ || || || ELF-SCROW || || || || || || || || O || || | || || | (O)- || || | || || | || || || || || || || || || || || ++=====================++  The resulting file was 24 bytes long: $ wc -c 18A.txt.enc
24 18A.txt.enc


That tells me that it’s using 8 byte blocks. The output also showed me an 8 byte key. That means Elfscrow is likely using DES encryption.

I also tried with a B then 17 A. The output was different in all blocks, not just the first:

$xxd 18A.txt.enc 00000000: 5a35 9433 b434 2770 8941 6335 236b a29b Z5.3.4'p.Ac5#k.. 00000010: 65ca 52d2 0bfd ddd9 e.R.....$ xxd B17A.txt.enc
00000000: e654 906b 49fb 6f4c 5454 61e8 9a26 f7e8  .T.kI.oLTTa..&..
00000010: 160f a652 375b 3516                      ...R7[5.


This result suggests it’s using CBC mode.

Network

I did it again with Wireshark running and the --insecure flag. The program issues a POST with the key:

POST /api/store HTTP/1.1
User-Agent: ElfScrow V1.01 (SantaBrowse Compatible)
Host: elfscrow.elfu.org
Content-Length: 16
Cache-Control: no-cache

47d3c1b25dd148dd


The response gives back the ID:

HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Fri, 03 Jan 2020 19:20:32 GMT
Content-Type: text/html;charset=utf-8
Content-Length: 36
Connection: keep-alive
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

ea24294c-5177-4f05-91f9-88d6d9f4dddd


When I ran the decrypt, It did the opposite. POST request with ID:

POST /api/retrieve HTTP/1.1
User-Agent: Elfscrow 1.0 (SantaBrowse Compatible)
Host: elfscrow.elfu.org
Content-Length: 36
Cache-Control: no-cache

ea24294c-5177-4f05-91f9-88d6d9f4dddd


And response with key:

HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Fri, 03 Jan 2020 19:21:58 GMT
Content-Type: text/html;charset=utf-8
Content-Length: 16
Connection: keep-alive
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

47d3c1b25dd148dd


It seems the networking part is just to go from id to key and back.

pdb

I had issues getting the PDB file to load in Ghidra, so I turned to the free Ida Pro. It actually located and loaded the .pdb file it on it’s own when I ran Ida in a Windows VM. Just for demonstration, here’s the functions with (left) and without (right) the .pdb file loaded:

Reversing Analysis

Main

The main function is mostly about processing the various command line arguments, and eventually gets to this section:

Key Generation

Inside do_encrypt, there’s a call to generate_key. This function calls time (MS docs), which returns seconds since midnight Jan 1 1970. That is set to state, which is used as the seed value. Then there’s this loop:

It loops 8 times, each time getting a byte from super_secure_random.

Jumping into that function, it’s very simple:

Eight times, it will , take two steps:

This just happens to be the random number generation algorithm used in WEP.

IV

Because it’s clearly in CBC mode, DES will require an initialization vector. In do_encrypt(), I see calls to CryptAcquireContext, CryptImportKey, and CryptEncrypt, but none of them seem to be taking an IV. I could spend more time trying to figure out how this works (and I will if my initial guess doesn’t work out), but for now, I’m going to guess the IV is the default value of all zeros.

Attack Scripts

Strategy

I’ve got a handful of pieces here:

• DES in CBC mode
• File was encrypted in a specific two hour time range
• Epoch time used as seed
• Algorithm to go from seed to key

If I assume that DES is using the default IV, I’ve got everything I need to generate all 7200 (60 * 60 * 2) possible seeds, and use them to generate 7200 keys, and try each one to decrypt the file.

Seed to Key

I re-implemented the key generation algorithm in Python in a file seed_to_key.py:

#!/busr/bin/env python3

import sys

def get_key(state):
key = b""
for _ in range(8):
state *= 0x343FD
state += 0x269EC3
state = state % pow(2, 32)
byte = (state // pow(2, 16)) % pow(2, 15)
key += f"{byte:02x}"[-2:].encode()
return key

if __name__ == "__main__":
key = get_key(int(sys.argv[1]))
print(f'[+] key: {key.decode()}')


The get_key function takes a state (or seed) and returns an eight-byte key as a hex string. That function can be imported into other Python scripts, or, if run as main, it will read the seed as a command line argument and print the result.

I can prove it works by encrpyting a file, and taking the seed it gives me, and seeing if I can match the key it provides:

E:\10>.\elfscrow.exe --encrypt 18A.txt 18A.txt.enc
Welcome to ElfScrow V1.01, the only encryption trusted by Santa!

Our miniature elves are putting together random bits for your secret key!

Seed = 1578138041

Generated an encryption key: 754aad0ccca2cb80 (length: 8)

Elfscrowing the key to: elfscrow.elfu.org/api/store

Your secret id is adff3777-c109-40b8-a79c-1f356f63d9ba - Santa Says, don't share that key with anybody!
File successfully encrypted!

++=====================++
||                     ||
||      ELF-SCROW      ||
||                     ||
||                     ||
||                     ||
||     O               ||
||     |               ||
||     |   (O)-        ||
||     |               ||
||     |               ||
||                     ||
||                     ||
||                     ||
||                     ||
||                     ||
++=====================++

$python3 seed_to_key.py 1578138041 [+] key: 754aad0ccca2cb80  Decrypt File with Key Next I want to take a file for which I know the key, and decrypt it, without using elfscrow.exe. I’ll save this as elfscrow_decrypt.py: #!/usr/bin/env python3 import binascii import sys from Crypto.Cipher import DES def decrypt_des(key, data): des = DES.new( binascii.unhexlify(key), DES.MODE_CBC, b"\x00\x00\x00\x00\x00\x00\x00\x00" ) cleartext = des.decrypt(data) return cleartext if __name__ == "__main__": fn = sys.argv[1] key = sys.argv[2] with open(fn, "rb") as f: enc = f.read() plaintext = decrypt_des(key, enc) print(plaintext)  The function takes a key and the encrpyted data, creates a DES object using the key, mode, and default IV of all 0s, and then returns the cleartext. That function can be imported into other Python scripts, or, if run as main, it will read a filename and key from the command line args, and print the plaintext result. I’ll test it on my file with 18 A that I encrypted above. It gave a key of 754aad0ccca2cb80. When I run the script, I get the plaintext (and some PKCS7 padding, which is fine): $ python3 elfscrow_decrypt.py 18A.txt.enc 754aad0ccca2cb80
b'AAAAAAAAAAAAAAAAAA\n\x05\x05\x05\x05\x05'


Looks like my guess about the default IV was correct.

Brute Force over Time Range

Now I just need to put those together with some code that loops over the possible seeds. I’ll use the calendar and time modules to get the seed values, and I’ll import the two other scripts above.

The script will read the encrypted file, and generate the start and stop epoch timestamps. Then it will loop from start to stop, for each seed generating a key using the script above, and then trying to decrypt the file using the other script above. If the plaintext starts with %PDF, the magic bytes for PDF files, it’s decrypted correctly and the loop can stop.

#!/usr/bin/env python3

import calendar
import sys
import time
import elfscrow_decrypt
import seed_to_key

outfile = "ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf"
infile = outfile + ".enc"
start_str = "2019-12-06 19:00:00"
end_str = "2019-12-06 21:00:00"
start = calendar.timegm(time.strptime(start_str, "%Y-%m-%d %H:%M:%S"))
end = calendar.timegm(time.strptime(end_str, "%Y-%m-%d %H:%M:%S"))

with open(infile, "rb") as f:

print(f"[*] Looping over seeds from {start_str}[{start}] to {end_str}[{end}]")
for seed in range(start, end + 1):
sys.stdout.write(f"\r[*] Trying seed {seed}")
sys.stdout.flush()
key = seed_to_key.get_key(seed).decode()
plaintext = elfscrow_decrypt.decrypt_des(key, enc)
if plaintext.startswith(b"%PDF"):
print(f"\n[+] Found it!\n  key: {key}\n  seed: {seed}")
print(f"[*] Writing decrpyted file to {outfile}")
with open(outfile, "wb") as f:
f.write(plaintext)
sys.exit()


The script takes about three minutes to find the correct seed, but it eventually decrypts the file:

\$ time python3 decrypt_sledo.py
[*] Looping over seeds from 2019-12-06 19:00:00[1575658800] to 2019-12-06 21:00:00[1575666000]
[*] Trying seed 1575663650
[+] Found it!