C:\CTF\GASLIGHT\CSS.EXE _□X

COMPILED SOURCE SHEETS

rev 131 solves by sportshead

Challenge

My new VM is Spectre-proof! It's also guaranteed race-condition free, and runs on every major OS[1]. Try it out!

[1] that supports Chrom(e|ium)

File given: compiled-source-sheets.tar.zst, a single vm.html (about 780 KB).

What this actually is

vm.html turns out to be x86CSS, a real project by Lyra Rebane: a working x86 CPU built entirely out of CSS, no JavaScript at all. State (registers, memory) lives in CSS custom properties, and computation happens through CSS's own cascade and selector-matching rules reacting to input, driven by holding down on-screen buttons. "Runs on every major OS that supports Chromium" is a joke about how niche this stunt is, not a real constraint on solving it.

Emulated RAM is stored as a giant flat list of custom properties, one per byte, shaped like this:

--__1m1234: var(--something, 87);

where 1234 is the memory address and 87 is that byte's current value (or a reference to another variable if it isn't a fixed literal yet). This is a snapshot baked straight into the static HTML and CSS, which means it can be read out of the file directly without running anything in a browser at all.

Reading the memory

If you read all the strings out of that memory dump, you get all the strings, and the key is kind of broken up across them:

import os
import re
path = 'where the file was'
with open(path, "r", encoding="utf-8") as f:
    html = f.read()
bytes_dict = {int(k): int(v) for k, v in re.findall(r'--__1m(\d+)\s*:\s*(?:var\([^,]+,\s*)?(\d+)\)?\s*;', html)}
mem = bytearray(max(bytes_dict.keys()) + 1)
for k, v in bytes_dict.items():
    mem[k] = v
for s in re.findall(b'[\x20-\x7e]{3,}', mem):
    print(s.decode('ascii'))

Running this surfaces readable strings scattered through memory, including the flag's static prefix split across a few chunks (some memory in between is either genuinely zero or only resolved once the CPU actually executes, so the pieces aren't one contiguous run):

guess th
e passwo
rd:
congrats
gaslight
CTF{ch3c
k_0ut_ly
ra-horse
!!_
nope :(

That reconstructs the flag's skeleton: gaslightCTF{ch3ck_0ut_lyra-horse!!_<password>}, plus UI strings confirming this is a password-check program running inside the emulated CPU, with a "guess the password" prompt and success or failure messages.

Filling in the password

You can do it the proper way and trace the actual CPU logic, or since guessing is faster, treat the check as a small system of constraints. The password logic resolves to bitwise relationships between 8 characters (x0..x7), each an XOR or AND relationship against a fixed constant, small enough to brute force directly over the printable ASCII range:

for x1 in range(32, 127):
    for x2 in range(32, 127):
        if (x2 ^ x1 == 8) and (x2 & x1 == 81):
            for x5 in range(32, 127):
                if (x2 ^ x5 == 17) and (x1 & x5 == 64):
                    x3 = x5 ^ 113
                    x7 = 127 - x3
                    x6 = x2 ^ 111
                    x4 = x7 ^ 118
                    x0 = x6 ^ 4
                    char_list = [x0, x1, x2, x3, x4, x5, x6, x7]
                    if all(32 <= c <= 126 for c in char_list):
                        print(f"gaslightCTF{{ch3ck_0ut_lyra-horse!!_{''.join(chr(c) for c in char_list)}}}")

This turns up two matches in the printable range. Only one is clean alphanumeric characters instead of stray punctuation, which is the one that looks like an actual generated password rather than a coincidence.