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


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.

Without access to the answer, none of our quizzes will get graded.

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 commad prompt:

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.


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://
2020-01-03T17:51:06.302+0000 W NETWORK  [thread1] Failed to connect to, 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, connection attempt failed :
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*               LISTEN

But more definitively, I can check out the process list:

elf@7e71c25338c3:~$ ps auxww
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 --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://
MongoDB server version: 3.6.3
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
Questions? Try the support group
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

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:

       __/ __


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


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

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.


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


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


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


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


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


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


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.


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


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 proove 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 your key...

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)

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

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:
    enc = f.read()

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}")
    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:

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!
  key: b5ad6a321240fbec
  seed: 1575663650
[*] Writing decrpyted file to ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf

real    2m57.066s
user    2m56.813s
sys     0m0.124s


Opening the document in a PDF reader shows an eight page document:


The solution to the objective is “Machine Learning Sleigh Route Finder”.