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.

local masked = crypto.mask(<flag constant>, "XVTY")
crypto.encrypt("AES-CBC", <key>;, masked)

rather than;

crypto.encrypt("AES-CBC", <key>;, crypto.mask(<flag constant>, "XVTY"))