Holiday Hack 2019: Recover Cleartext Document
Objective
Links:
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.
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 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:
- \[state = (state * 0x343FD + 0x269EC3)\mod 2^{32}\]
- \[set byte[i] = int(\frac{state}{2^{16}})\ \&\ 0xff\]
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 encrypting 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)
print(plaintext)
The function takes a key and the encrypted 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:
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}")
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!
key: b5ad6a321240fbec
seed: 1575663650
[*] Writing decrpyted file to ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf
real 2m57.066s
user 2m56.813s
sys 0m0.124s
Document
Opening the document in a PDF reader shows an eight page document:
The solution to the objective is “Machine Learning Sleigh Route Finder”.