Cover Image

HTB MISC Challenges

 April 5, 2021    HackTheBox

The secret of a Queen

With a simple google search query "Queen cryptography" we find this image. After the decoding we get HTBRR THEBABINGTONPLT with a bit of formatting the flag is HTB{THEBABINGTONPLOT}.


misDIRection

The zip contains one folder for each letter. Some folders contain numbers, but all files have 0 byte length. Our first guess was the numbers might be indices where the letter appears in the flag. We quickly set up a large bash command which finds all files, converts the format to <number> <letter> sort these entries numerically, strip away the newlines and spaces and decode the result from base64. This successfully prints the flag.

find . \
  | grep '/./' \
  | tr '/' ' ' \
  | awk '{ print $3 " " $2; }' \
  | sort --numeric-sort \
  | grep -Eo ' .' \
  | tr -d '\n' \
  | tr -d ' ' \
  | base64 --decode


Canvas

The goal is to be able to find a login to a given website. The only program code seems to be in Canvas.zip/js/login.js. The code is heavily obfuscated. Many of the strings are encoded using '\x71' instead of 'q'. The last line seems to call String.fromCharCode which is often used to hide texts from being easily readable:

res=String['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](0x48,0x54,0x42,0x7b,0x57,0x33,0x4c,0x63,0x30,0x6d,0x33,0x5f,0x37,0x30,0x5f,0x4a,0x34,0x56,0x34,0x35,0x43,0x52,0x31,0x70,0x37,0x5f,0x64,0x33,0x30,0x62,0x46,0x75,0x35,0x43,0x34,0x37,0x31,0x30,0x4e,0x7d,0xa);

which es equivalent to:

res = String.fromCharCode(0x48,0x54,0x42,0x7b,0x57,0x33,0x4c,0x63,0x30,0x6d,0x33,0x5f,0x37,0x30,0x5f,0x4a,0x34,0x56,0x34,0x35,0x43,0x52,0x31,0x70,0x37,0x5f,0x64,0x33,0x30,0x62,0x46,0x75,0x35,0x43,0x34,0x37,0x31,0x30,0x4e,0x7d,0xa);

which again es equivalent to the flag:

res = "HTB{W3Lc0m3_70_J4V45CR1p7_d30bFu5C4710N}\n";


Blackhole

We get a .zip file, binwalk -e archive.zip extracts a image of Stefan Hawking, which in turn has a flag.txt that can be extracted steghide extract -sf hawking with the password hawking. We get a base64 string the can be easily decoded with "form base64" and "Rot 14" CyberChef 🎜


Eternal-Loop

We get a zip file with a password, fcrackzip shows us that the password is the filename of the unzipped file. With this information we can write a little script to automate the process.

unzzip() {
    zipfile="$1"
    next="$(unzip -Z1 "$zipfile" | head -n1)"
        if echo "$next" | grep "\.zip$"; then
            unzip -P "${next%%.*}" "$zipfile"
            unzzip "$next"
        fi
}
unzzip "37366.zip"

The last file is 6969.zip it seems to have different password, so we use fcrackzip -b 6969.zip -u -D -p ./rockyou.txt -v to find the new password letmeinplease. This reveals a SQLLite database with songs, strings DoNotTouch | grep "HTB" gives us the flag.


Longbottom's Locker

We get two images and a html file which asks for a password. Sadly at first glance the password check seems well designed with no obvious flaws which could be attacked. Even more sadly though the password is not Mimbulus mimbletonia. Clearly the two image files have to contain a hint to the password. Running binwalk on the included socute.jpg reveals a hidden file called donotshare. It contains a pickle serialized python object. Using the following snippet it can be decoded:

fd = open("donotshare", "rb")
x = pickle.load(fd)
print(x)

x consists of two layer nested arrays including tuples each with a character and a number:

[[(' ', 163)], [(' ', 1), ('.', 1), ('d', 1), ('8', 4), ('b', 1), ('.', 1), (' ', 12), ('d', 1), ('8', 3), (' ', 7), ('8', 3), (' ', 2), ('.', 1), ('d', 1), ('8', 4), ('b', 1), ('.', 1), (' ', 22), ('d', 1), ('8', 4), (' ', 2), ('8', 3), ('b', 1), (' ', 4), ('8', 3), (' ', 8), ('8', 7), ('b', 1), ('.', 1), (' ', 3), ('.', 1), ('d', 1), ('8', 4), ('b', 1), ('.', 1), (' ', 2), ('8', 9), (' ', 2), ('8', 9), (' ', 2), ('8', 3), (' ', 5), ('8', 3), (' ', 15)],
# and so on ...

Our first attempt to use the same algorithm as for the misDIRection challenge was unsuccessful. Instead each entry in the outer list corresponds to one line in the output. For each tuple in the inner lists the number represents how often the character has to be repeated. Running the following line reconstructs the full message, an ASCII art text:

>>> print(*[*map(lambda l: "".join([*map(lambda k: k[1] * k[0], l)]), x)], sep="\n")

 .d8888b.            d888       888  .d8888b.                      d8888  888b    888        8888888b.   .d8888b.  888888888  888888888  888     888               
d88P  Y88b          d8888       888 d88P  Y88b                    d8P888  8888b   888        888   Y88b d88P  Y88b 888        888        888     888               
888    888            888       888 888    888                   d8P 888  88888b  888        888    888 888    888 888        888        888     888               
888        888  888   888   .d88888 888    888        888  888  d8P  888  888Y88b 888        888   d88P 888    888 8888888b.  8888888b.  888     888 88888b.d88b.  
888  88888 888  888   888  d88" 888 888    888        888  888 d88   888  888 Y88b888        8888888P"  888    888      "Y88b      "Y88b 888     888 888 "888 "88b 
888    888 888  888   888  888  888 888    888 888888 Y88  88P 8888888888 888  Y88888 888888 888 T88b   888    888        888        888 888     888 888  888  888 
Y88b  d88P Y88b 888   888  Y88b 888 Y88b  d88P         Y8bd8P        888  888   Y8888        888  T88b  Y88b  d88P Y88b  d88P Y88b  d88P Y88b. .d88P 888  888  888 
 "Y8888P88  "Y88888 8888888 "Y88888  "Y8888P"           Y88P         888  888    Y888        888   T88b  "Y8888P"   "Y8888P"   "Y8888P"   "Y88888P"  888  888  888 

This password can be fed into the html page from the original zip to retrieve the flag.


Deterministic

We get a txt file, a start rule an end rule and a hint to a 1-byte key length XOR encryption. By examining the first 15 lines of the given txt file we can see the first number gives the source state, the middle number the value and the third value the target state. We thus simply have to parse all state transitions, start with the given start state 69420 and end when we reach state 999, you can find our simple python solution below.

Out of this we get a list of bytes which we output hex encoded so we can easily pass it to CyberChef 🎜 and use its builtin XOR key breaking method to brute force the XOR key. Afterwards we can use the found key 69 to decode the full message, again using CyberChef 🎜

from binascii import hexlify
rules = {}
with open("deterministic.txt", "r") as fd:
    for line in fd.readlines():
        try:
            src, val, target = map(int, line.split(" "))
            rules[src] = (target, val)
        except:
            pass # just ignore invalid lines lol

curr = 69420
values = []

while curr != 999:
    curr, val = rules[curr]
    values.append(val)

print(hexlify(bytearray(values)))


M0rsarchive

The downloaded file contains an image with a morse code and a zip file flag_999.zip. The morse code decodes to 9 which is the password of the given zipfile. This zip again has a morse code encoded zip file flag_998.zip, you can see where this is going. Thus we have to write a script which decodes the morse and unzips the file recusively. Here is our solution which unpacks all 1000 zip files until finally we extract a file called flag.

import os
import cv2
import subprocess

# src: https://gist.github.com/mohayonao/094c71af14fe4791c5dd
char2morse = {
  "0": "-----",
  "1": ".----",
  "2": "..---",
  "3": "...--",
  "4": "....-",
  "5": ".....",
  "6": "-....",
  "7": "--...",
  "8": "---..",
  "9": "----.",
  "a": ".-",
  "b": "-...",
  "c": "-.-.",
  "d": "-..",
  "e": ".",
  "f": "..-.",
  "g": "--.",
  "h": "....",
  "i": "..",
  "j": ".---",
  "k": "-.-",
  "l": ".-..",
  "m": "--",
  "n": "-.",
  "o": "---",
  "p": ".--.",
  "q": "--.-",
  "r": ".-.",
  "s": "...",
  "t": "-",
  "u": "..-",
  "v": "...-",
  "w": ".--",
  "x": "-..-",
  "y": "-.--",
  "z": "--..",
  ".": ".-.-.-",
  ",": "--..--",
  "?": "..--..",
  "!": "-.-.--",
  "-": "-....-",
  "/": "-..-.",
  "@": ".--.-.",
  "(": "-.--.",
  ")": "-.--.-"
}
morse2char = {}
for key in char2morse:
    morse2char[char2morse[key]] = key

def list_files_in_zip(filename):
    output = subprocess.check_output(['unzip', '-l', filename])
    lines = output.decode().split("\n")[3 : -3]
    files = list(map(lambda x: x.split(" ")[-1], lines))
    return files

def unzip_files(filename, password):
    os.system(f"unzip -P {password} -o {filename}")

def read_morse(filename):
    img = cv2.imread(filename, cv2.IMREAD_COLOR)
    b, r, g = img[1][1]

    height, width, channel = img.shape
    result = ""
    for y in range(1, height, 2):
        morse = ""
        wasHigh = False
        highLength = 0
        for x in range(1, width):
            if b == img[y][x][0] and r == img[y][x][1] and g == img[y][x][2]:
                if not wasHigh:
                    wasHigh = True
                    highLength = 0
                highLength += 1
            elif wasHigh:
                wasHigh = False
                if highLength == 1:
                    morse += '.'
                else:
                    morse += '-'

        if morse in morse2char:
            result += morse2char[morse]
        else:
            print("WARNING: unknown morse sequence", morse)

    return result

pw = "hackthebox"
zipfile = "M0rsarchive.zip"
imagefile = "pwd.png"

if __name__ == "__main__":
    while zipfile:
        print(zipfile, imagefile, pw)
        files = list_files_in_zip(zipfile)
        unzip_files(zipfile, pw)

        print(files)
        zipfile = list(filter(lambda x: x.endswith(".zip"), files))[0]
        imagefile = list(filter(lambda x: x.endswith(".png"), files))[0]
        pw = read_morse(imagefile)