QShell

2019-07-22 :: Jarrod Pas (ydob0n) from CyBRICS CTF Quals 2019

QShell is running on
nc spbctf.ppctf.net 37338

Grab the flag

When a challenge specifies a nc command it means that we should be making a TCP connection with that host and port. So, Lets see what happens when we run the command.

$ nc spbctf.ppctf.net 37338
█████████████████████████████████
█████████████████████████████████
█████████████████████████████████
█████████████████████████████████
████       █ █  █    █       ████
████ █████ █  ██  █  █ █████ ████
████ █   █ ███ █ █ █ █ █   █ ████
████ █   █ █   █████ █ █   █ ████
████ █   █ █         █ █   █ ████
████ █████ █ ███ █ █ █ █████ ████
████       █ █ █ █ █ █       ████
████████████████ █ ██████████████
███████ ██ ██ █ ██  ███   █  ████
███████   █ █   ██ █  ██ █   ████
████ ████    ████    █  █ ██ ████
█████  ████   █ ███   ███ ███████
████ █  █  █ ██  █  ██ █  ███████
████████ ██ █ █ ███  █████   ████
████ ██ ██      ██ ██████  █ ████
█████ █ ██████    █ █   █ ██ ████
████  █  █  ██ ███ █     █ █ ████
████████████   ██    ███ █   ████
████       ███ █ █ █ █ █ ██  ████
████ █████ ████ █ ██ ███    █████
████ █   █ ██ ███ █       █ █████
████ █   █ █ █ ████  █    █ █████
████ █   █ ██ █  █   █   ███ ████
████ █████ ███  ██ ████   ███████
████       █████ ███     ██  ████
█████████████████████████████████
█████████████████████████████████
█████████████████████████████████
█████████████████████████████████

.

We get a QR code which decodes to sh-5.0$, so it looks like we have a shell that operates on QR codes delimited by . characters.

Since the codes are in utf-8 text and all the decoders we could take images, we need to convert the text into an image. The white text pixels are Full Block U+2588 unicode runes and the black text pixels are space characters.

def qr_decode(text):
    from pyzbar.pyzbar import decode  # QR decoder
    from PIL import Image
    from itertools import chain

    rows = []
    for line in text.split('\n'):
        if not line or line[0] != WHITE:
            continue

        row = [c == WHITE for c in line]
        # QR decoder wants larger than 1x1 blocks
        row = list(chain(*zip(row, row)))
        rows.append(row)
        rows.append(row)

    size = (len(rows[0]), len(rows))
    im = Image.new('1', size)
    im.putdata(list(chain(*rows)))

    decoded = decode(im)[0]
    return decoded.data

We will also want to convert strings into QR codes.

def qr_encode(text):
    import qrcode

    qr = qrcode.QRCode(box_size=1, border=4)
    qr.add_data(text)
    qr.make(fit=True)
    im = qr.make_image().convert('1')

    rows = []
    row = []
    for i, c in enumerate(im.getdata()):
        if i != 0 and i % im.width == 0:
            rows.append(''.join(row))
            row = []
        row.append(WHITE if c else BLACK)

    return '\n'.join(rows)

Now that we have our encoder and decoder we can assemble it all into a loop to get us an interactive shell that looks like a normal one!

def qshell(host, port):
    conn = connect(host, port)
    conn.readuntil('.')  # skip the first since it is always 'sh-5.0$ '

    while 1:
        cmd = raw_input('$ ')
        cmd = bytearray(qr_encode(cmd), 'utf-8')

        try:
            conn.sendline(cmd)
            conn.sendline('.')
            out = conn.readuntil('.')
        except EOFError:
            return

        out = qr_decode(out.decode('utf-8'))
        print(out, end='')

Putting everything together with a little bit of preamble.

#!/usr/bin/env python
from __future__ import print_function
from pwn import *

WHITE = b'\xe2\x96\x88'.decode('utf-8')
BLACK = b' '.decode('utf-8')

def qr_decode(...):
    ...

def qr_encode(...):
    ...

def qshell(...):
    ...

qshell('spbctf.ppctf.net', 37338)

Now we get can go find the flag!

root@b1abea01ecf3:/ctf# ./qshell.py
[+] Opening connection to spbctf.ppctf.net on port 37338: Done
$ ls
1.py
2.py
docker-compose.yml
Dockerfile
flag.txt
log.txt
qweqwe.png
rex.txt
runserver.sh
run.sh
$ cat flag.txt
cybrics{QR_IS_MY_LOVE}
$
[*] Closed connection to spbctf.ppctf.net port 37338
root@b1abea01ecf3:/ctf#