Morgan / 2018-04-03 / Uncategorized / 0 Comments

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

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

msf > services


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


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;


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
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”;


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; 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;


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_*"

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
        sequence    = 9560,1080,1200

Back to the Kali box, install knockd, and;

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

Quick check with nmap;

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 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('.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('.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;

while :
  if [ -f /opt/sinatra/.raIhUJTLEMAfUW3GmynyFySPw ]
    cp /opt/sinatra/.raIhUJTLEMAfUW3GmynyFySPw /tmp
    echo "We got one!"
    exit 0

Run the script in the background, bounce the service;

root@ip-10-0-37-251:/tmp# ./ &
[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 .*

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('.raIhUJTLEMAfUW3GmynyFySPw'));'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


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))

image = ChunkyPNG::Image.from_file('joker.png')! do |pixel|

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.).


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;

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;

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;

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))

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

Ruxcon 2017 Hardware Hacking Village Wrap

Only eight hours sleep across the weekend (gotta love insomnia…) but we’re done for another year.

I was pretty happy with the level of engagement we saw at the Hardware Hacking Village this year, catering to a group like that is always a bit tough.

You can find my slide deck, the badge and simple solder build docs and badge firmware at my Ruxcon 2017 HHV page.

We handed out 111 of the “Simple Solder” kits across the course of the weekend, so we were pretty much spot on with the 120 that we ordered for (Gotta say, really wasn’t expecting to go through that many of those kits, last year IIRC we only ran out of the 50 we’d bought in by about 11 on the Sunday.)

The badge was the real surprise, last year we only went through about 10 of the kits on Saturday and we ended up handing them out to anybody after we ran out of Simple Solder kits on Sunday. This year we blew through 30 badge kits in the first 3 hours the village was open, going to have to figure out how to get 50+ into the budget for next year.

A note for those people who spoke to me and asked if the designs were going to be put up online. I have good news and bad news, the bad news is; the gerbers will not be being put up online. The art on the badge this year was commissioned on the understanding that it would be used for a limited run, as such we can’t publish them. However, if you’re quick there is some good news; I do have several spare boards (I actually had them with me at the village but my brain was too fried on Sunday to think of offering them to you.) because at the last minute I panelised them two up which meant we could get 40 for what it would’ve cost us for 20. I’m happy to send one out to anyone in AU gratis, if you’re international and you really want one I can send one out but it may have to be at your cost. Hit me up via email ( if you’re after one.

The only real downer from the weekend was that two of the Bus Pirates that Ruxcon had generously provided for Dr Noise’s workshop walked out with delegates.

Thanks to everyone who attended, without you the conference wouldn’t happen. And a special thanks to Chris Spencer without whose insanely tireless work we wouldn’t have a con πŸ˜‰

Over the coming weeks I’ll put up some posts around the badge flag and maybe some bits and pieces about the IoT CTF stuff, stay tuned.

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

Adventures in IPv6 – The Next Generation – Part 2

Part 2 – Configuring The IPSec Tunnel

First thing you’ll need to do is decide whether you’re going to use certificate based or PSK authentication between the ends of the IPSec tunnel, if you decide on certificate based auth, you’ll of course need some certificates, your choice where you get them from, I created an internal CA on my local pfSense box and used that to issue the certificates for my tunnel.

Next step is to configure IPSec Phase 1 (the configurations of each end of the IPSec tunnel are basically mirrors of each other, so I’ll only show one side here);

One end of the IPSec Phase 1 config, the other end mirrors this

Now onto Phase 2 (again, this is the “outside” host, the “inside” host mirrors this);

“Outside” end IPSec Phase 2 config, the other end mirrors this.

Once that’s done, verify that the tunnel comes up.

IPSec Status showing the tunnel is up and online

At this point you’re largely done and dusted, you need to assign an appropriate IP address (one from your \48) to the LAN interface on your local pfSense box, configure DHCPv6 however you choose, personally I assign the chunk of addresses from ::00FF to ::EFFF to my wired LAN then delegate \62 prefixes out to every other router in my network to dispense as needed, it’s unlikely I’m ever going to have more than 4 subnets behind any of them, should that change in future I can always change the delegation size.


The config as described here does work, but there’s an issue which seems intractable, inbound connections on IPv6 are fine, connections between “inside” hosts and the “outside” host are fine, connections from my “outside” host and the rest of the world are fine, but connections from “inside” to the rest of the world are excessively slow (~70kB/sec), it seems to be an MSS/MTU issue, I suspect that the IPSec overhead is just too much to do efficient IPv6 encapsulation on my connection.

So for the purposes of exposing internal hosts this is fine (because my end manages MSS at that point), but for actually connecting out to the IPv6 internet it’s not great.

My next step will be to try again with another tunneling protocol that’s lighter weight than IPSec, more on that when I get around to it πŸ˜‰

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

Adventures in IPv6 – The Next Generation – Part 1

Part 1 – Background and Approach

I posted a few days ago about my Adventures in IPv6 which was a brief summary of getting IPv6 connectivity via the Hurricane Electric free IPv6 Tunnel Broker service, in that post I remarked about some issues I had due to the fact that my tunnel was terminated in Japan, this series of posts will cover “The Next Generation” where I basically set up a “personal” IPv6 Tunnel service.

A quick note ahead of time; in my setup, I’m using pfSense on both ends of the tunnel, this is mainly because I’m lazy. Most of the parameters in pfSense map directly to config files though so it should be relatively straight forward to translate the steps here into raw config files if that is more to your taste.

This project morphed several times as it encountered a few stumbling blocks on its way to completion:

  • Plan A – find an Australian IPv6 tunnel broker;
    NOPE, there do not appear to be any active IPv6 tunnel brokers with Australian PoPs any more.

    • SixXS stopped accepting signups and requests for tunnels January 2016 declaring “Call your ISP for IPv6” month, which given that we’re basically awash with IPv4 addresses here in AU, was never really going anywhere for us.
    • The AARNet tunnel broker service appears to have been discontinued, but the web portal has not been decommissioned
    • IPv6Now apparently used to run a tunnel broker service, there is nothing on their website any more and they did not respond to my email
  • Plan B – find some “reasonably priced” colo in Australia with IPv6 transit to drop a router into;
    NOPE, “reasonably priced” colo is a unicorn in Australia (I recall this being the case some many years ago, things have not really changed), the cheapest option I found was $85/month, which would nearly double the monthly price of my connection.
  • Plan C – find a VPS provider with IPv6 connectivity and roll my own tunnel broker.

Plan C it is

Initially I found HostUS (affiliate link), who offered reasonably priced VPSes in their Australian PoP; I signed up with them, started to play with things then discovered that OpenVZ wasn’t going to work for me. After some discussions with support basically concluded that they weren’t going to be able to satisfy my requirements, which is unfortunate, because I was REALLY impressed with their support department. I cancelled my service and received full refund (quick and painless, a credit to their approach).

Did some rummaging around for KVM VPSes hosted in Australia with IPv6 connectivity, apparently I wasn’t looking under the right rocks because I didn’t manage to find anything myself. Time to ask the brainstrust, asked on a couple of lists to see if anybody knew of any suitable providers, got referred to Vultr (affiliate link).

Vultr, I’ve got to say, is pretty much the slickest interface I’ve ever seen for VPS/Cloudy stuff management (not that I have a huge amount of experience but every other provider I’ve worked with seems to run WHMCS with varying levels of customisation), and their billing system is nicely flexible. Unfortunately the largest allocation Vultr will provide is a /64, but they are happy to provide BGP sessions so you can announce your own IP space. As such, for my purposes I needed to purchase some IPv6 addresses from a third-party.

(Re)enter HostUS;
HostUS will provide an APNIC issued /48 for $30USD/year, which seems pretty reasonable to me, so I purchased a block from them, requested an LOA (Letter Of Authorisation) from support, per my previous interactions with their support department the request was handled quickly and with a minimum of friction.

Next I raised a support ticket with Vultr to get BGP enabled on my account, basically had to provide a Use-Case, nominate a BGP password and attach the LOA to them, they’d come back within the day to say it was ready.

Time to start configuring our Tunnel, there are various options you could use for this, GRE, GIF, IPSec, probably OpenVPN and I’m sure there are others, I chose IPSec because it allows an IPv4 phase one with IPv6 and/or IPv4 encapsulation within phase two (if the slash and burn my ISP is presently engaged in on their international network (or rather the formerly pretty nice international network of one of their recent acquisitions) continues, there may come a time when tunneling my IPv4 traffic out through my Vultr instance and via their international transit may become preferable) the data is encrypted in transit (the other options are cleartext encapsulations) and the overheads are OK.

OK, this is probably an opportune point at which to break the post, the story will continue in Part 2.

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

Adventures in IPv6

So I was having a discussion on one of the boards I lurk on some weeks ago around IPv6, and that spurred me to finally pull my finger out and actually DO something about it (up until this point I’d been basically taking the “Ostrich” path of sticking my head in the sand and pretending it didn’t exist).

My current provider has not deployed dual-stack to the edge, and all indications are that there will be significant costs involved in doing so (and they’ve recently been purchased by the biggest cheapskate in the ISP game, so I’m not going to see native IPv6 any time soon), and I’m rather attached to my extremely lightly contested HFC connection, so switching to another provider for native IPv6 is not an option, time to do some fishing.

A bit of looking around reveals the existence of “Tunnel Brokers” who provide 6in4 tunneled connectivity, awesome, let’s go find one of them…

The largest tunnel broker in the world (and also arguably the “centre of the IPv6 internet” because they were basically the first major IPv6 deployment on the internet) is Hurricane Electric and their service which offers up to 5 free 6in4 tunnels (and optionally a routed /48 for each), signed up there, created a tunnel, followed this (now rather antiquated, but still usable) document to get the tunnel up and running on my border router.

Configured DHCPv6 to start serving the routed /64 provided by HE, et voila, IPv6 on my LAN. Surprisingly straight forward. The hardest part was needing to remove the “REALLY REALLY disable IPv6” regkey from my Windows box (because that meant I needed to reboot it πŸ˜‰ ).

So at this point I’ve got IPv6 connectivity working on the LAN, time to go deeper (I’ve got several subnetworks for different purposes), and dig into the world of Prefix Delegation.

Log into HE, click button that says “assign /48”, /48 allocated, woo!

Update DHCPv6 config on pfSense, set “Prefix Delegation Range”, set “Prefix Delegation Size” to something sensible (I used 62, so I can have 4 networks behind each of my routers, realistically I could’ve used 56 and still not been remotely in danger of running out of prefix…).

Enable IPv6 WAN interface on (one of several) Wireless router, router pulls prefix and starts assigning to clients. Well shit that was easy…

Throw in an inbound rule to allow ICMP from one of my VPSes with IPv6 connectivity into one of the addresses on my wireless network, ping address, lo and behold, it “just works(tm)”…

All in all I was surprised at just how painless it was to get up and running, the nicest thing about this is I don’t have to bend over backwards to renew the LE certs I use on my internal services anymore πŸ™‚

Unfortunately it’s not all roses though;

IPv6 in and of itself is pretty straight forward and more or less “just works(tm)”, the major ugliness comes in when you start looking into stuff like NAT64 and DNS64 which are dirty hacks intended to get us through the transition between “getting everything on IPv6” and “getting everything off IPv4”.

All in all, this setup works pretty well but there is one major issue with it. HE do not have any PoP’s in Australia, the closest PoP (routing-wise) I could find was Tokyo, which is not a major issue (though it adds 300ms of latency) in and of itself, but it is problematic in some situations because I have an IPv4 address which geolocates to Australia, where my IPv6 address geolocates to Japan. CloudFlare in particular does not like this, I’ve had a large increase in the incidence of “prove your human” type prompts out of it when I access CloudFlare protected sites. I’ve also had one attempt at purchasing something online being flagged as fraudulent because my IP address geolocates to a country that doesn’t match my CC billing address.

I’m presently working on resolving that issue, and once I get that working I’ll write a post on that.

Morgan / 2017-01-24 / Uncategorized / 0 Comments

Injecting Your Own SSL Certs Into The Uniclass Prima IP-16 Redux

So apparently I’m a muppet, I spent a while doing some digging around in disassemblies of the client and server components, only to discover that the certificate file I dropped onto the KVM was in the wrong format…

So, I grabbed another copy, renamed the files appropriately and restarted the webserver, lo and behold it came up and is now presenting the correct certificate. Hallelujah.

Next step, convince it to use that cert for the actual KVM bits, easy enough, copy webserver.crt and webserverkey.pem to dserver.crt and dserverkey.pem respectively, restart kleserver, comes up and all is happy, now it’s time to connect to the KVM using their viewer…

IP Viewer SSL Error

Holy crap, they actually validate certs… OK, need to fix that…

There’s a “Trust” directory under the client which presumably is where you’re supposed to shove this stuff, but it’s undocumented and I can’t be bothered reversing that part of things. No matter, there’s a file called “droot.crt” sitting in the client directory, sounds promising, replace that with fullchain.pem from the Let’s Encrypt cert, still not happy, for some reason it appears that the “fullchain” from LE isn’t actually the full chain, it only contains the intermediate CA, evidently we need the whole chain, go dig up a PEM for “DST Root CA X3”, throw it into the droot.crt file, try again, et voilΓ  it works! I’ll have to cook up an automatic update mechanism still, but for the moment, that’ll do.

Next thing I want to do for this is to replace their shitty client with something that sucks less…

Morgan / 2016-12-22 / Uncategorized / 0 Comments