Cyber Security Rumble - weirdcomputer

Fun CTF during Halloweekend. Played with Kernel Sanders and secured 15th place. Pretty decent for only a couple people playing :P

weirdcomputer - 22 solves

This is a weird computer… nc 2384 Author: lukas2511 | RedRocket

I was excited to try out this challenge because it was a challenge designed around a CHIP-8 emulator, and I have been working on my own CHIP-8 emulator the week prior. My solution was definitely very cheese and I might have used a forbidden CTF tatic.



After connecting to the server, we could either press ‘L’ to get the code running the challenge or send base64 encoded CHIP-8 ROM to the challenge. I immediately picked up that the code was a CHIP-8 emulator made in Python. I used this reference to check if the functionality of their emulator matched.

Inside the jump instruction block, I noticed they added something interesting:

# First nibble = 1
elif instr>>12 == 1:
    # Checking if the address provided equals the address in program counter
    if PC == (instr ^ 0x1000):
        print("endless loop detected, aborting. current framebuffer content:")
        for y in range(32):
            # Dumping data from frame buffer
            print("".join(list([bin(p)[2:].rjust(8, "0") for p in MEM[FB+y*8:FB+y*8+8]])).replace("0", " ").replace("1", "\u2588"))
    PC = instr ^ 0x1000

This gave us the ability to dump data from inside the program. The flag was loaded into a random address from 2500 to 3500.

start = random.randrange(2500, 3500)
start += start%2 # Ensures that start is an even number
for i, d in enumerate(open("flag.txt","rb").read().strip()):
    MEM[start] = (0x1000 | start) >> 8
    MEM[start+1] = (0x1000 | start) & 0xff
    MEM[start + 2 + i] = d

So the only way to display data from the program is using that functionality of jump into the PC. The frame buffer is set using the DRW instruction.

elif instr>>12 == 0xD:
    reg1 = instr>>8 & 0x0f
    reg2 = (instr >> 4) & 0x0F
    height = instr & 0xf
    X = V[reg1]
    Y = V[reg2]
    V[0xF] = 0
    for y in range(height+1):
        sprbyte = I + y
        for x in range(8):
            if (X+x) > 63:
            fbbyte = FB + ( (Y+y) * 8 ) + (X+x)//8
            newbit = int(bool(MEM[sprbyte] & (0x1 << (7-(x%8)))))
            if newbit:
                MEM[fbbyte] ^= newbit << (7 - (X+x) % 8)
                V[0xF] = 1

It does some fancy magic setting the memory, but the important part is that it starts loading sprites at I.

My plan of attack was this:

  1. The random range is small, so we can just load I somewhere in that range of 2500 to 3500. (I chose 3000)
  2. We use the DRW instruction to set up the frame buffer, and if we get lucky a part of the flag will be in that frame buffer
  3. We leak the data with JP to PC, so we cause a crash and dump the frame buffer
  4. The dumped frame buffer will output data in and , which we can convert into binary and get the characters of the flag
0xABB8 ; Set I to 0xbb8 (3000)
0xD01f ; DRW
0x1204 ; JP to PC

This is by no means the best way to do it, but I was lazy. After setting up a script to implement my plan, I ran into an issue. I got individual pieces of the flag, which I could piece together to form:


However, the flag ended with a bunch of A’s terminated by Y}. I had no way to determine the number of A’s in the flag with my attack method, I could only determine the minimum number of A’s that had to be at the end.

Forbidden Tatic - Literally Brute Forcing Flag Submission

Alright, ultimate meme time. I was lazy and didn’t want to start coding more CHIP-8 instructions. So the only other option – brute force flag.

I captured a valid flag submission request on their platform and wrote a script to send new requests that just changed the number of A’s. Worked like a charm.