Note to fellow-HTBers: Only write-ups of retired HTB machines or challenges are allowed.

Machine info

QuickR [by hfz]
Let’s see if you’re a QuickR soldier as you pretend to be
Difficulty: Medium (40 points)
Release: 16 April 2020

Recon

Browsing or cURL’ing the endpoint, we get a QR code in our terminal.

$ curl docker.hackthebox.eu:32169
                                                    
   ___               _          __       _______    
 .'   `.            (_)        [  |  _  |_   __ \   
/  .-.  \  __   _   __   .---.  | | / ]   | |__) |  
| |   | | [  | | | [  | / /'`\] | '' <    |  __ /   
\  `-'  \_ | \_/ |, | | | \__.  | |`\ \  _| |  \ \_ 
 `.___.\__|'.__.'_/[___]'.___.'[__|  \_]|____| |___|
                                                    
                                                    
[*] Hello there! Let's see if you are an QuickR soldier, you got only 3 seconds!
                                                    
<Prints QR code>                                    
                                                    
[+] It's important to realise that this is, in a real sense, an illusion: you simply need the true machine value.
[!] Decoded string: [-] Wrong!
QR code in a terminal

There’s also the notion of something being Wrong and having a 3 second limit.

Scanning this QR code with a smartphone, we get an equation:
48.530023014695644 - 58.8774162315505 / 214.69427403011673 =

So we can assume the challenge requires us to perform the following steps:

  1. open connection, read output
  2. extract QR code
  3. decode QR code
  4. calculate equation
  5. return answer to endpoint
  6. retrieve flag

Tackling the challenge

We definitely need to automate this whole challenge, since we can’t manually retrieve the QR code, scan it with a smartphone, solve the equation and return the solution.

Capture the QR code

The hardest part of this challenge was finding a way to convert the QR code in the terminal to one that we can scan via a script.
I first looked into a solution to screengrab the terminal, but didn’t immediately find a solution that worked for me.
Next tactic was to read the output and build an image in a kind of search and replace way.

Note that ^[ is actually a control character Ctrl + Esc followed by a square bracket.
Typing this in a console might not work because the terminal captures the control command.
I guess it might also work using \e or \033 instead, but I haven’t tried.

def qrimg(lines ,filename):
    font = ImageFont.truetype('DejaVuSansMono.ttf')
    img=Image.new("1", (102,102), 1)
    draw = ImageDraw.Draw(img)
    y = 0
    for line in lines:
        line = line.decode("utf-8", "backslashreplace")
        line = re.sub(r"^[ \t]*","", line)
        line = re.sub(r"\[41m  \[0m","*", line)
        line = re.sub(r"\[7m  \[0m","-", line)

        x = 0
        for c in line:
            if c == "*":
                draw.point((x, y), 0)
                #draw.point((x, y+1), 0) # drawing 2x2 pixels
                #draw.point((x+1, y), 0) # drawing 2x2 pixels
                #draw.point((x+1, y+1), 0) # drawing 2x2 pixels
            #elif c == "-":
            #    draw.point((x, y), 1) # Adding white pixel is not needed
            x+=1
            #x+=2 # drawing 2x2 pixels
        y += 1
        #y += 2 # drawing 2x2 pixels
        draw = ImageDraw.Draw(img)
    img.save(filename)
    #print("x: {}  y: {}".format(x, y))

    print("[+] Wrote QR code image to {}".format(filename))

Scan the QR code

Scanning the QR code from an image is easier, since there are Python modules readily available for this.

def qrdecode(filename):
    result = ""

    qr = qrtools.QR()
    if qr.decode(filename):
        #conn.send(qr.data)
        result = qr.data

    print("[+] QR Code decodes to: '{}'".format(result))
    return result

Solving the equation

Easy peasy

def solve(equation):
    equation = re.sub("x", "*", equation)
    equation = re.sub("=", "", equation)
    equation = re.sub(r"^[ \t]*", "", equation) # trim whitespace, might not be necessary
    equation = re.sub(r"[ \t]*$", "", equation)) # trim whitespace, might not be necessary
    print("[+] Cleaned equation: '{}'".format(equation))

    result = eval(equation)
    print("[+] Equation results to: '{}'".format(result))

    return result

Send answer

Finally we send our answer back using the same socket and retrieve the flag.

Make sure you end your output with a newline.
This almost got me pulling my hair out.

conn.sendline("{}".format(result))
print("[+] Send '{}' to {}:{}".format(result, hostname, port))

answer = conn.recvall().decode()
print(answer)

Solution

Combining all of this together and running our final script, we get:

$ ./QuickR.py 
[+] Opening connection to docker.hackthebox.eu on port 31607: Done
[+] Wrote QR code text to quickr.txt
[+] Wrote QR code image to quickr.png
[+] QR Code decodes to: '74.22252072924252 - 5.833454784680273 / 379.68114191784696 = '
[+] Cleaned equation: '74.22252072924252 - 5.833454784680273 / 379.68114191784696'
[+] Equation results to: '74.20715664043048'
[+] Send '74.20715664043048' to docker.hackthebox.eu:31607
[+] Receiving all data: Done (263B)
[*] Closed connection to docker.hackthebox.eu port 31607
    

[+] It's important to realise that this is, in a real sense, an illusion: you simply need the true machine value.
[!] Decoded string: [+] Congratulations! Here is your flag: HTB{@lriGh7_1_tH1nK_y0u`r3_QuickR_s0ldi3r}

And this is the full script:

#!/usr/bin/env python3

### Solution for QuickR by hfz on HackTheBox.eu
### By FlatMarsSociet
### See https://sequr.be/blog/ for more info

import re
import PIL
from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw
from pwn import *
import qrtools


def qrtxt(lines, filename):
    with open(filename,"w") as f:
        for line in input:
            line = line.decode("utf-8", "backslashreplace")
            f.write("{}\n".format(line))
    print("[+] Wrote QR code text to {}".format(filename))


def qrimg(lines ,filename):
    font = ImageFont.truetype('DejaVuSansMono.ttf')
    img=Image.new("1", (102,102), 1)
    draw = ImageDraw.Draw(img)
    y = 0
    for line in lines:
        line = line.decode("utf-8", "backslashreplace")
        line = re.sub(r"^[ \t]*","", line)
        line = re.sub(r"\\[41m  \\[0m","*", line)
        line = re.sub(r"\\[7m  \\[0m","-", line)

        x = 0
        for c in line:
            if c == "*":
                draw.point((x, y), 0)
                #draw.point((x, y+1), 0) # drawing 2*2 pixels
                #draw.point((x+1, y), 0) # drawing 2*2 pixels
                #draw.point((x+1, y+1), 0) # drawing 2*2 pixels
            #elif c == "-":
            #    draw.point((x, y), 1) # Adding white pixel is not needed
            x+=1
            #x+=2 # drawing 2*2 pixels
        y += 1
        #y += 2 # drawing 2*2 pixels
        draw = ImageDraw.Draw(img)
    img.save(filename)
    #print("x: {}  y: {}".format(x, y))

    print("[+] Wrote QR code image to {}".format(filename))


def qrdecode(filename):
    result = ""

    qr = qrtools.QR()
    if qr.decode(filename):
        #conn.send(qr.data)
        result = qr.data

    print("[+] QR Code decodes to: '{}'".format(result))
    return result


def solve(equation):
    equation = re.sub("x", "*", equation)
    equation = re.sub("=", "", equation)
    equation = re.sub(r"^[ \t]*", "", equation)
    equation = re.sub(r"[ \t]*$", "", equation)
    print("[+] Cleaned equation: '{}'".format(equation))

    result = eval(equation)
    print("[+] Equation results to: '{}'".format(result))

    return result


def output(lines):
    for line in lines:
        # line = line.decode("utf-8", "backslashreplace")
        print("{}".format(chr(line)), end = '')

hostname = 'docker.hackthebox.eu'
port = 31607

conn = remote(hostname, port)
conn.recvuntil("you got only 3 seconds!")
conn.recvlines(3)
input = conn.recvlines(51)

qrimage = "quickr.png"
qrtext = "quickr.txt"
qrtxt(input, qrtext)
qrimg(input, qrimage)
equation = qrdecode(qrimage)
result = solve(equation)

conn.sendline("{}".format(result))
print("[+] Send '{}' to {}:{}".format(result, hostname, port))

answer = conn.recvall().decode()
print(answer)

conn.close()