Metasploitable3 Community CTF – Walkthrough(ish)

This isn’t intended as a “full” walkthrough, I’m basically just going to outline my approach and perhaps mention a few alternatives.

A quick note; this is the first time I’ve participated to any major degree in a CTF contest (Though I have tested and designed a number of levels for the Ruxcon CTF over the past couple of years), I’ve poked at a few in the past but I seem to quickly lose interest. I think the fact that this one was over a defined time period and that the #ctf-support channel was there on slack really helped to keep me engaged.

Recon and initial access
Of course the first tool I reached for out of the gate was nmap, I wasn’t particularly discrete about it because I figured with the objective of this one there wasn’t likely to be any network-level mitigations in place.

nmap -sV -Pn -T4 -p 1-65535 -vv -oX dump 10.0.37.251

I pulled that into metasploit by way of db_import, which gave me the following to work from;

msf > services

Services
========

host         port  proto  name         state   info
----         ----  -----  ----         -----   ----
10.0.37.251  21    tcp    ftp          open    ProFTPD 1.3.5
10.0.37.251  22    tcp    ssh          open    OpenSSH 6.6.1p1 Ubuntu 2ubuntu2 Ubuntu Linux; protocol 2.0
10.0.37.251  80    tcp    http         open    Apache httpd 2.4.7
10.0.37.251  445   tcp    netbios-ssn  open    Samba smbd 3.X - 4.X workgroup: WORKGROUP
10.0.37.251  631   tcp    ipp          open    CUPS 1.7
10.0.37.251  3000  tcp    ppp          closed
10.0.37.251  3306  tcp    mysql        open    MySQL unauthorized
10.0.37.251  3500  tcp    http         open    WEBrick httpd 1.3.1 Ruby 2.3.5 (2017-09-14)
10.0.37.251  6697  tcp    irc          open    UnrealIRCd
10.0.37.251  8181  tcp    http         open    WEBrick httpd 1.3.1 Ruby 2.3.5 (2017-09-14)

Which provided us with quite a few options as far as exploitation goes, but first off the bat I did some sniffing around the various httpd services that were lying around to see if there was anything interesting, at this point I found;
– phpMyAdmin, drupal, a custom “payroll_app.php” and a custom chatbot running out of apache.
– A Rails server running on 3500.
– A custom web app running on 8181.

On to exploitation, had a poke around the exploit database in Metasploit and settled on ‘exploit/unix/irc/unreal_ircd_3281_backdoor’.

This got me a shell on the box as boba_fett, which was enough to get me several of the flag source files, and also lead me to creds for the rest of them.

Getting creds

While I was looking around the system I found a PoC for an SQLi in the payroll_app (Equally could’ve gone direct to the database since the credentials were in the clear in the file, but if it’s there you might as well use it πŸ™‚ ), I’d previously confirmed that it was likely vulnerable to SQLi but parked it for later.

This got me a dump of user creds for the Payroll App which I then fed through ‘auxiliary/scanner/ssh/ssh_login’ for validation.

anakin_skywalker:but_master:(
artoo_detoo:b00p_b33p
ben_kenobi:thats_no_m00n
boba_fett:mandalorian1
c_three_pio:Pr0t0c07
chewbacca:rwaaaaawr8
greedo:hanSh0tF1rst
han_solo:nerf_herder
jabba_hutt:my_kinda_skum
jarjar_binks:mesah_p@ssw0rd
kylo_ren:Daddy_Issues2
lando_calrissian:@dm1n1str8r
leia_organa:help_me_obiwan
luke_skywalker:like_my_father_beforeme

Turns out most of the users on this system (with the notable exception of darth_vader) are guilty of re-using passwords, this got me a bunch of low-privilege user accounts I could directly login with so I could at least switch to a “real” interactive shell πŸ˜‰

8 of Clubs

This was a quick and easy find due to world-readable home directories, find doesn’t care about depth of directory structures, so;

find /home -iname "*_of_*"

quickly turned this and others up;

./anakin_skywalker/52/37/88/76/24/97/77/22/23/63/19/56/16/27/43/26/82/80/98/73/8_of_clubs.png
./leia_organa/2_of_spades.pcapng
./artoo_detoo/music/10_of_clubs.wav

This one was a very simple, “get file, md5sum file, submit hash”.

8 Clubs Flag

8 Clubs Flag

9 of Diamonds

This one was found by manually hunting through home directories, it was found in a hidden folder under kylo_ren’s home directory;

han_solo@ip-10-0-37-251:/home/kylo_ren$ ls -laR
...
./.secret_files:
total 680
drw---x--- 2 kylo_ren users   4096 Nov  7 16:45 .
drwxr-xr-x 5 kylo_ren users   4096 Dec  6 05:35 ..
-rw---x--- 1 kylo_ren users 688128 Nov  7 16:45 my_recordings_do_not_open.iso

The flag was then retrieved by mounting the iso and doing the usual “md5sum file, submit hash” dance.

9 Diamonds Flag

9 Diamonds Flag

2 of Spades

This one was also found from the above “find”;

./leia_organa/2_of_spades.pcapng

This one requires a little bit more work to retrieve, scanning through the pcap in Wireshark, we see a bunch of local network traffic which is not terribly interesting, but as you dig further you start to see this;

Section of pcap for 2 Spades

Section of pcap for 2 Spades

So we have a VoIP call, so let’s see what Wireshark has to say about that (Telephony->VoIP Calls);

2 Spades VoIP Call

2 Spades VoIP Call

Playing back the call we find that it’s a very scratchy audio call to an echo test service with a voice reading out a URL for us; https://imgur.com/gmThKFP going to the URL, sure enough, there it is, the 2 of spades.

2 Spades Flag

2 Spades Flag

10 of Spades

This one was another that was found by find, I don’t recall specifically, I think it was probably on /opt.

Another retrieve flag (/opt/readme_app/public/images/10_of_spades.png), md5sum file, submit hash.

10 Spades Flag

10 Spades Flag

5 of Hearts

Was one I’d found earlier, but hadn’t looked into in detail yet, it was openly available on the Drupal install on Apache, but the file wasn’t quite right (each challenge described the flag image);

5 Hearts Original

5 Hearts Original


So I parked it while I went after any other low-hanging fruit.

Once I got back to this one I fed it to identify from ImageMagick, and was greeted by a blob of base64 encoded data in a tEXt chunk in the file, the text was duly extracted and fed to base64 -d to decode it into the original binary, which was the PNG I was looking for;

5 Hearts Flag

5 Hearts Flag

3 of Hearts

It was about here that I started looking for root, the rest of the flags had been acquired with low-privilege accounts.

It took longer than I really care to admit for me to figure out how to escalate to root, I spent a couple of hours slaving over the process list looking for convenient chinks in processes run by root. Classic overthinking though, because at some point I did a “cat /etc/group” and immediately facepalmed when I found this;

...
sudo:x:27:ubuntu,leia_organa,luke_skywalker,han_solo
...

Two of these are accounts I had credentials for, so I switched to han_solo asked “I wonder” and ran “sudo -s” lo and behold, unrestricted root shell. One *facepalm* later, I continued on with the process.

First port of call was to re-check for low-hanging fruit in case there was something which wasn’t world/users readable, there was;

root@ip-10-0-37-251:~# find / -iname "*_of_*"
...
/lost+found/3_of_hearts.png
...

Winner! Another quick md5sum file, submit hash.

3 Hearts Flag

3 Hearts Flag

5 of Diamonds

Onward and upward, earlier I’d noticed in the process list that knockd was running, but until I got root there wasn’t anything I could do with that because /etc/knockd.conf was owned by root and had 0600 permissions, now I could actually see it.

root@ip-10-0-37-251:/opt/knock_knock# cat /etc/knockd.conf
[options]
        UseSyslog
[openFlag]
        sequence    = 9560,1080,1200
...

Back to the Kali box, install knockd, and;

ec2-user@kali:~/staging$ knock 10.0.37.251 9560 1080 1200"

Quick check with nmap;

PORT     STATE SERVICE REASON         VERSION
8989/tcp open  rtsp    syn-ack ttl 64

We’re golden, hit it with lynx, download the file it presents, do the dance, and done;

5 Diamonds Flag

5 Diamonds Flag

7 of Diamonds

The source file for this one was also discovered in the earlier global find as root, it was an encrypted ZIP file sitting in a docker container, it was duly retrieved and inside it were found;
“hint.gif” and another “7_of_diamonds.zip”.

7 Diamonds "Hint"

7 Diamonds “Hint”


Excuse the animated gif.

So it’s an animated gif with a stack of QR codes in it, so presumably we need to extract the frames, read the QR codes and concatenate all the data together.

I used an online service to split the gif into frames (could’ve used ImageMagick but I’d have had to read the manual to remember how to do this operation πŸ˜‰ ), then wrote some code around the ZXing library to parse each of the frames in turn (I went hunting for some pre-existing code but couldn’t find anything quickly), and spit out the binary data (each QR code had a hex string which was part of the larger file), which yielded this;

7 Diamonds Password

7 Diamonds Password

Which allowed me to extract the actual flag file.

7 Diamonds Flag

7 Diamonds Flag

8 of Hearts

I’d retrieved the source for this one pretty much immediately after getting into the box, the root credentials for MySQL were in the clear in the payroll_app.php file, I logged in to MySQL via phpMyAdmin and found the following;

8 Hearts phpMyAdmin

8 Hearts phpMyAdmin

The blob was duly extracted, and found to be an encrypted zip file. The reason this took me so long to resolve was basically down to using the wrong tool, I’d grabbed a current copy of John The Ripper, and fed the file to that along with several wordlists including rockyou, but I was unable to get a positive result, I resorted to pure brute force, by feeding the output of Mask Processor with a 13 character ASCII mask into JTR to try to crack the file (it’s a big keyspace but at 4.5Mc/s it’s not too bad), I left it running overnight.

The following day I still didn’t have a result, but I happened to have a look at the #ctf-support channel scrollback and found suggestions that the password *WAS* in fact in rockyou, some time later I saw reference to fcrackzip which is not a tool I’d used, so I killed my MP/JTR job and instead went for fcrackzip+rockyou, the password was found in about 30 seconds…I thought requiring an outright brute force was a bit OTT but couldn’t think of another option, live and learn.

Turns out the password was ‘vagrant’, extracted ZIP, did the dance and got the flag;

8 Hearts Flag

8 Hearts Flag

Ace of Clubs

This flag was hidden in the chatbot which was running out of Apache.

I gather it’s possible to get the bot to divulge the flag (it says as much, but you have to “ask the right question”, it’s also vulnerable to command injection so there’s an alternative path to root for you), but I dislike these types of challenges, never can seem to get the “right” question so instead I went straight for the source.

A bit of fossicking around in the files in /opt/chatbot lead me to two NodeJS processes in the process list;

 1255 ?        Sl     0:00 nodejs /opt/chatbot/papa_smurf/functions.js
 1256 ?        Sl    54:56 nodejs /opt/chatbot/papa_smurf/chat_client.js

Looking inside chat_client we find a base64 encoded blob, which upon extraction and decoding is “surprise” another PNG file;

Ace Clubs Flag

Ace Clubs Flag

6 of Clubs

This one is getting a bit more involved and seemed to cause the most “technical” trouble for contestants, I coached about 6 other people through solving this one one way or the other.

There are two pathways to it, either you can modify and re-sign a cookie, or you can recover the source. Of the people I coached, I think it was about a 50:50 split.

I’m not much of a web hacker so I went straight for the source, but for some variety here is the broad strokes of how to do it through the “front door”;

The web app for this one is running on 8181, when you access it it tells you what you need to do, “The /flag route can give you a flag if the _metasploitable cookie has the name of the flag.” but the cookies it sets are signed.

The cookie signing secret is in the session cookie that is set when you connect to the service, so you need to extract that, then add “6 of clubs” into the _metasploitable field within the cookie, then re-sign it and replace the cookie, it took quite a while for all the people who took this approach successfully to get the cookie “just right”.

I took the “other” approach, a bit of hunting around lead me to two ruby processes in the process list;

...
 9592 ?        S      0:00 /bin/sh -c cd /opt/sinatra && ruby -e "require 'obfuscate'; Obfuscate.setup { |c| c.salt = 'sinatra'; c.mode = :string }; cr = Obfuscate.clarify(File.read('.raIhUJTLEMAfUW3GmynyFySPw')); File.delete('.raIhUJTLEMAfUW3GmynyFySPw') if File.exists?('.raIhUJTLEMAfUW3GmynyFySPw'); eval(cr)" --
 9593 ?        Sl     0:07 ruby -e require 'obfuscate'; Obfuscate.setup { |c| c.salt = 'sinatra'; c.mode = :string }; cr = Obfuscate.clarify(File.read('.raIhUJTLEMAfUW3GmynyFySPw')); File.delete('.raIhUJTLEMAfUW3GmynyFySPw') if File.exists?('.raIhUJTLEMAfUW3GmynyFySPw'); eval(cr)
...

What we can see here is that something is changing to the directory /opt/sinatra, and executing some ruby code which picks up a hidden file ‘.raIhUJTLEMAfUW3GmynyFySPw’, reads the contents, deobfuscates it, stores it in a variable, deletes the original file then “eval”s the code to execute it. Logically *something* must be creating that file on startup because otherwise this service would be broken on reboot.

So we need to A) figure out under what circumstances the file is being (re)created, and B) retrieve it before it gets deleted.

Some hunting in /etc/init reveals that “sinatra” (which is referred to in the first command above) is a service on the system (it doesn’t appear in ‘service –status-all’ presumably “because systemd”).

Issuing a “service sinatra stop” we observe that the two ruby processes above have disappeared, so evidently this is the service responsible for the creation of the file and executing the code which removes the file.

Excellent, a quick shell script later;

#!/bin/bash
while :
do
  if [ -f /opt/sinatra/.raIhUJTLEMAfUW3GmynyFySPw ]
  then
    cp /opt/sinatra/.raIhUJTLEMAfUW3GmynyFySPw /tmp
    echo "We got one!"
    exit 0
  fi
done

Run the script in the background, bounce the service;

root@ip-10-0-37-251:/tmp# ./copy.sh &
[1] 16772
root@ip-10-0-37-251:/tmp# service sinatra stop && service sinatra start
sinatra stop/waiting
sinatra start/running, process 16786
root@ip-10-0-37-251:/tmp# We got one!
ls -a .*
.raIhUJTLEMAfUW3GmynyFySPw

Ta Da! We have our file. From the commands above we can tell that it’s going to be obfuscated so we need to do something about that, the command above with a slight twist will do the trick;

ruby -e "require 'obfuscate'; Obfuscate.setup { |c| c.salt = 'sinatra'; c.mode = :string }; cr = Obfuscate.clarify(File.read('.raIhUJTLEMAfUW3GmynyFySPw')); File.open('file.rb', 'w') { |file| file.write(cr) }; "

Which gives us our cleartext Ruby code, once again the flag is in there as a base64 encoded blob, which is duly extracted, decoded, md5sum’d and submitted;

6 Clubs Flag

6 Clubs Flag

King of Spades

Another one which took longer than was reasonable to do…

In the early stages of the contest I connected to everything on the server, or almost everything… When I originally attempted to connect to the ircd on the box I ran into trouble, the IRC server was running on 6697 which caused me to assume it was using SSL, it wasn’t…

I’d gone through most of the rest of the flags before coming back to this one, and I’ll admit I needed a nudge (thanks @Fullname) to move me in the right direction, when you connect to the ircd you get the following;

...
02:40 -!- - G2dXn2DJ5MuUiul38wHYu8rFwYfxAzhkm/S5oUCRJkPbcw3n4uOhvTmYEbGEjpWdvH3SbZmke5A9LkU00
02:40 -!- - jE03jLWA3LmKmec6G6eIXnGr6l/IsXUNrYfEJfhq3P2J4uvgD+2BRnKUC8b/GL2kyl8bLfC617xFzfip8
02:40 -!- - G92Q4rRzOd81dZ6LHBnV51FKLQ00UGc2lEzkO7xOkDALeZQDzPN8HJOzcmnT1kHiebyd4vexyTzAKu1yI
02:40 -!- - 05GHjfjsvOYIUmTDgME6eMNzrJvrHQ6JvAjOgFXSvdgvmLOwe/h0OCknGIKKWUc4+w5Qhlf4hFe4jbOoI
02:40 -!- - x8s/WmJisF2beKP50XU/xG1vNDs6F/Ulbez430AEHChNI6OiFpGKCfXl7mhCxJk0cdV9MBdLA6hc/gnxy
...

Initially I logged the output, cleaned it up and fed it to ‘base64 -d’ but that didn’t work because irssi truncates the lines. Never mind, we now know that the flag is in the irc message of the day so we can extract it directly from ‘/opt/unrealircd/Unreal3.2/ircd.motd’

Content was decoded and found to be a PNG image, but hang on… This doesn’t look like the description…

King Spades Original

King Spades Original

Looking a bit deeper into the file with a hex editor (binwalk would’ve done it directly though) we notice that something’s not quite right here, PNG files aren’t supposed to have PKZIP trailers…

root@ip-10-0-37-251:/tmp# xxd king.bin
...
0024ba0: e947 05f8 5b34 549e 28bf 57b4 0af9 1f50  .G..[4T.(.W....P
0024bb0: 4b01 0234 0314 0000 0008 00e1 03fc 4ac8  K..4..........J.
0024bc0: 7a3e 4fa7 2202 00c0 2302 0012 0000 0000  z>O."...#.......
0024bd0: 0000 0001 0000 00a4 8100 0000 006b 696e  .............kin
0024be0: 675f 6f66 5f73 7061 6465 732e 706e 6750  g_of_spades.pngP
0024bf0: 4b05 0600 0000 0001 0001 0040 0000 00d7  K..........@....
0024c00: 2202 0000 00                             "....

The PNG was stripped off the top of the ZIP archive and the archive was extracted revealing “the real king of spades”;

King Spades Flag

King Spades Flag

10 of Clubs

This one is another that took me WAAAAY too long to solve, I got there after a nudge from another contestant (thanks @Fullname). The problem here was basically a failure to abide pretty much the first commandment in binary file analysis “thou shalt binwalk”.

I’d spent some time manually pouring over the data in the file looking for headers and such but didn’t find any, which lead me to the rabbit hole of overthinking, I’d basically tunnel visioned onto the idea that the data must be somehow encoded into the audio, spent a lot of time with stego tools and GNU Radio Companion trying to work it out.

After a comment about “not overthinking challenges” in #ctf-support, and the aforementioned nudge I was back on the path.

In the end, this one was as simple as “binwalk -e 10_of_clubs.wav” which revealed that there was a chunk of ZLIB compressed data after the WAV file header, upon extraction we find that said ZLIB compressed data is a PNG file and the flag;

10 Clubs Flag

10 Clubs Flag

Joker

This is the only challenge within the contest that I have a gripe with, it was very difficult to get right not because it was technically challenging but because it has ONE *VERY* specific solution (well there was also a code-based solution but I’d expect that that was written “after the fact” to replicate the results, because the chances of “stumbling” onto the “right” code is basically nil, I’ll explain further below).

So this one looks pretty straight forward, there’s a file in /etc called “joker.png” owned by root with 0600 permissions so once you’ve got root it’s trivial, right… Not quite…

The file looks like this;

Joker Original

Joker Original

So obviously the colours are inverted, that’s easy enough to fix and Rapid7 provided the following hint;
“@channel A little hint for you: The Joker is tricky; it’s easy to calculate the wrong MD5 hash. In fact, you might notice the hash changes on you (this is the Joker, after all). Pay attention to why and when the hash changes. Working with an image editing tool? Watch the metadata. Using a custom script? Focus on pixels. Good luck!” Which I interpret as “make sure that nothing except the pixels change in the file”.

I then proceeded to try about a dozen different approaches to generate a file that had *ONLY* the pixels inverted, I eventually settled on this chunk of Ruby;

require 'chunky_png'

module ChunkyPNG::Color
  def invert(value)
    rgba(MAX - r(value), MAX - g(value), MAX - b(value), a(value))
  end
end

image = ChunkyPNG::Image.from_file('joker.png')

image.pixels.map! do |pixel|
  ChunkyPNG::Color.invert(pixel)
end

image.save('joker_inverted.png')

Which resulted in the desired result;

Joker File Structures

Joker File Structures

Note that the IHDR and IEND CRCs are identical, the only difference here is in the IDAT section, to further confirm I also extracted the IDAT sections from the two files and did a direct comparison, and verified that the pixel RGB values were, in fact inverted between them.

"My" Joker Flag

“My” Joker Flag

But that didn’t work… The only conclusion I could come to at that point that although compressed data ought to be deterministic, that there must be some quirk in the LZ77 implementation in chunky_png versus whatever was used to generate the “master” image, in desperation I reached out to one of the contestants who had already solved all the challenges and I was told “all the solves I’m aware of used GIMP” (thanks @mubix)…

I’d already tried that a couple of days before, but discounted it because when I looked at the file structure it was different… But lo and behold I exported from GIMP with “Save creation time” unset, and the flag was accepted.

"Correct" Joker Flag

“Correct” Joker Flag

My problem with this one is that every tool I used to perform the changes produced a file with a different structure, which basically meant the *ONLY* way to solve this was to use GIMP, and considering that the file structure of the “right” file looks like this;

"Right" Joker Structure

“Right” Joker Structure

You can probably see why I doubt that the code which produced the “right” answer was produced without having seen the file structure first (Furthermore it used chunky_png and what I have above is pretty much the simplest case for inverting pixel colours with that.).

Summary

Overall barring that one minor gripe it was a thoroughly enjoyable competition.

I thank Rapid7/Metasploit and in particular juggernot and sinn3r for their efforts in putting the competition together and catc0n for her ongoing encouragement throughout the competition.

Also, if you want to read a WAAAAAY more comprehensive and detailed write up have a look at mubix’s (Second place in the CTF) Metasploitable 3 – A Walk-through: Linux Edition

Morgan / 2017-12-12 / Uncategorized / 0 Comments

Metasploitable3 Community CTF

A while back I saw the announcement of the Metasploitable 3 Community CTF come past on Twitter, signed up mainly for shits and giggles, didn’t expect to get much chance to do much on it, because day job, but figured it might be entertaining to poke at it occasionally over the course of the week.

Started a bit behind the curve (being that it was a US based contest and I’m in AU), but ended up doing pretty well none the less. Placed 10th out of a field of 500.

Thanks to everyone at Rapid7 for making the contest happen, it was an enjoyable way to spend some time here and there across a few days.

For anybody still struggling with the Joker, you’re probably overthinking it (I know I was), remember hackers are generally lazy πŸ˜‰

Not sure if I’ll leave this as a permanent post or if I’ll just treat it as a placeholder for some “walkthrough” type stuff later, time will tell.

Morgan / 2017-12-08 / Uncategorized / 0 Comments

Ruxcon 2017 HHV Badge Flag – Part 2 – The “Hard” Way

In this part we’ll go through how to retrieve the flag directly from the binary. This should be easier than it is but there are some eccentricities to the NodeMCU Lua compiler which break most of the tools out there, after spending some time trying various decompilers and attempting to get ChunkSpy to behave with the NodeMCU bytecode (if I can be bothered I might write up a post on said eccentricities at some point, but don’t hold your breath), I ended up doing the decompilation manually. Took a while but wasn’t particularly “hard”.

The full annotated listing is in this file: RuxBadge 2017 Listing.

Lua is a bit of an odd bird, but the only thing you really need to know for the purposes of this discussion is that pretty much everything in Lua is a table.

If you want to dig down into the nitty gritty I recommend this document A No Frills Introduction to Lua 5.1 VM Instructions that’s where most of the info I used in this process came from.

For the sake of expedience I’m not going to go through the whole listing, hopefully what’s there is fairly self explanatory, though I will explain a few bits and pieces along the way.

The chunk we’re interested in here is function[0] which starts at offset 0x70 in the binary file.

The basic structure of a Lua binary chunk is;
– Function header
– Function code table
– Function constants table
– Function functions prototypes table (for sub-functions)
– Function source line info table (optional debug info)
– Function local variables table (optional debug info)
– Function upvalues table (optional debug info)

So the flag will obviously be some kind of constant, so your first port of call is the constants table (I’m jumping to the function[0] constants table for expedience, but it’s pretty easy to figure out which of the three is the right one to be looking at);

function[0] constants:
011C 13 00 00 00 - Table size (19)

0120 06 - Constant Type (6 = NodeMCU String)
0121 07 00 00 00 - Length of Constant (7)
0125 63 72 79 70 74 6F 00 - "crypto\0"

012C 06 - Constant Type (6 = NodeMCU String)
012D 06 00 00 00 - Length of Constant (6)
0131 74 6F 48 65 78 00 - "toHex\0"
...
019F 06 - Constant Type (6 = NodeMCU String)
01A0 27 00 00 00 - Length of Constant (39)
01A4 3E 3A 35 3E 23 60 11 6F
01AC 6D 14 66 1F 68 63 65 1D
01B4 61 12 66 1A 6E 14 12 1D
01BC 1E 63 62 6D 1D 63 16 18
01C4 1C 6F 6C 69 6F 2B 00 - ">:5>#`\17om\20f\31hce\29a\18f\26n\20\18\29\30cbm\29c\22\24\28olio+\0"
...

So the table has 19 entries, the first couple look to be some kind of function calls, but what’s this weird looking string down here which just happens to be 39 chars (well 38+NULL terminator)? Could it be our flag? Clearly it’s been mangled somehow so we’ll need to figure that out, to the code table (offset 0x80 in the file)!

...
00CC 41 00 03 00 - loadk (>:5>#`\17om\20f\31hce\29a\18f\26n\20\18\29\30cbm\29c\22\24\28olio+)
00D0 85 00 00 00 - getglobal (crypto)
00D4 86 40 43 01 - gettable (encrypt)
00D8 C1 80 03 00 - loadk (AES-CBC)
00DC 00 01 00 00 - move
00E0 45 01 00 00 - getglobal (crypto)
00E4 46 C1 C3 02 - gettable (mask)
00E8 80 01 80 00 - move
00EC C1 01 04 00 - loadk (XVTY)
00F0 5C 01 80 01 - call (crypto.mask(..."XVTY"))
00F4 9C 80 00 00 - call (crypto.encrypt(AES-CBC"...))
...

This is not the easiest to follow example of how function calls work in Lua because there are some register swaps but it’s not *too* bad.

The function call mechanism is vaguely similar to how the stack works in an x86 processor, so that provides a useful analog to work with, you can consider the above code as follows (not 100% accurate but close enough for our purposes);
– Load the “flag” constant onto stack.
– Load “crypto” global (a reference to the NodeMCU crypto module) onto stack.
– Load “encrypt” table (actually a function, but remember pretty much everything in Lua is a table) onto stack.
– Load the constant “AES-CBC” onto the stack.
– Juggle some registers (moving the “flag” constant down).
– Load “crypto” global onto stack.
– Load “mask” global onto stack.
– Juggle some registers (moving the “flag” constant down).
– Load the constant “XVTY” onto stack.
– Call “crypto.mask(<flag constant>, “XVTY”)”
– Call “crypto.encrypt(“AES-CBC”, <key>, <result from above>)”

So it’s taking the “flag” constant, and the value XVTY and feeding it to the crypto.mask function, time to go figure out what that does, to the docs! NodeMCU crypto module docs

From the documentation we find that this function merely applies an XOR mask to the provided string, so that must be how it’s getting the “actual” flag to encrypt in the next call, since XOR is trivially reversible all we need to do is apply the mask back to the string to get the answer, let’s try that;

3E 3A 35 3E
58 56 54 59 ^
-----------
66 6C 61 67

Converting back to ASCII we get;
flag

WIN! Now we just need to apply the mask to the rest of the string;

3E 3A 35 3E 23 60 11 6F 6D 14 66 1F 68 63 65 1D 61 12 66 1A 6E 14 12 1D 1E 63 62 6D 1D 63 16 18 1C 6F 6C 69 6F 2B
58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 58 56 54 59 ^
------------------------------------------------------------------------------------------------------------------------
66 6C 61 67 7B 36 45 36 35 42 32 46 30 35 31 44 39 44 32 43 36 42 46 44 46 35 36 34 45 35 42 41 44 39 38 30 37 7D

Converting to ASCII we get;
flag{6E65B2F051D9D2C6BFDF564E5BAD9807}

Ta Da!

A random sidenote here, this code would likely flow a bit more logically if I’d not done it as a compound call i.e.
crypto.encrypt(“AES-CBC”, <key>, crypto.mask(<flag constant>, “XVTY”))

Morgan / 2017-11-09 / Uncategorized / 0 Comments

Ruxcon 2017 HHV Badge Flag – Part 1 – The “Easy” Way

As promised, here’s a walkthrough for retrieving the Badge Flag from the Ruxcon 2017 HHV Badge.

The easy way requires that you have the badge hardware up and running (though you could just load the firmware onto some random ESP8266 module for the same effect).

If we connect to the UART port on the badge we see the following at startup;

Boot messages

In this data we see;
flag: cbd3f82962b3b38679fba0250f243c4c7dc5b8aa5ff403383f43de6e9dd2e23a32de120d62776077e2196c36ae0a330d

SWEEET! that was easy!

Err, no… Typically for a CTF flag it’s something like a GUID or it’s something like flag{<GUID>} or just flag{<16 random hex bytes>}, which doesn’t really gel with the above, furthermore, if you boot a different badge you’ll see a different value here, so apparently we need to apply some intelligence.

If we have a look closer at the data we’ll see that there are 96 characters there, since it’s evidently hex, that corresponds to 48 bytes. Looking at the values, there are bytes there above 7F so it can’t be a direct ASCII encoding or any ASCII-based encoding.

Considering that flag{<GUID>} is 42 characters and flag{<16 random bytes>} is 38 characters, and our ciphertext is 48 bytes that indicates that it’s likely using some flavour of block-wise encryption (in this case 16 bytes or 128 bits) because if it was using a stream type encryption or most other (non ASCII based) encoding schemes the output would be the same size as the input.

In case you didn’t notice from the startup messages above, the firmware on the badge is based on NodeMCU (and the crypto module is included). NodeMCU is basically an embedded Lua engine for ESP8266, the documentation can be found at; https://nodemcu.readthedocs.io/en/master/.

Looking at the documentation for the crypto module we find that the NodeMCU crypto module supports AES-CBC-128 and AES-ECB-128 algorithms for encrypt/decrypt, both of which are blockwise and both of which are 128 bit in the NodeMCU implementation.

So we have a couple of candidate algorithms, but we still need a key.

Since they’re 128 bit algorithms we’re looking for a 16 byte string. As I told everyone who asked over the course of the conference, the key is out in the open. So the question is where… Let’s go back to that screen grab from earlier;

Boot messages

Do we see any 16 character, or otherwise 16 byte strings here? Moreover, they need to be a *UNIQUE* 16 character/byte string, because the “flag” field is different on every badge… That rules out the commit ID because that’s the same for every badge (assuming you’re using the NodeMCU build that ships with the firmware) and it’s too long in any case. Let’s see, can’t be the SSID, that’s only 15 characters, wait what’s this?

Badge Boot Messages Anotated

It’s a 16 character string… And it’s being used as a crypto key (FWIW clue on the CTF listing for this one was “Reusing crypto keys is bad mmm’kay” or something like that), so that might just be it…

Conveniently enough we can use the badge to perform the decryption. First things first though, we need to convert our “flag” to a string for processing by crypto.decrypt(), a quick google will get you the following function;

function fromhex(str)
  return (str:gsub('..', function (cc)
    return string.char(tonumber(cc, 16))
  end))
end

Let’s try AES-ECB first because otherwise we need to figure out what was used as an IV for AES-CBC;

Well that’s interesting, looks like we’re on the right track, but we’re not quite there yet. We can still get some useful info here though, we know the flag begins with, flag{6E65B2F051D, so it’s definitely in the form “flag{<something>}” but we can go even further than that. GUIDs are of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, we have the first 11 chars here, and there’s no hyphen so it must be in the form flag{<16 random hex bytes>} which tells us that the length of the whole flag is 38 bytes long. This will become important later.

So we get a partial decryption with AES-ECB, this suggests that the code is using AES-CBC here, which means we need an IV.

The IV for any algo using one is the same as the block size (in this case 16 bytes), and in order to enable them to decrypt the data one needs to communicate the IV to the other party. Typically this is done by either prepending or appending the value to the ciphertext, so in this case we’d be looking at two blocks being cipher text and one being IV…

Hang on, but know our flag’s 38 bytes, how the hell do we fit that into 32 bytes…

From what we’ve seen previously, there’s clearly no clever encoding going on here, 38 bytes doesn’t fit in the 2 blocks we have “available”. Either we’re not being told the IV, or the IV is all zeros. Now, not telling you the IV would be a bit of a dick move, because without the IV you can’t decrypt the whole flag, so one can only assume that if it’s not an impossible challenge, the IV is all zeros.

This is easy enough to test, referring back to the doco, it specifies that if an IV is not provided it will use all zeros so, let’s try that;

Ta Da! We appear to have a flag.

It’ll take a while longer for me to do the write up of the “hard” way, because it involves a lot of bytecode and some understanding of the guts of Lua but we’ll get there eventually.

Morgan / 2017-10-28 / Uncategorized / 0 Comments