Tuesday, July 23, 2019

CyBRICS CTF Quals 2019 - QShell

by Renato "shrimpgo" Pacheco

Description

QShell is running on nc spbctf.ppctf.net 37338

Grab the flag

When we connect on the server, we receive the following question:

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

.

That server waits our answer. So if you type anything, press Enter, type dot and press Enter again, it answers: list index out of range. So we know how to answer to server. When you decode this QRCode manually, it answers sh-5.0$, a prompt shell. Like the description title says (QShell), we have answer to server with another QRCode that decodes some interactive shell command. So diofeher (Diógenes) and me made this code below in Python 3. Enjoy it!

#!/usr/bin/python3

from PIL import Image
import numpy as np
from qrdecode import qrdecode
import pyqrcode
import nclib

def save_into_image(arr, filename, second=False):
    if not second:
        arr = np.array(arr[:-1])
        size = len(arr)
    else:
        arr = np.array(arr[1:-1])
        size = len(arr)
    arr = arr.reshape((size, size)).astype('uint8')*255
    im = Image.fromarray(arr)
    im.save(filename)

def qrcode_to_text(filename):
    qr = qrdecode.decode(filename=filename)
    return qr

def text_to_qrcode(text):
    img = pyqrcode.create(text)
    arr = img.text()
    resp = arr.replace('0', '\u2588').replace('1', ' ')
    return resp

def read_content(text):
    text = text.replace(b'\xe2\x96\x88', b'1')
    text = text.replace(b' ', b'0')
    return text.splitlines()


def parse(content, second=False):
    print('Length', len(content.splitlines()), 'x', len(content.splitlines()[0]))
    content = read_content(content)
    arr = convert_to_array(content)
    filename = 'out.png'
    save_into_image(arr, filename, second=second)
    print('Recv: ', qrcode_to_text(filename))

def convert_to_array(lines):
    resp = []
    for line in lines:
        try:
            resp.append([int(c) for c in line.decode()])
        except ValueError as e:
            pass
    return resp


class Client(object):
    def __init__(self):
        self.conn = nclib.Netcat(('spbctf.ppctf.net', 37338), verbose=False)

    def read(self, second=False):
        content = self.conn.read_until(b'.')
        parse(content, second=second)

    def send(self, text):
        req = text_to_qrcode(text)
        out = str.encode(req) + b'\n.\n'
        self.conn.send(out)


def main():
    client = Client()
    client.read()
    client.send('ls\n')
    client.read(second=True)

    client = Client()
    client.read()
    client.send('cat flag.txt\n')
    client.read(second=True)

main()

Results:

$ python3 qshell.py 
Length 35 x 99
Recv:  sh-5.0$ 
Length 64 x 0
Recv:  1.py
2.py
docker-compose.yml
Dockerfile
flag.txt
log.txt
qweqwe.png
rex.txt
runserver.sh
run.sh

Length 35 x 99
Recv:  sh-5.0$ 
Length 40 x 0
Recv:  cybrics{QR_IS_MY_LOVE}

Flag: cybrics{QR_IS_MY_LOVE}

Capture the Flag , Programming , Writeup