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

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

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

I’m presently contemplating a project to build a small DC/storage outbuilding down the side of my garage, basically to relocate all the random servers and such which are currently located in my study to somewhere else, ideally with proper climate control.

Now of course I don’t want the inconvenience of actually having to go out there to yutz with things when I need to so I went digging around on eBait for an IP KVM, turned up a “Prima IP-16 Uniclass” for $140, seemed like a reasonable price, unfortunately it’s one of the ones that uses an oddball cable that feeds PS2/USB and VGA via an HD15 connector. But I digress.

When I got it home, I started poking around if of course. First thing I wanted to do was to upload my own SSL certs to it, via the vendor-supplied web interface you can upload *SOME* certs but not others, one of the ones you can’t upload is the SSL cert for the web server, no biggie, let’s make that happen.

First things first, these guys have a non-password-protected root console available on Serial port 1, w00t! What an awesome start, fire up realterm, connect to port, determine that we’re talking 115200-8-n-1, reboot box. Surprise surprise it’s running Linux (on some flavour of Intel XScale processor);

Prima IP-16 Bootloader

Prima IP-16 Bootloader

Having a sniff around we find that we’ve got one linux user, and that’s root, not sure what the default password is, but since it’s just crypt’d I could feed it to cudahashcat, or since I’ve got a root shell I could just change it…

Prima IP-16 Password Reset

Prima IP-16 Password Reset

Let’s have a look at inittab;

/> cat /etc/inittab
#slog:unknown:/bin/syslogd -n
#klog:unknown:/bin/klogd -n
#inet:unknown:/bin/inetd
webs:unknown:/bin/webs
kleserver:unknown:/bin/kleserver
button:unknown:/bin/button

Hmm, we’ve an inetd but it’s disabled, I wonder what runs out of that?

/> cat /etc/inetd.conf
discard dgram udp wait root /bin/discard
discard stream tcp nowait root /bin/discard
telnet stream tcp nowait root /sbin/telnetd
ftp stream tcp nowait root /bin/ftpd -l

Woo, telnet and ftp, just what I always wanted, let’s run it up and see if she works

/> /bin/inetd&
[1533]

Sure enough, now we can telnet over and login using the root password we set;

Prima IP-16 Telnet Access

Prima IP-16 Telnet Access

Now that we have a fully functional terminal we can actually use “vi” so let’s make that inetd change stick;

/> cat /etc/inittab
#slog:unknown:/bin/syslogd -n
#klog:unknown:/bin/klogd -n
inet:unknown:/bin/inetd
webs:unknown:/bin/webs
kleserver:unknown:/bin/kleserver
button:unknown:/bin/button
/> reboot

Next step, find the certificates, a bit more nosing around turned up /etc/config/certs;

/> cd /etc/config/certs
/etc/config/certs> ls
dh1024.pem        dserverkey.pem    server.crt        webserverkey.pem
dh512.pem         ldapcert.crt      serverkey.pem
droot.crt         ldapkey.pem       webroot.crt
dserver.crt       root.crt          webserver.crt
/etc/config/certs>

OK, so here are all the certs, not just the ones the web UI will let us update, I reckon that “webserver.crt” and “webserverkey.pem” are probably what we’re looking for, so we can just drop in our own certs and we’re golden, right? Not quite so fast there, there’s a problem;

/etc/config/certs> cat webserverkey.pem
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,D0C717240CAC789F

That’s a fly in the ointment, the private key is encrypted, probably with some hard-coded password. Time for some reverse engineering, by the wonders of FTP we download /bin/webs and feed it to our old friend IDA, have a sniff around in the “Strings” sub-view to find ourselves a starting point;

Prima IP-16 webs IDA "Strings"

Prima IP-16 webs IDA “Strings”

A bit of digging around leads to;

Prima IP-16 webs Code

Prima IP-16 webs Code

Oh, look “dserverpass” that sounds suspiciously like a really un-creative private key password, let’s grab the keys from the KVM and see what we can see;

$ openssl rsa -in webserverkey.pem -out webserver.key
Enter pass phrase for webserverkey.pem: dserverpass
writing RSA key

Awesome, so the password is, in fact “dserverpass”, so let’s go encrypt our private key with that password;

$ openssl rsa -in privkey.pem -out webserverkey.pem -des3
writing RSA key
Enter PEM pass phrase: dserverpass
Verifying - Enter PEM pass phrase: dserverpass

FTP the cert and key up, reboot the device and no webserver…

/> cat /var/log/webs.log
SSL: Unable to set certificate file </etc/config/certs/webserver.crt>

Bugger… The cert which was there to begin with used a 1024-bit RSA key, maybe it doesn’t like 2048-bit keys…

$ openssl req -newkey rsa:1024 -keyout -nodes test.key -x509 -days 365 -out test.crt
Generating a 1024 bit RSA private key
.................++++++
..++++++
writing new private key to 'test.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

$ openssl pkey -in test.key -out test.pem -des3
Enter PEM pass phrase: dserverpass
Verifying - Enter PEM pass phrase: dserverpass

Upload, rename, reboot, et. viola, it’s presenting our new cert… That’s rather a kick in the teeth.

At least we got our own cert into it, but I’d prefer something a bit more than 1024-bit…

Not sure whether it’s a limitation to how they’ve compiled the openssl libs for it, or if it’s a limitation of their implementation, I’ll have to do some digging and figure out which it is, if it’s a limitation in their code it might be possible to patch it (and fix the crappy hard-coded key passphrase while I’m at it…) if it’s a limitation in the libs it may well have to go in the “too hard” basket, because I don’t particularly fancy building a cross-compile toolchain for an unknown platform, time will tell.

In the meantime I’ll leave it as is and disable inetd again.

Morgan / 2016-11-25 / Uncategorized / 0 Comments