huge commit

This commit is contained in:
2026-03-09 21:44:25 +01:00
parent ab2e537520
commit d1017bae51
441 changed files with 3438154 additions and 1363 deletions

View File

@@ -0,0 +1,2 @@
win98_extracted
forge_attempts

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@@ -0,0 +1,25 @@
import os
def brute_force_forge(bmp_path, forge_bin_path):
with open(bmp_path, 'rb') as f:
header = f.read(54)
pixels = f.read()
with open(forge_bin_path, 'rb') as f:
forge = f.read()
os.makedirs("forge_attempts", exist_ok=True)
# We try shifting the starting point of the Forge key
# Most common offsets are 0, 44 (WAV header), or small alignments
for offset in range(0, 100):
result = bytearray()
for i in range(len(pixels)):
# Cycle through the forge starting at the 'offset'
result.append(pixels[i] ^ forge[(i + offset) % len(forge)])
with open(f"forge_attempts/offset_{offset}.bmp", "wb") as f:
f.write(header + result)
print("[*] Generated 100 attempts. Look for the one that isn't static.")
brute_force_forge("./carved_bmps/bmp/00146504.bmp", "./carved_wavs/wav/wav_diff.raw")

View File

@@ -0,0 +1,59 @@
import struct
import sys
import os
def strict_bmp_carve(dump_file):
print(f"[*] Scanning {dump_file} for valid BMP headers...")
with open(dump_file, 'rb') as f:
data = f.read()
# Create an output directory
os.makedirs("strict_bmps", exist_ok=True)
count = 0
offset = 0
while True:
# Find the next 'BM' signature
offset = data.find(b'BM', offset)
if offset == -1:
break
# Ensure we have enough data for a header
if offset + 14 > len(data):
break
header = data[offset:offset+14]
# Unpack the header:
# 2s (BM), I (Size), H (Res1), H (Res2), I (Data Offset)
magic, size, res1, res2, data_offset = struct.unpack('<2sIHHI', header[:14])
# STRICT VALIDATION:
# 1. Reserved bytes must be 0
# 2. File size must be reasonable (e.g., between 1KB and 10MB)
# 3. Data offset must be valid (usually 54 bytes for a standard BMP)
if res1 == 0 and res2 == 0 and (1024 < size < 10000000) and (54 <= data_offset <= 1024):
print(f"[+] Valid BMP found at offset {hex(offset)}")
print(f" Size: {size} bytes | Data Offset: {data_offset}")
# Extract exactly 'size' bytes
bmp_data = data[offset:offset+size]
out_name = f"strict_bmps/image_{hex(offset)}_{size}bytes.bmp"
with open(out_name, 'wb') as out_f:
out_f.write(bmp_data)
count += 1
# Move forward safely
offset += 2
print(f"\n[*] Finished. Carved {count} strict BMP files into 'strict_bmps/' directory.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 carve_bmps_strict.py <dosbox_ram.raw>")
else:
strict_bmp_carve(sys.argv[1])

View File

@@ -0,0 +1,25 @@
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File
Foremost started at Sun Mar 1 16:07:24 2026
Invocation: foremost -t bmp -i dosbox_ram.raw -o carved_bmps
Output directory: /home/cato/ctf/2026/srdnlen_quals/forensic/chapter2/second_attempt/carved_bmps
Configuration file: /etc/foremost.conf
------------------------------------------------------------------
File: dosbox_ram.raw
Start: Sun Mar 1 16:07:24 2026
Length: 128 MB (134221824 bytes)
Num Name (bs=512) Size File Offset Comment
0: 00103872.bmp 43 KB 53182464 (182 x 237)
1: 00104770.bmp 5 KB 53642672 (310 x 35)
2: 00146504.bmp 576 KB 75010048 (512 x 384)
Finish: Sun Mar 1 16:07:24 2026
3 FILES EXTRACTED
bmp:= 3
------------------------------------------------------------------
Foremost finished at Sun Mar 1 16:07:24 2026

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@@ -0,0 +1,27 @@
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File
Foremost started at Sun Mar 1 17:29:23 2026
Invocation: foremost -t wav -i dosbox_ram.raw -T wav
Output directory: /home/cato/ctf/2026/srdnlen_quals/forensic/chapter2/second_attempt/output_Sun_Mar__1_17_29_23_2026
Configuration file: /etc/foremost.conf
------------------------------------------------------------------
File: dosbox_ram.raw
Start: Sun Mar 1 17:29:23 2026
Length: 128 MB (134221824 bytes)
Num Name (bs=512) Size File Offset Comment
0: 00014799.wav 36 B 7577580
1: 00038872.wav 676 KB 19902464
2: 00056278.wav 36 B 28814684
3: 00072813.wav 676 KB 37280640
4: 00099048.wav 1 KB 50712576
Finish: Sun Mar 1 17:29:23 2026
5 FILES EXTRACTED
rif:= 5
------------------------------------------------------------------
Foremost finished at Sun Mar 1 17:29:23 2026

View File

@@ -0,0 +1,13 @@
def xor_wavs(wav1, wav2, out_file):
with open(wav1, 'rb') as f1, open(wav2, 'rb') as f2:
d1 = f1.read()[44:] # Skip headers
d2 = f2.read()[44:]
# XOR the two audio streams
res = bytes(a ^ b for a, b in zip(d1, d2))
with open(out_file, 'wb') as f:
f.write(res)
print(f"[+] XORed WAVs into {out_file}")
xor_wavs("00038872.wav", "00072813.wav", "wav_diff.raw")

View File

@@ -0,0 +1,21 @@
def claim_the_armory(bmp_path, forge_bin_path, output_path):
# Load the Armory (Relic 1)
with open(bmp_path, 'rb') as f:
bmp_header = f.read(54)
bmp_pixels = f.read() # This is 589,824 bytes
# Load the Forge (The result of XORing the WAVs)
with open(forge_bin_path, 'rb') as f:
# We only need as much of the forge as we have pixels
forge_key = f.read(len(bmp_pixels))
print(f"[*] Applying the Forge to the Armory...")
# XOR the noise (fleeting air) with the system map (volatile stone)
flag_pixels = bytes(a ^ b for a, b in zip(bmp_pixels, forge_key))
with open(output_path, 'wb') as f:
f.write(bmp_header + flag_pixels)
print(f"[+] The Armory is unlocked: {output_path}")
claim_the_armory("./carved_bmps/bmp/00146504.bmp", "./carved_wavs/wav/wav_diff.raw", "FLAG_REVEALED.bmp")

View File

@@ -0,0 +1,27 @@
import math
def shannon_entropy(data):
if not data: return 0
entropy = 0
for x in range(256):
p_x = float(data.count(x))/len(data)
if p_x > 0:
entropy += - p_x * math.log(p_x, 2)
return entropy
relic_1 = bytes.fromhex("8E97A917696BEB846B1937307F99E18F")
file_path = "./dosbox_ram.raw"
with open(file_path, "rb") as f:
data = f.read()
# Scan in 16-byte chunks
for i in range(0, len(data) - 16, 1):
chunk = data[i:i+16]
if chunk == relic_1:
print(f"Relic 1 found at offset: {hex(i)}")
continue
# We look for high entropy (typically > 3.5 for a 16-byte random-ish string)
if shannon_entropy(chunk) > 3.5:
# Check if it's surrounded by nulls or repetitive data (common for keys in RAM)
print(f"Potential Relic 2 at {hex(i)}: {chunk.hex().upper()}")

View File

@@ -0,0 +1,24 @@
import re
def find_win98_processes(ram_file):
with open(ram_file, 'rb') as f:
data = f.read()
print(f"[*] Searching for active process signatures...")
# Windows 98 processes often have a characteristic 'magic' or
# sit near their own filename in a specific K32 object structure.
# We look for .EXE strings and check the surrounding 128 bytes.
matches = re.finditer(b"[A-Za-z0-9_]{1,8}\.EXE\x00", data)
seen = set()
for m in matches:
name = m.group().decode('ascii', errors='ignore')
if name not in seen:
# Check if this looks like a process entry (usually preceded by pointers)
# In Win9x, the 'Process Database' is often in the 0x80000000+ range
# (which is mapped into our 128MB at different physical offsets).
print(f"[+] Found potential process: {name} at {hex(m.start())}")
seen.add(name)
find_win98_processes("dosbox_ram.raw")

View File

@@ -0,0 +1,34 @@
def find_the_lock(bmp_path, fnd_path):
with open(bmp_path, 'rb') as f:
target_header = f.read(54)
target_pixels = f.read()
with open(fnd_path, 'rb') as f:
fnd_data = f.read()
print(f"[*] Attempting to align the Forge...")
# We search for a 16-byte known plaintext: the start of a second BMP
# or the string 'srdnlen{'
for offset in range(0, len(fnd_data) - len(target_pixels), 1):
# We check the first 8 bytes of the XOR result at this offset
# If it matches 'srdnlen{', we found it!
sample = bytes(target_pixels[i] ^ fnd_data[offset + i] for i in range(8))
if b"srdnlen{" in sample:
print(f"[!!!] FLAG KEY FOUND AT OFFSET: {hex(offset)}")
# Perform full XOR
result = bytes(target_pixels[i] ^ fnd_data[offset + i] for i in range(len(target_pixels)))
with open("REVEALED_WEAPON.txt", "wb") as out:
out.write(result)
return
print("[-] No direct string match. Trying visual alignment...")
# If no string found, generate a few blocks to check for patterns
import os
os.makedirs("visual_align", exist_ok=True)
for jump in range(0, 10000, 1000):
res = bytes(target_pixels[i] ^ fnd_data[jump + i] for i in range(len(target_pixels)))
with open(f"visual_align/jump_{jump}.bmp", "wb") as out:
out.write(target_header + res)
find_the_lock("./carved_bmps/bmp/00146504.bmp", "./relic_fnd.bin")

View File

@@ -0,0 +1,31 @@
# The "Relic" you found might be the start or a key within the buffer
RELIC_1 = bytes.fromhex("8E97A917696BEB846B1937307F99E18F")
PROCESS_DUMP = "./chall.dmp"
def find_armory():
with open(PROCESS_DUMP, "rb") as f:
# Map the file or read in chunks if it's huge
data = f.read()
# Search for the first relic
offset1 = data.find(RELIC_1)
if offset1 == -1:
print("Relic 1 not found in process dump.")
return
print(f"Relic 1 found at process offset: {hex(offset1)}")
# Now search for a 'twin' relic at common VRAM offsets (2MB, 4MB, 8MB)
for gap in [0x200000, 0x400000, 0x800000]:
offset2 = offset1 + gap
if offset2 + 16 < len(data):
# Try XORing the first 2 bytes of the two relics
# We are looking for "MZ" (0x4D 0x5A)
header_check = (data[offset1] ^ data[offset2], data[offset1+1] ^ data[offset2+1])
if header_check == (0x4D, 0x5A):
print(f"ARMORY FOUND! XOR match at {hex(offset1)} and {hex(offset2)}")
return offset1, offset2
return None
# If found, you can then extract the weapon
# weapon = bytes(a ^ b for a, b in zip(data[offset1:offset1+SIZE], data[offset2:offset2+SIZE]))

View File

@@ -0,0 +1,48 @@
import sys
import os
def find_ghost_relics(ram_dump, original_bmp):
target_pixel_size = 589824 # 512 * 384 * 3
with open(original_bmp, 'rb') as f:
f.seek(54) # Skip header
original_pixels = f.read()
with open(ram_dump, 'rb') as f:
ram_data = f.read()
print(f"[*] Scanning RAM for blocks of size {target_pixel_size}...")
# We search in 4KB increments (standard memory page alignment)
for offset in range(0, len(ram_data) - target_pixel_size, 4096):
# Skip the offset where we already found the first BMP
# (Assuming your first BMP was at 0x4789000 based on previous output)
if offset == 0x4789000:
continue
candidate = ram_data[offset : offset + target_pixel_size]
# Heuristic: Check if the block is "pixel-like"
# Often, XORed or raw buffers have a high frequency of 0x00 or 0xFF
# or share a similar byte distribution to the original.
null_count = candidate.count(b'\x00')
# If the block is mostly empty or mostly full, it might be the 'Forge'
# (the background layer or the XOR mask)
if null_count > (target_pixel_size * 0.1): # If > 10% is null
print(f"[!] Potential relic found at {hex(offset)} (Null-ratio: {null_count/target_pixel_size:.2%})")
# Save it as a raw bin and also a reconstructured BMP
out_bmp = f"relics/relic_{hex(offset)}.bmp"
# Reconstruct with the header from the original
with open(original_bmp, 'rb') as h:
header = h.read(54)
with open(out_bmp, 'wb') as b:
b.write(header + candidate)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python3 relic_hunter.py <dosbox_ram.raw> <original.bmp>")
else:
find_ghost_relics(sys.argv[1], sys.argv[2])

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@@ -0,0 +1,58 @@
import sys
import os
def fine_tune_xor(target_path, key_path):
with open(target_path, 'rb') as f:
t_header, t_pixels = f.read(54), f.read()
with open(key_path, 'rb') as f:
k_pixels = f.read()[54:]
os.makedirs("tunes", exist_ok=True)
# Try shifting the starting byte of the key
for shift in range(0, 10):
result = bytearray()
for i in range(len(t_pixels)):
result.append(t_pixels[i] ^ k_pixels[(i + shift) % len(k_pixels)])
with open(f"tunes/shift_{shift}.bmp", "wb") as f:
f.write(t_header + result)
print("[*] Generated 10 shifts in /tunes. Look for the one where lines are perfectly vertical.")
def xor_with_repeating_key(target_path, key_path, output_path):
print(f"[*] Loading Target: {target_path}")
print(f"[*] Loading Key: {key_path}")
with open(target_path, 'rb') as f:
target_header = f.read(54)
target_data = f.read()
with open(key_path, 'rb') as f:
f.read(54) # Skip the 44KB BMP's header
key_data = f.read()
print(f"[*] Target Data: {len(target_data)} bytes")
print(f"[*] Key Data: {len(key_data)} bytes")
# Perform Repeating XOR
# We use a generator to loop the key indefinitely
def key_generator(k):
while True:
for byte in k:
yield byte
gen = key_generator(key_data)
print("[*] Forging the weapon (XORing)...")
result = bytearray()
for b in target_data:
result.append(b ^ next(gen))
with open(output_path, 'wb') as f:
f.write(target_header + result)
print(f"[+] SUCCESS! Relic forged: {output_path}")
if __name__ == "__main__":
# target = 589KB BMP, key = 44KB BMP
xor_with_repeating_key("./carved_bmps/bmp/00146504.bmp", "./carved_bmps/bmp/00104770.bmp", "FINAL_ARMORY_FLAG.bmp")
#fine_tune_xor("./carved_bmps/bmp/00146504.bmp", "./carved_bmps/bmp/00103872.bmp")