NorthSec 2021 Badge Writeup
Solving all 10 flags + easter eggs within the NorthSec 2021 CTF badge
To learn more about the badge itself and the development behind the scenes I would recommend watching this YouTube video.
Although NorthSec was remote for the second year in a row the badges still formed the bases of a CTF. However the badge's challenges were completely unrelated to the actual competitive CTF which was one of the highlights from the event as far as I was concerned.
The badge itself hold 10 flags which is really impressive given the memory on the chip and the engineering behind it all. The different categories of the flag are trivia, general problem solving, and reverse engineering. All challenges are independent from each other, so you can look for flags in any order. The lore behind the badge follows the theme of the 2021 northsec: medieval
This was also my first time doing any type of hardware hacking. Because of this I have to thank vicious, Padraignix, and Eric Hogue for giving me gentle hints without spoiling any of the fun. Padraignix and Eric have writeup's of their own. If there is something I explain poorly go check them out and their approach.
Flag #1 / Intro
The first flag is one of those "warmup" flags. There is visibly a chest in the upper left hand side of the screen, visible upon booting up the game.
This is our first flag
You submit the flags through the FLAGBOT on the official NorthSec Discord server and by some magic it syncs up to your badge.
Flag #8 / Toms Hut Part 1
I spent a good 20 minutes talking to all the NPC's and going around the map. Eventually I walked into a house presumably owned by someone named Tom.
Inside Tom's hut we are greeted with the following message
I also remember seeing a house with a WiFi symbol on it.
Once you enter that house you see the following:
Unfortunately we're only allowed to toggle the WiFi on and off without any connection to any network being established. Thankfully this badge has a CLI feature that I used to connect to the WiFi using the serial connection.
We'll use dmesg to see if the nsec badge is detected and sure enough it is!
So we'll just connect to it using the screen command to connect to the GUI. Below we see what commands we've got to play with.
Using the commands above we connected ourselves to our home WiFi
Now when you go back to Tom's hut you're welcomed with the following message:
So lets go ahead and get those files and see what Tom's got in store for us.
We will start with the rc101 challenge then solve the rc102. The rc101.zip contains a .elf in a Tensilica Xtensa format. This will be a first for me because I've never touched Xtensa architecture. After a fair amount of googling I was able to find a Ghidra module that could read this type of format.
With this module loaded, everything ran smoothly. So I went ahead and loaded up the
app_main from this we can get a rough idea of whats happening.
Its actually quite straight forward
- 0x400d2360 user supplied input
- 0x400d2381 makes a check to see input is whats expected
- 0x400d2386 if expected, succeed, then load flag in register
- 0x400d2390 if fail, jump to try_again
The Verify function is is kinda long. Essentially our offset is the flag. Just from looking at this function there is a glaring pattern present. If you stare at it for 30 seconds you'll see that there is a jump of 0x4 each time which is then validating our user input (a1). The validation is being performed against values loaded in the a8 to a15 registers.
So what we'll do is take the register values and build it back up sequentially one at a time... maybe there is a sick 1337 way to do it in Ghidra but I did in manually in a notepad. I'm sure there is a smarter quicker way to do this but this will have to do.
This leaves you with
11a922186c46496eb1317c128b361720 which we'll submit to the challenge as user input to receive our flag.
So the flag is:
Flag #9 / Toms Hut Part 2
Its not over yet! We still have another reverse challenge to do. So after some light recon we're going to be dealing with more or less the same architecture.
Lets jump in and look at our main.
For a second I thought that I loaded the wrong app because the main looks exactly the same but the verify function is very different.
The way I understand it is that something will move something from an offset of ctx to the register a9. So you are comparing a9 with the ascii of a8 before counting down to the next block just like before. Therefore we're going to move the asii value of a8 then compare both, if they're equal we jump to the next block, and if not it makes a jump, resets ctx and return from the function. This in my head is easier to understand then the previous challenge's verify function.
- move input from ctx to a9
- a8 = 0x66 = f
- compare a9 with 0x66
- if equal then move onto next block
This leaves us with
f219e6cdb1fa4a48b160d00d61118f93 which we'll submit to the challenge as user input to receive our flag.
So the flag is:
Flag #2 / Konami
I'm actually impressed I thought of this one. After solving the first 3 flags I didn't really know what to solve next but there was a chest in the top left corner of the map that looked inaccessible.
For a while I thought I needed to find a bug with the collision detection but then I remembered this was a video game! And EVERY video game has cheats!... Right? At first I tried the most popular "walking through walls cheats" but had no success with those. However when I googled the most popular cheat codes did I find success.
Perhaps its because its the most popular one? But the Konami/Contra code opened the door on the first try. I'm not quite sure if a different cheat codes would have also worked but once entered a magical door will open!
Just go through the door to the chest to receive the second flag!
Flag #5 / Cyber Punk
I kinda got this flag by accident. Right after getting flag #2 I randomly also got flag #4 and I was trying to figure out where it came from. I investigated by talking to the 10 or so NPC's scattered around the map to see if their text options had changed. Eventually I had to chase down the NPC with pink hair named "Punk_" (seen below).
Personally this NPC (like the dog) never stopped walking and my fat fingers stumbled but eventually I got to his ass.
When you talk to this absolute chad he's all about making you prove yourself to him. You need to be cool and have a lot of money, and be 1337... as seen below.
Since I'm none of the above I walked away a little too quickly, hitting the down key and triggering the following.
With that we get flag #5 I still don't know how I got flag #4 though... But we'll figure it out eventually.
Flag #7 / 20:20 Vision
Due to the reset button being at the back of the badge accidentally rebooted the game multiple time as it laid flat on my desk.
In doing so I noticed the following message:
So I started going through the alphabet tab-completing like a maniac and eventually it completed to the_sword_of_azeroth! (a world of warcraft reference)
And with this we have the 7th flag
Flag #6 / Le Lack Du Quack
If you walk around the map you'll run into a pretty mean looking duck chilling in a pond.
He doesn't say much besides quack.
But I noticed that as soon as I started interacting with him that my CLI segfault. I connected to the CLI in a few different ways as seen below.
minicom --device /dev/ttyUSB0 screen /dev/ttyUSB0 115200 tio /dev/ttyUSB0 picocom -b 115200 /dev/ttyUSB0
But they all left me with a similar shell.
It appears that there was output before it segfaults we see it display "quickquickquick" which kinds sounds like "quack", the noise a duck would make. So I went ahead and piped this into a file.
At first I though my terminal was broken but I kept getting the same weird output. I ended up installing a new VM but nothing changed.
Its only when I decided properly check the end of those lines that I realize maybe there might be a message here...
After removing all the gunk I was left with this nice poem.
quackquackquickquack quickquickquack quickquack quackquickquackquick quackquickquack quickquick quackquack quickquack quackquickquick quickquickquack quackquickquackquick quackquickquack quack quackquackquack quackquackquick quick quack quackquickquickquick quickquackquick quick quickquack quackquickquick quickquickquack quickquickquick quick quack quickquickquickquick quickquick quickquickquick quickquickquackquick quickquackquickquick quickquack quackquackquick quackquickquickquick quackquick quickquickquickquack quackquackquickquick quackquickquack quickquack quickquackquack quackquack quickquickquick quackquickquackquack quickquickquick
Now there are only two words present quick and quack and some spaces. So maybe the spaces before the newlines are a fluke and they're not suppose to exist. If they are then I'm thinking morse code could be a solution. Also maybe also we can turn the 0's and 1's into text.
Now when I was talking to some of the NPC's one of them really triggered me by what he said...
Everyone knows that PHP never left, and will never be die. So I wrote a little script in PHP to get us the output in 0's and 1's and later the mores code.
<?php $data = "quackquackquickquackquickquickquackquickquackquackquickquackquickquackquickquackquickquickquackquackquickquackquackquickquickquickquickquackquackquickquackquickquackquickquackquackquackquackquackquackquackquickquickquackquackquickquickquickquickquackquickquickquickquackquackquickquickquickquickquackquickquickquickquickquackquickquickquickquickquickquickquickquickquickquickquickquackquickquickquackquickquickquickquackquackquackquickquackquickquickquickquackquickquickquickquickquackquackquackquickquickquackquickquackquickquackquickquackquackquackquackquickquickquickquackquickquackquackquickquickquick" ; $data = str_replace( "quick" , "0" , $data); $data = str_replace( "quack" , "1" ,$data); echo $data = str_replace( " " , "." ,$data); ?>
This left us with two combinations:
This didn't really amount to much and cyber-chef sent me the following sms.
Its at this point that I realized that perhaps my gut feeling was right and went ahead to confirm my initial suspicion. The new lines did act as spaces after all. Morse code needs spaces so I made some tweaks to my robust PHP code and obtained the following message as seen below:
"QUACK IM A DUCK TO GET BREAD USE THIS FLAG BNVZKAWMSYS" so the flag is therefore
Flag #3 / A Fisherman's Downfall
It at around this time that I start feeling like I'm going to have to swim in the deep end of the pool. Again I have no real hardware reversing experience. One thing I do want to do is extract all the textures from the application and see if I can reconstruct the map. But before we get to that there is a female NPC which gives us our next hint to a flag.
Whats important to note is that the NPC's don't ask questions they all speak in funny infosec anecdotes.
At this point I thought that I needed to render the other side of the shore and that maybe a flag was printed on it. In case it isn't obvious as far as I know the shore isn't visible / inaccessible by our character. Also we cant swim. I was a little stumped at this point so I had to ask for a light hint.
After some light googling I fell on this GitHub which looked to be exactly what I was looking for. As can be seen below I had a lot of issues in dumping the flash. It didn't seem to matter what I ran or how I ran it...
For the next several hours I kept failing and nothing was working. Some progress was made along the way however.
We know that the firmware is ~16M thanks to the function above. Then I just started playing around, lowering the Baud rate, switching cables and USB ports which proved to have its successes but no cigar.
At this point it seemed like dumping the whole firmware was impossible. I spend roughly 4h on this trying different techniques but nothing worked. Mentally I was here:
I started asking around on discord. The general consensus was that the CH340 chip wasn't the most reliable when it came to this type of thing. Thats when a hero named Padraignix came to my aid. He told me that he had a very similar experience and only got a full dump on his ~40th attempt. So not all hope was lot. After harping on discord for a while longer another hero appeared named vicious who laid down some general tips.
But it seems like I could reliably grab around 2MB of the firmware.
So lets just try and get 1MB and be happy...
And after all that struggle we got a flag?!?!
Turns out this is just a bonus flag. Which makes sense because we're on the fisherman woman quest-line. Since I want to parse the textures I was able to find a open source tool that should get the job done. So first we'll list all of the available partition.
The only partition that really interests me is storage and perhaps factory. So lets use the same tool with the help of some flags to
Lets just use binwalk because that's the next logical thing to do really, lets hope there are a sufficient amount of images within.
At this point I thought that maybe I could get the firmware from the github? Since this is northsec and we're past the end of the CTF they should have open sourced it.
I was right, but the thing I didn't expect to see was the full map of the game!!!
And we can see in the bottom right corner that there is a chest! Now... listen... I know this is kind of "cheating" since this wasn't available during the actual CTF... but hey I cant dump the whole firmware to solve it the indented way (so far). Anyways from this image I think that we should be able to go out of bounds either at the bottom right of the map near the stone wall, or at the top right near the trees.
After some messing around I was right and you can go out of bounds right around here:
After going out of bounds you keep going down and start mashing ENTER and you'll open the chest.
So the flag is
Now that was kinda cheating so I will also solve it the intended way. Since I wasn't able to dump the firmware myself ill yoink it from the GitHub.
Now that we have the firmware we will replete the previous steps. We'll start of by seeing what partitions we're dealing with and our output should be the same as before.
Now we have another binary file but I don't really know how to read it / what it really is so like any hacker we'll just fuck around and find out. Binwalk to the rescue!
Alright so lets just extract the images and see if we could have figured this without "cheating"
... Wait so they're corrupted?
At this point I wasn't quite sure what to do... so I went back to cheating a.k.a looking at the GitHub to try and get an understanding of the ESP32, and how it stores information and perhaps even if it has a filesystem / hierarchy. I did a lot of googling based off of what I saw in the GitHub... Like a lot. But one thing stood out was "spiffs".
Basically the ESP32 uses SPIFFS instead of storage cards like SD cards. This means that with SPIFFS, you can write your code in a separated file and save them on the ESP32 filesystem using magic. SPIFFS lets you access the flash memory like you would do in a normal filesystem in your computer, but simpler and more limited. Basically, you can read, write, close, and delete files. So it makes sense that binwalk could recognize some of the file structure but for a reason I don't genuinely fully understand wasn't able to extract the images successfully. My guess is because its in an "unknown state" where only headers are being recognized without their entire contents being present.
After even more research I discovered a tool called mkspiffs that essentially unpack or list all the files in the spiffs. Now this was a pain in the ass to configure and I don't know what my final make command looked like but finding the proper flags took me around 30 minutes to get right. Basically from the storage dump we're going to rebuild a directory structure in a folder named storage. note that a 1024 fs page size in bytes was the only thing that worked for me.
Now I spend a long 30 minutes just looking around running shitty forensics tool to get an idea of what I had. Through a hint from our previous hero Padraignix he told us to look into rpg which had 2 files. I didn't know what to make of this at fist but it almost seemed like a bit map I just needed to find the right dimensions.
What we end up getting is the following
As you can see it somewhat makes sense. But it feels like the areas that should be empty are filled with 0's and the areas that are full are filled with 1's. For example the castle at the bottom of the map doesn't make much sense in this bit array.
After A LOT, and I mean A LOT of fuckin about I finally got a map that made sense. With the path that we would have expected.
And I noticed something interesting with the castle there appears to be a way to get inside the castle!!!
Now there wasn't any flags within the castles but there was an "Easter egg" that made me shit my pants.
When you go into the building there a weird sound then your game just corrupts perpetually the more you move. I seriously thought I destroyed my badge and I was going to have to find a way to reinstall the firmware. But thankfully after the reboot everything was fine.
Flag #10 / I am 1337
I should remind everyone that at this point I am way out of my comfort zone. This flag can be best described by the following image.
During the previous flag when I was digging quite deep into the source code and I ended up learning a lot about the ESP32. When I was looking for anything pertaining to memory and files I fell face to face with the NVS (non volatile storage) term quite often. There is actually a library that goes in depth into NVS and ESP32.
Thankfully the esp32_image_parser.py that we've used before has a function to dump nvs. Ill make a quick note that I was given a 3 letter hint "NVS" which is one of the main reasons I investigated this avenue. So we will read the NVS partition from the full firmware dump and go from there. The way I understand it the goal of this exercise would be to read the key pair since NVS is essentially just a storage of key pairs.
Initially I was running shit like this and the output was making no sense
But thankfully I went back to read the documentation and was gifted with this snippet of information.
Now I have output that makes more sense?
One thing that was annoying is that you couldn't pipe this into jq or npx so I literally had to use an online tool to make this readable.
So after A LOT of pain I finally started understand. Basically when the flash is initialized, the page headers have been written to flash. These page have a valid sequence number. Pages that have some empty entries is where data can be written. No more than one page can be in this state at any given moment. So we need to find the right entry in NVS in which the flag counter is saved / data is written. In theory this should be where our "bitmap state: Written" entry is so we should understand what needs to be modified as far as data when we dump the NVS.
Again, the way I understand it is that I need to find the right entry since its just a key pair, therefore dumping the NVS partition will be our way to read them. From this point I'm assuming we need to patch it and push it to the firmware with a flag trigger that gives us the 10th flag.
Here is the catch 22. I was not able to dump my firmware myself so I had to use the firmware from the GitHub. That firmware doesn't have any flags solved and therefore saved in its NVS. The issue is that there is no way for me to know how to set a flag if no flags are set in the NVS.
I had no choice at this point to have someone help me. Thankfully a hero by the name of EricHogue came to my rescue. And gave me a firmware dump with 1 flag.
Then I ran
python3 esp32_image_parser.py dump_nvs -partition nvs nsec2021.bin and
python3 esp32_image_parser.py dump_nvs -partition nvs ~/Desktop/badge.bin to compare the two outputs.
|nsec2021.bin (from GitHub)||badge.bin (from EricHogue)|
|Line 15: Key : storage
Line 25: Key : misc
Line 36: Key : log
Line 50: Key : log
Line 63: Key : nvs.net80211
Line 74: Key : opmode
Line 85: Key : sta.ssid
Line 101: Key : sta.ssid
Line 115: Key : sta.authmode
Line 126: Key : sta.pswd
Line 144: Key : sta.pswd
Line 158: Key : sta.pmk
Line 173: Key : sta.pmk
Line 187: Key : sta.chan
Line 198: Key : auto.conn
Line 209: Key : bssid.set
Line 220: Key : sta.bssid
Line 234: Key : sta.bssid
Line 248: Key : sta.lis_intval
Line 259: Key : sta.phym
Line 270: Key : sta.phybw
Line 281: Key : sta.apsw
Line 295: Key : sta.apsw
Line 309: Key : sta.apinfo
Line 366: Key : sta.apinfo
Line 380: Key : sta.scan_method
Line 391: Key : sta.sort_method
Line 402: Key : sta.minrssi
Line 413: Key : sta.minauth
Line 424: Key : sta.pmf_e
Line 435: Key : sta.pmf_r
Line 446: Key : sta.btm_e
Line 457: Key : sta.rrm_e
Line 468: Key : ap.ssid
Line 484: Key : ap.ssid
Line 498: Key : ap.passwd
Line 516: Key : ap.passwd
Line 530: Key : ap.pmk
Line 545: Key : ap.pmk
Line 559: Key : ap.chan
Line 570: Key : ap.authmode
Line 581: Key : ap.hidden
Line 592: Key : ap.max.conn
Line 603: Key : bcn.interval
Line 614: Key : ap.phym
Line 625: Key : ap.phybw
Line 636: Key : ap.sndchan
Line 647: Key : ap.pmf_e
Line 658: Key : ap.pmf_r
Line 669: Key : lorate
Line 680: Key : country
Line 694: Key : country
Line 708: Key : opmode
Line 718: Key : phy
Line 729: Key : cal_data
Line 818: Key : cal_data
Line 884: Key : cal_data
Line 898: Key : cal_mac
Line 912: Key : cal_mac
Line 926: Key : cal_version
Line 937: Key : opmode
|Line 15: Key : namespace_name
Line 26: Key : key1
Line 294: Key : cal_data
Line 359: Key : cal_data
Line 372: Key : cal_mac
Line 385: Key : cal_mac
Line 398: Key : cal_version
Line 408: Key : opmode
Line 418: Key : save
Line 433: Key : save
Line 1418: Key : storage
Line 1428: Key : misc
Line 1439: Key : log
Line 1453: Key : log
Line 1466: Key : nvs.net80211
Line 1477: Key : opmode
Line 1488: Key : sta.ssid
Line 1504: Key : sta.ssid
Line 1518: Key : sta.authmode
Line 1529: Key : sta.pswd
Line 1547: Key : sta.pswd
Line 1561: Key : sta.pmk
Line 1576: Key : sta.pmk
Line 1590: Key : sta.chan
Line 1601: Key : auto.conn
Line 1612: Key : bssid.set
Line 1623: Key : sta.bssid
Line 1637: Key : sta.bssid
Line 1651: Key : sta.lis_intval
Line 1662: Key : sta.phym
Line 1673: Key : sta.phybw
Line 1684: Key : sta.apsw
Line 1698: Key : sta.apsw
Line 1712: Key : sta.apinfo
Line 1769: Key : sta.apinfo
Line 1783: Key : sta.scan_method
Line 1794: Key : sta.sort_method
Line 1805: Key : sta.minrssi
Line 1816: Key : sta.minauth
Line 1827: Key : sta.pmf_e
Line 1838: Key : sta.pmf_r
Line 1849: Key : sta.btm_e
Line 1860: Key : sta.rrm_e
Line 1871: Key : ap.ssid
Line 1887: Key : ap.ssid
Line 1901: Key : ap.passwd
Line 1919: Key : ap.passwd
Line 1933: Key : ap.pmk
Line 1948: Key : ap.pmk
Line 1962: Key : ap.chan
Line 1973: Key : ap.authmode
Line 1984: Key : ap.hidden
Line 1995: Key : ap.max.conn
Line 2006: Key : bcn.interval
Line 2017: Key : ap.phym
Line 2028: Key : ap.phybw
Line 2039: Key : ap.sndchan
Line 2050: Key : ap.pmf_e
Line 2061: Key : ap.pmf_r
Line 2072: Key : lorate
Line 2083: Key : country
Line 2097: Key : country
Line 2111: Key : opmode
Line 2121: Key : phy
Line 2132: Key : cal_data
As you can see there is a major difference! What stood out the most was a save key and the difference between the flags.
Entry 34 Bitmap State : Written Written Entry 34 NS Index : 2 Type : BLOB_DATA Span : 3 ChunkIndex : 0 Key : save Blob Data : Size : 36 Data : 00000000: 00 00 00 00 01 00 00 00 01 01 00 00 01 00 19 00 ................ 00000010: 01 00 00 00 FF 00 FF 00 01 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 .... Entry 37 Bitmap State : Empty Written Entry 37 NS Index : 2 Type : BLOB_IDX Span : 1 ChunkIndex : 255 Key : save Blob IDX : Size : 36 Chunk Count : 0 Chunk Start : 255
Now I was also able to obtain a firmware with 2 flags solved. And the solution because obvious as seen below.
Therefore to get all of the 10 flags should be this bitmap state's data should be:
00 00 00 00 01 00 00 00 00 00 01 01 01 00 19 00 01 00 00 00 ff 00 ff 00 01 01 01 01 01 00 01 01 01 01 00 00
So lets grab that copy of Erics's firmware, perform a NVS dumps, flipped the corresponding two bits and then write to flash.
Then ill open this in a hex editor and flip all those bits.
Now with that done we will write our new NVS to the flash of the badge and fucking pray to all gods that I don't destroy my badge.
Holly shit we did it!
Flag #6 / Who's Knocking?
This flag was literally hell. IT took me way to long to figure out what was going on. In case you don't know me that well I'm not great at WiFi pentests. I haven't done a lot of them and its not really an area that interests me. I don't have a WiFi card so the only way I could solve this challenge was by booting kali off of a USB. I spent roughly 3 solid days trying to make a workaround for my VM but VirtualBox simply cant do it.
Due to flag 8 and 9 (the reverse challenge) and having to connect to WiFi I knew there could be something funky going. My gut feeling was that I needed to capture some WiFi transmission. At first I thought that maybe the badge's interface is transmitting frames on lets say channel X and I just had to figure it out. When that was disprove I thought that maybe the badge had a secret SSID but that was also false. There were a lot of other theories that were tested and disproven.
This was clearly not one of my best moments, but it worked...
I need my setup to be this way since I need to create a hotspot WiFi from the same WiFi connection but since I couldn't do that because I don't own a USB WiFi card I created a WiFi connection from my a wired connection. Thanks to a stack overflow comment I discovered a tool called create_ap as seen below.
This created a ap0 interface which was on 198.51.100.42 which is what I needed. With this I will be able to send all the traffic into wireshark and know that whatever the badge would send once it established a connection I would receive.
Then I used the badges CLI to join the AP I just created.
And out of the short capture I noticed that once the badge authenticated to the IP
I would see that the badge was trying to reach out to 198.51.100.172 on port 4444 and even if someone doesn't know whats going on and is drunk on whisky 4444 screams reverse shell.
I setup a netcat listener on my localhost and sure enough after a few seconds I caught the connection
Our final flag of the journey is