Hello Navi

Tech, Security & Personal Notes

I dropped my message into the bin of Legos. It’s all scrambled up now. Please help.

Initial Analysis

The challenge provides a Python script encoder.py and its corresponding output output.txt. The goal is to recover a flag that has had its bits scrambled based on a seed derived from a hardcoded string.

Seed Generation

The script uses RSA to “encrypt” the string "I_LOVE_RNG":

1
2
3
4
text = "I_LOVE_RNG"
n, seed = RSA_enc(text)
# ...
enc_seed = pow(seed, e, n)

The RSA_enc function calculates seed = pow(plain_num, e, n). In the main function, it then calculates enc_seed = pow(seed, e, n). Since e = 7 is very small and the plaintext "I_LOVE_RNG" is short, the value me (where m is the integer representation of the text) is much smaller than the 4096-bit RSA modulus n. This means pow(m, e, n) is simply me without any modular reduction.

Bit Shuffling

1
2
3
4
flag_bits = get_flag_bits(flag)
for i in range(10):
random.seed(seed*(i+1))
random.shuffle(flag_bits)

The flag is converted into a list of bits and shuffled 10 times using Python’s random.shuffle. Each shuffle is seeded with a value derived from the seed identified above.

Solution

To recover the flag, we follow these steps:

  1. Recover the Seed: Calculate the integer value of "I_LOVE_RNG" and raise it to the 7th power to get the seed. We can verify this against enc_seed in the output file by checking if seed**7 == enc_seed.
  2. Reverse the Shuffle: Since random.shuffle is deterministic when the seed is known, we can track how the positions change. Instead of shuffling the bits directly, we shuffle a list of indices [0, 1, 2, ..., len(bits)-1].
  3. Reconstruct the Flag: After 10 shuffles, the index list tells us where each original bit ended up. We map the bits from the encoded flag back to their original positions and convert the bit array back into a string.

Solution Script

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
import random

def get_flag_bits(flag_hex):
flag_bytes = bytes.fromhex(flag_hex)
bits = []
for b in flag_bytes:
bits.extend(list(bin(b)[2:].zfill(8)))
return bits

def bit_arr_to_str(bit_arr):
byte_arr = []
for i in range(0, len(bit_arr), 8):
byte = bit_arr[i:i+8]
char = int(''.join(byte), 2)
byte_arr.append(char)
return bytes(byte_arr)

# Values from output.txt
enc_flag_hex = "a9fa3c5e51d4cea498554399848ad14aa0764e15a6a2110b6613f5dc87fa70f17fafbba7eb5a2a5179"
text = "I_LOVE_RNG"
plain_num = int.from_bytes(text.encode(), "big")
seed = plain_num ** 7

flag_bits_enc = get_flag_bits(enc_flag_hex)
num_bits = len(flag_bits_enc)
indices = list(range(num_bits))

# Replay the shuffle on the indices
for i in range(10):
random.seed(seed * (i + 1))
random.shuffle(indices)

# Map scrambled bits back to original positions
flag_bits_orig = [None] * num_bits
for j in range(num_bits):
flag_bits_orig[indices[j]] = flag_bits_enc[j]

print(bit_arr_to_str(flag_bits_orig).decode())

Flag

UMASS{tH4Nk5_f0R_uN5CR4m8L1nG_mY_M3554g3}

My friend tells me there’s an office of a store that sells special bricks above a well-known shopping centre on this street. My friend tells me I’ll it will bring me joy, but I’m not sure that the bricks are from the famous company. Can you give me the contact email address of the store so I can learn more?

Flag format: UMASS{email@example.com}

Key clues: - “Special bricks” (possibly a LEGO alternative store) - “Above a well-known shopping centre” - “Bring me joy” (might be part of the store’s name)

Solution

By researching shopping centers in Hong Kong known for hobbyist shops, we identified Ho King Commercial Centre (好景商业中心) and Ho King Shopping Centre (好景商场) as likely locations.

Using tools like Google Gemini Deep Search to find shops selling “bricks” in these locations, we found a store associated with “Joy” (matching the “bring me joy” hint). The store in question is Joying Bricks.

Searching for their contact information lead to the official email address.

Flag

UMASS{joyingwang@gmail.com}

Hey! A man was caught with malware on his PC in Lego City. Luckily, we were able to get a packet capture of his device during the download. Help Lego City Police figure out the source of this malicious download.

The flag for this challenge is in the format UMASS{[sha256sum]} of the malicious download.

Initial Analysis

The challenge provides a packet capture (thedamage.pcapng) containing traffic from a machine that downloaded malware. The goal is to identify the malicious file and its hash.

Solution

Using tshark, we analyzed the HTTP traffic to see what files were downloaded:

1
tshark -r thedamage.pcapng -Y http -T fields -e http.host -e http.request.uri | sort | uniq -c | sort -rn

The output revealed several interesting files: - /installer.py - /launcher - Several images (/fungame.jpg, /cooldog.jpeg, etc.)

We extracted the objects from the HTTP sessions:

1
tshark -r thedamage.pcapng --export-objects http,extracted_files -q

The launcher file appeared suspicious, but its file type was obscured and it didn’t match known malware signatures.

Examining installer.py revealed it was a dropper script that decrypts launcher:

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib
import nacl.secret

def fix_error():
seed = "38093248092rsjrwedoaw3"
key = hashlib.sha256(seed.encode()).digest()
# XSalsa20 + Poly1305 MAC
box = nacl.secret.SecretBox(key)
with open("./launcher", "rb") as f:
data = f.read()
decrypted = box.decrypt(data)
with open("./launcher", "wb") as f:
f.write(decrypted)

The script uses PyNaCl (SecretBox) to decrypt launcher using the SHA256 hash of the seed 38093248092rsjrwedoaw3.

Decryption and Hashing

After decrypting the file, we calculated its SHA256 hash. The decrypted file was identified as a FreeBSD/i386 compact demand paged dynamically linked executable.

  • Decrypted SHA256: e7a09064fc40dd4e5dd2e14aa8dad89b328ef1b1fdb3288e4ef04b0bd497ccae

Checking this hash on VirusTotal confirms it is a known malicious file.

Flag

UMASS{e7a09064fc40dd4e5dd2e14aa8dad89b328ef1b1fdb3288e4ef04b0bd497ccae}

It’s in the name!

Initial Analysis

We are given a file cake. Binary STL files, introduced in 1987, do not have a mandatory magic number. The first 80 bytes are reserved for a header (comments/description), which in this case are all zeros.

1
2
3
4
5
6
7
8
9
10
11
❯ xxd cake.stl | head
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 2a99 0000 59a9 053f 24a9 05bf 78a4 2cbf *...Y..?$...x.,.
00000060: f669 d740 6242 6140 2fd6 8b40 2a71 d940 .i.@bBa@/..@*q.@
00000070: 5a24 6540 63e7 8b40 bbb3 d940 3d3c 6440 Z$e@c..@...@=<d@
00000080: c774 8c40 0000 11a9 053f efa8 05bf d9a4 .t.@.....?......
00000090: 2cbf 3c49 d940 2cc4 5e40 4840 8e40 c748 ,.<I.@,.^@H@.@.H

Because of this lack of a fixed signature, tools like file may simply identify it as data.

Solution

Using ImHex with an STL file pattern, we can confirm the file structure. The triangleCount field (2a99 at offset 0x50) reveals that there are 39,210 triangles in the model.

1
triangleCount -> 2a99 -> 39210

By importing the model into Blender and zooming into its interior, we can find the flag model floating in the center.

Flag

UMASS{SL1C3_&_D1C3}

There’s something in the water…

Initial Analysis

The challenge provides a GIF file (CHALL.gif). Inspecting the file with standard tools reveals a 12-frame animation.

  • File Analysis: Running identify -verbose CHALL.gif shows that the image uses an 8-color Global Color Table (3-bit).
  • Palette Anomaly: The palette contains redundant entries for almost identical colors. Specifically, the dark blue color of the water (11, 41, 71) is mapped to both index 1 and index 3.

Solution

In many steganography challenges involving GIFs or indexed PNGs, redundant palette indices are used to hide data. I wrote a script to isolate these two specific indices by extracting the frames and visualizing the pixels.

1
2
3
4
5
6
7
8
9
10
11
import PIL.Image
import numpy as np

# 'P' 代表 Palette(调色板)模式
img = PIL.Image.open('frame-0.gif').convert('P')
data = np.array(img)

# Visualize index 1 as '#' and everything else as '.'
for r in range(100):
row_str = "".join(["#" if x == 1 else "." for x in data[r]])
print(row_str)

By printing the pixel grid of the water area and specifically looking for the “hidden” index (Index 1 vs Index 3), a clear ASCII art representation of the flag appeared:

1
2
3
4
5
6
7
8
9
10
11
12
13
.....................................#.....................................................#........
..................................####.....................................................####.....
..................................#...........................................................#.....
..................................#...#..#...#......#####......#####...#.######.######...##...#.....
...#..#.#####.#####.#####.#####...#..##..#...#......#...#......#......##.....#......#...#.#...#.....
...#..#.#.#.#.#...#.#.....#......##.#.#..##..#......#####......#.....#.#.....#......#..#..#...##....
...#..#.#.#.#.#####.###...###...##....#..#.#.#......#...#......#..##...#....#......#..#######..##...
...#..#.#...#.#...#...###...###..##...#..#.#.#......#...#......#..###..#...#......#.......#...##....
...#..#.#...#.#...#.....#.....#...#...#..#..##......#...#......#....#..#...#......#.......#...#.....
...####.#...#.#...#.#####.#####...#..###.#...#.####.#...#.####.######.###..#......#.......#...#.....
..................................#...........................................................#.....
..................................####.....................................................####.....
.....................................#.....................................................#........

Flag

UMASS{1N_A_G1774}

Welcome to UMassCTF 2026! Join our Discord server using this link: https://discord.gg/E3rSU5UWwY

Initial Analysis

The challenge is a standard welcome task. We need to join the official Discord server and follow the instructions to find the flag.

Solution

After joining the Discord server, the Togekiss bot in the #welcome channel provides instructions to obtain the participant role:

“React to this message after reading the rules above to obtain the participant role”

点击那个 🔒 图标。

Flag

UMASS{w3lc0m3_70_um455c7f2026}

Decrypt tampered audit log entries to reconstruct evidence of unauthorized access.

Initial Analysis

The challenge provides a secure audit log export from “ClinCore Health Systems”. Scanning through the logs, several EncryptedToken entries are visible, formatted as Base32 strings.

A critical clue is found in the logs:

1
[2026-03-15 16:00:05] ENCRYPT: Cipher config: XOR mode=repeating key_len=4

This indicates that the tokens are encrypted using a repeating 4-byte XOR key.

Solution

While most tokens in the log decode to fragmented text, the token on line 108 (YX2THEVPQ4LNRIMFCHIKFUCBRL2IGF6567KEFW7Q2AK5XIUEJXI7FUCE33VQ====) decodes to raw binary data, suggesting it contains the flag.

Since the flag format is SDG{...}, we can perform a known-plaintext attack to recover the 4-byte XOR key by XORing the first 4 bytes of the ciphertext with SDG{.

1
2
3
4
5
6
7
8
9
10
11
12
import base64

# The tampered token (line 108)
ct = base64.b32decode("YX2THEVPQ4LNRIMFCHIKFUCBRL2IGF6567KEFW7Q2AK5XIUEJXI7FUCE33VQ====")

# Use "SDG{" as known plaintext to derive the 4-byte XOR key
known = b"SDG{"
key = bytes([ct[i] ^ known[i] for i in range(4)])

# Decrypt with repeating 4-byte key
plaintext = bytes([ct[i] ^ key[i % 4] for i in range(len(ct))])
print(plaintext.decode())

Key Recovery: - Ciphertext (hex): c5f53392... - Known Plaintext: 5344477b (SDG{) - Derived Key: 96b174e9

Flag

SDG{96b174e94a5cb2c4ae62faa24598da07}

Score! You found a treasure chest! Now if only you could figure out how to unlock it… maybe there’s a magic word?

Initial Analysis

The challenge provides a Linux binary that prompts for a “magic word.” If the correct word is entered, it displays a treasure chest (ASCII art) and presumably the flag. The goal is to reverse-engineer the “magic word” verification logic.

Using IDA Pro to decompile the main function, the following program flow was identified:

  1. Input Collection: The program reads up to 256 characters from stdin using fgets.
  2. Padding & Allocation:
    • It calculates the input length (v8).
    • It determines a padding value (v7 = v8 % 8).
    • It allocates memory for the input plus padding and copies the string into it.
    • Crucially, it appends v7 null bytes.
  3. Encryption: It calls sub_4012A9, which iterates through the input in 8-byte blocks and encrypts them using sub_4011C6.
  4. Verification: The program checks if the final processed length v8 is 34 (0x22) and if the resulting ciphertext matches a hardcoded byte array at unk_404080.
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[256]; // [rsp+10h] [rbp-130h] BYREF
void *v5[3]; // [rsp+110h] [rbp-30h] BYREF
void *dest; // [rsp+128h] [rbp-18h]
int v7; // [rsp+134h] [rbp-Ch]
signed int v8; // [rsp+138h] [rbp-8h]
int i; // [rsp+13Ch] [rbp-4h]

qmemcpy(v5, "tiny_encrypt_key", 16);
puts("Try to open the chest!");
printf("Maybe try saying the magic word: ");
fgets(s, 256, stdin);
s[strcspn(s, "\n")] = 0;
printf("input: %s\n", s);
v8 = strlen(s);
v7 = v8 % 8;
dest = malloc(v8 + v8 % 8);
memcpy(dest, s, v8);
memset((char *)dest + v8, 0, v7);
v8 += v7;
sub_4012A9(dest, (unsigned int)v8, v5);
printf("result: 0x");
for ( i = 0; i < v8; ++i )
printf("%02X", *((unsigned __int8 *)dest + i));
putchar(10);
if ( v8 == 34 && !memcmp(dest, &unk_404080, 0x22u) )
{
puts("Congrats! Here's your treasure: ");
puts("*******************************************************************************");
puts(" | | | |");
puts(" _________|________________.=\"\"_;=.______________|_____________________|_______");
puts("| | ,-\"_,=\"\" `\"=.| |");
puts("|___________________|__\"=._o`\"-._ `\"=.______________|___________________");
puts(" | `\"=._o`\"=._ _`\"=._ |");
puts(" _________|_____________________:=._o \"=._.\"_.-=\"'\"=.__________________|_______");
puts("| | __.--\" , ; `\"=._o.\" ,-\"\"\"-._ \". |");
puts("|___________________|_._\" ,. .` ` `` , `\"-._\"-._ \". '__|___________________");
puts(" | |o`\"=._` , \"` `; .\". , \"-._\"-._; ; |");
puts(" _________|___________| ;`-.o`\"=._; .\" ` '`.\"` . \"-._ /________________|_______");
puts("| | |o; `\"-.o`\"=._`` '` \" ,__.--o; |");
puts("|___________________|_| ; (#) `-.o `\"=.`_.--\"_o.-; ;___|___________________");
puts("____/______/______/___|o;._ \" `\".o|o_.--\" ;o;____/______/______/____");
puts("/______/______/______/_\"=._o--._ ; | ; ; ;/______/______/______/_");
puts("____/______/______/______/__\"=._o--._ ;o|o; _._;o;____/______/______/____");
puts("/______/______/______/______/____\"=._o._; | ;_.--\"o.--\"_/______/______/______/_");
puts("____/______/______/______/______/_____\"=.o|o_.--\"\"___/______/______/______/____");
puts("/______/______/______/______/______/______/______/______/______/______/________");
puts("*******************************************************************************");
free(dest);
return 0;
}
else
{
puts("Hmmmm.... didn't open...");
free(dest);
return 0;
}
}

__int64 __fastcall sub_4012A9(__int64 a1, unsigned int a2, __int64 a3)
{
__int64 result; // rax
__int64 v5; // [rsp+1Ch] [rbp-Ch] BYREF
unsigned int i; // [rsp+24h] [rbp-4h]

for ( i = 0; ; ++i )
{
result = a2 >> 2;
if ( i >= (unsigned int)result )
break;
v5 = *(_QWORD *)((int)(8 * i) + a1);
sub_4011C6(&v5, a3);
*(_QWORD *)(a1 + (int)(8 * i)) = v5;
}
return result;
}

t64 __fastcall sub_4011C6(unsigned int *a1, _DWORD *a2)
{
unsigned int v3; // [rsp+1Ch] [rbp-14h]
unsigned int v4; // [rsp+20h] [rbp-10h]
int i; // [rsp+28h] [rbp-8h]
int v6; // [rsp+2Ch] [rbp-4h]

v4 = *a1;
v3 = a1[1];
v6 = 0;
for ( i = 0; i <= 31; ++i )
{
v6 -= 1640531527;
v4 += ((v3 >> 5) + a2[1]) ^ (v3 + v6) ^ (16 * v3 + *a2);
v3 += ((v4 >> 5) + a2[3]) ^ (v4 + v6) ^ (16 * v4 + a2[2]);
}
*a1 = v4;
a1[1] = v3;
return v3;
}

Reverse Engineering the Cipher

The function sub_4011C6 implements a variation of the Tiny Encryption Algorithm (TEA).

  • Key: The key is the hardcoded string "tiny_encrypt_key".
  • Constants: It uses the standard TEA delta 0x9E3779B9.
  • Structure: It performs 32 rounds of Feistel-like transformations.
  • Implementation Detail: Unlike standard TEA, it updates the “sum” (v6) at the start of the loop and uses key indices [0, 1] for the first half and [2, 3] for the second half of the block update.

Extraction & Decoding

To solve the challenge, we extract the target ciphertext from 0x404080 and the 16-byte key. Since TEA is a symmetric block cipher, we can implement a decryption routine.

Target Ciphertext (34 bytes): 38 75 5B CB 44 D2 BE 5D 96 9C 56 43 EA 98 06 75 4A 48 13 E6 D4 E8 8E 4F 72 70 8B FF DC 99 F8 76 C5 C9

Decryption Script (Python):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def tea_decrypt(v_bytes, k_bytes):
v0 = int.from_bytes(v_bytes[0:4], 'little')
v1 = int.from_bytes(v_bytes[4:8], 'little')
k = [int.from_bytes(k_bytes[i:i+4], 'little') for i in range(0, 16, 4)]

delta = 0x9E3779B9
sum_val = (delta * 32) & 0xFFFFFFFF

for _ in range(32):
v1 = (v1 - (((v0 >> 5) + k[3]) ^ (v0 + sum_val) ^ ((v0 << 4) + k[2]))) & 0xFFFFFFFF
v0 = (v0 - (((v1 >> 5) + k[1]) ^ (v1 + sum_val) ^ ((v1 << 4) + k[0]))) & 0xFFFFFFFF
sum_val = (sum_val - delta) & 0xFFFFFFFF

return v0.to_bytes(4, 'little') + v1.to_bytes(4, 'little')

# Data from unk_404080
ciphertext = bytes.fromhex("38755BCB44D2BE5D969C5643EA9806754A4813E6D4E88E4F72708BFFDC99F876")
key = b"tiny_encrypt_key"

flag = b""
for i in range(0, len(ciphertext), 8):
flag += tea_decrypt(ciphertext[i:i+8], key)

print(flag.decode().strip('\x00'))

Conclusion

Running the decryption yields the magic word/flag. The check for length 34 and the specific padding logic suggests the original flag was 29 characters long, which when padded with 5 null bytes (29 % 8 = 5), results in the 34-byte block compared by the binary.

Flag

RS{oh_its_a_TEAreasure_chest}

You have recieved a message in a bottle, saying something about the strange behavior of sea creatures. I wonder what that could be about?

Initial Analysis

The challenge provides a rosbag2 recording consisting of a metadata.yaml file and a SQLite database mystery_message_0.db3. The metadata indicates that the recording contains messages for several topics, most notably /draw_commands.

Database Inspection

Opening the database with sqlite3, we find several tables, including topics and messages. The topics table reveals that the /draw_commands topic uses the std_msgs/msg/String type and contains JSON-formatted instructions.

1
2
SELECT * FROM topics WHERE name = '/draw_commands';
-- topic_id 6, type std_msgs/msg/String

Data Extraction

The messages in the messages table are stored in CDR (Common Data Representation) format. For std_msgs/msg/String, the actual string data starts after an 8-byte header (4 bytes CDR header + 4 bytes string length).

By extracting and parsing these strings, we find two types of commands:

  • {"cmd": "pen", "off": 0/1, ...}: Controls whether the turtle is drawing.
  • {"cmd": "teleport", "x": ..., "y": ..., "theta": ...}: Moves the turtle to specific coordinates.

Visualization

Using a Python script with matplotlib, we can reconstruct the path drawn by the turtle. By treating teleport as a movement and pen as the draw state, we can plot the lines.

The coordinates reveal two distinct rows of characters:

  • Top Row (Y ≈ 6.0): Letters forming RS{f0ll0w_th3_
  • Bottom Row (Y ≈ 4.7): Letters forming 5ea_Turtles}

Flag

RS{f0ll0w_th3_5ea_Turtles}

1
2
3
4
5
6
7
8
9
10
11
# get help
# command /?

# Pagination
A:\> dir /p

# Wide List Format
A:\> dir /w

# Piping to more
A:\> dir | more

cat.exe (type)

1
2
3
4
5
6
7
8
9
10
11
12
13
DOS          <DIR>         12-15-25   5:07p
COMMAND COM 54,645 05-31-94 6:22a
WINA20 386 9,349 05-31-94 6:22a
CONFIG SYS 71 12-15-25 5:07p
AUTOEXEC BAT 78 12-15-25 5:07p
FLAG TXT 60 04-02-26 5:07a
6 file(s) 64,203 bytes
10,428,416 bytes free

C:\>type flag.txt
pwn.college{QMkbfHdYCkFD_y-FmBdS66_cyD2.******************}

C:\>

ls.exe (dir)

DOS filenames were case-insensitive

DOS assumed that every file had a name of 8 characters or less and an “extension” (after the one allowed . in the filename) of 3 characters or less.

there is no permission or ownership information

As a side note, extensions were critical in DOS for similar reasons that they remain important in Windows: figuring out the type of file. Specifically for DOS, programs needed to be named with extensions of EXE (normal DOS executable), COM (simple, older executables that were basically just raw binary code), and BAT (a batch script containing commands to run, analogous to a weird variant of a typical shell script).

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
Booting from Hard Disk...
Starting MS-DOS...


HIMEM is testing extended memory...done.
Batch file missing

C:\>dir

Volume in drive C is MS-DOS_6
Volume Serial Number is 5B8F-88E6
Directory of C:\

DOS <DIR> 12-15-25 5:07p
COMMAND COM 54,645 05-31-94 6:22a
WINA20 386 9,349 05-31-94 6:22a
CONFIG SYS 71 12-15-25 5:07p
FLG396 TXT 60 04-02-26 5:14a
5 file(s) 64,125 bytes
10,432,512 bytes free

C:\>type flg396.txt
pwn.college{c1dnzzEBipHvxFUOKeBsqnezrlb.******************}

C:\>

Changing Disks

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
Boot failed: could not read the boot disk

Booting from Hard Disk...
Starting MS-DOS...


HIMEM is testing extended memory...done.

C:\>C:\DOS\SMARTDRV.EXE /X
C:\>d:

D:\>dir

Volume in drive D has no label
Volume Serial Number is 409D-8F04
Directory of D:\

FLAG TXT 60 04-02-26 5:15a
1 file(s) 60 bytes
16,723,968 bytes free

D:\>type flag.txt
pwn.college{A_QSyVfCu23CuMIMATEp-popFEE.******************}

D:\>

Reading Floppies

floppy disks (软盘)

DOS assigned floppy drives the A: and B: drives, and “mounting” happened automatically on access (or, more specifically, there was no filesystem “mounting”; access just happened). The two letters were for computers that had both a 5.25” and a 3.5” floppy drive

load floppy in control panel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+00F91FB0+00EF1FB0 CA00



Booting from Floppy...
Boot failed: could not read the boot disk

Booting from Hard Disk...
Starting MS-DOS...


HIMEM is testing extended memory...done.

C:\>C:\DOS\SMARTDRV.EXE /X
C:\>a:

A:\>cat flag.txt
Bad command or file name

A:\>type flag.txt
pwn.college{USkPRtXd3nCSZPKf6MUGwdJLU8J.******************}

A:\>

Installing MS-DOS

use the provided floppies to install MS-DOS 6.22

since each floppy only holds 1.44mb, it was necessary to ship software in multiple floppies. MS-DOS 6.22 used three disks

load floppy in control panel from 0 to 2 and follow the wizard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SeaBIOS (version rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org)


iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+00F91FB0+00EF1FB0 CA00



Booting from Floppy...
Boot failed: could not read the boot disk

Booting from Hard Disk...
Starting MS-DOS...


HIMEM is testing extended memory...done.

C:\>C:\DOS\SMARTDRV.EXE /X
C:\>a:

A:\>type flag.txt
pwn.college{gF-MZnE1WGsp7qf0RwDh0DJDe2c.******************}

A:\>

autoexec.bat

Any boot process includes not just the loading of the OS itself, but its initialization. In DOS, this was elegantly handled by autoexec.bat, a script that specified what commands would run when the system booted.

you can access the various menu items using your Alt key (e.g., Alt-F to open the File menu).

modify the file from del to type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SeaBIOS (version rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org)


iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+00F91FB0+00EF1FB0 CA00



Booting from Floppy...
Boot failed: could not read the boot disk

Booting from Hard Disk...
Starting MS-DOS...


HIMEM is testing extended memory...done.

C:\>type c:\flag.txt
pwn.college{UBwCBfcUDi1W3W0o4QuVdHGB6rH.******************}

C:\>

doskey

Once doskey is loaded, you gain access to command history with up/down arrows and command line editing

上下箭头 :浏览命令历史记录 左右箭头 :在行内移动光标 F7 :在弹出窗口中显示命令历史记录 F9 :按编号选择命令

FUN FACT: doskey was introduced in MS-DOS 5.0 (1991) and was a game-changer for productivity. Before doskey, if you made a typo in a long command, you’d have to retype the whole thing!

edit and add doskey at top of autoexec.bat and quit relaunch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C:\>
C:\>echo pwn.college{
pwn.college{

C:\>echo 011NPLaz5mkZ
011NPLaz5mkZ

C:\>echo PSINV-xsu6g7
PSINV-xsu6g7

C:\>echo 7JI.********
7JI.********

C:\>echo **********}
**********}

C:\>

pwn.college{011NPLaz5mkZPSINV-xsu6g77JI.******************}

DOS Networking

install the packet drivers (make sure to specify the interrupt!) install and configure mtcp netcat to 192.168.13.37 port 1337 for the flag!

load pcnet floppy

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
PKTCHK   COM           856 01-28-92   9:58p
PKTMODE COM 1,728 01-28-92 9:58p
PKTMULTI COM 1,822 01-28-92 9:59p
PKTRING COM 2,420 01-28-92 9:59p
PKTSEND COM 2,346 01-28-92 9:59p
PKTSTAT COM 1,693 01-28-92 9:59p
PKTTRAF COM 6,019 01-28-92 9:59p
TERMIN COM 1,419 01-28-92 9:58p
TRACE COM 2,124 01-28-92 9:58p
WINPKT COM 3,617 01-28-92 9:59p
18 file(s) 116,641 bytes
192,512 bytes free

A:\PKTDRVR>pcntpk int=0x60
Packet driver for an PCNTPK, version 03.10
Packet driver skeleton copyright 1988-92, Crynwr Software.
This program is free software; see the file COPYING for details.
NO WARRANTY; see the file COPYING for details.

Packet driver is at segment 0BC8
Interrupt number 0xB (11)
I/O port 0xC000 (49152)
My Ethernet address is 52:54:00:12:34:56

A:\PKTDRVR>

load mtcp floppy

netcat to 192.168.13.37 port 1337 for the flag!

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
set mtcpcfg=ftp.cfg

# need edit yourself
A:\>type ftp.cfg
PACKETINT 0x60
IPADDR 192.168.13.100
NETMASK 255.255.255.0
GATEWAY 192.168.13.1
NAMESERVER 192.168.13.1
MTU 1500

A:\>set mtcpcfg=ftp.cfg

A:\>nc -target 192.168.13.37 1337
mTCP Netcat by M Brutman (mbbrutman@gmail.com) (C)opyright 2007-2025
Version: Jan 10 2025

Resolving server address - press Ctrl-Break to abort

Server resolved to 192.168.13.37 - connecting

Connected!

pwn.college{sig-JqvzdYNSstfBBdU6WAW8lln.******************}

Elapsed time: 0.00 Bytes sent: 0 Received: 60

A:\>