From: Jessica <jessica@megacortek.com>
To: LSE <lse@megacortek.com>
Subject: New email from our contact
Attachments : executable2.ndh

Thank you again for your help, our technical staff has a pretty good
overview of the new device designed by Sciteek. Your account will be
credited with $500.

You did work hard enough to impress me, your help is still more than
welcome, you will get nice rewards. Our anonymous guy managed to get access
to another bunch of files. Here is one of his emails:

---
Hi there, see attached file for more information. It was found on
http://sci.nuitduhack.com/EgZ8sv12.
---

Maybe you can get further than him by exploiting this website.  We also
need to get as much information as possible about the file itself. If you
succeed, you will be rewarded with $2500 for the ndh file and $1000 for the
website. Please use "Sciteek shortener" and "strange binary file #2"
titles.

Regards,
Jessica.

This time let’s work on the attached file: executable2.ndh. Like all of the binary challenges at this contest, this is an executable for the contest VM, so we can reuse our very nice IDA plugin to analyze it!

After the initial analysis, we can see a very suspect function at address 0x8531. It has a loop which dispatch at each iteration to a subfunction according to the return value of a function. Looking a bit more at it, we were convinced really fast that it is actually a VM inside the VM.

After 20 minutes of analysis, we wrote this simple documentation of the VM opcodes:

01 RD RS: MOV RD, RS
    PC += 3
    REG[RD] <- REG[RS]

02 RD BB: MOVB RD, #0xBB
    PC += 3
    REG[RD] <- 0xBB

03 RD: INC RD
    PC += 2
    REG[RD] <- REG[RD] + 1

04 RD: DEC RD
    PC += 2
    REG[RD] <- REG[RD] - 1

05 RD RS: ADD RD, RS
    PC += 3
    REG[RD] <- REG[RD] + REG[RS]

06 RD RS: XOR RD, RS
    PC += 3
    REG[RD] <- REG[RD] ^ REG[RS]

07 RA RB: CMP RA, RB
    PC += 3
    REG[R8] <- REG[RA] == REG[RB]

08 EA: JEQ EA
    IF REG[R8] == 1
        PC = EA
    ELSE
        PC += 2

09 EA: JNE EA
    IF REG[R8] == 0
        PC = EA
    ELSE
        PC += 2

0A EA: JMP EA
    PC = EA

0B: STOP
    HAMMERTIME

We also write a very simple disassembler to avoid having to do that by hand. Because hey, we’re lazy, and we had to procrastinate the stegano challenge!

import sys

OPLEN = {
    0x01: 3,
    0x02: 3,
    0x03: 2,
    0x04: 2,
    0x05: 3,
    0x06: 3,
    0x07: 3,
    0x08: 2,
    0x09: 2,
    0x0A: 2,
    0x0B: 1
}

def disas_one(b):
    if b[0] == 1:
        return "MOV R%d, R%d" % (b[1], b[2])
    elif b[0] == 2:
        return "MOVB R%d, #0x%02X" % (b[1], b[2])
    elif b[0] == 3:
        return "INC R%d" % b[1]
    elif b[0] == 4:
        return "DEC R%d" % b[1]
    elif b[0] == 5:
        return "ADD R%d, R%d" % (b[1], b[2])
    elif b[0] == 6:
        return "XOR R%d, R%d" % (b[1], b[2])
    elif b[0] == 7:
        return "CMP R%d, R%d" % (b[1], b[2])
    elif b[0] == 8:
        return "JEQ %02X" % b[1]
    elif b[0] == 9:
        return "JNE %02X" % b[1]
    elif b[0] == 10:
        return "JMP %02X" % b[1]
    elif b[0] == 11:
        return "STOP"

def disas(bytes):
    i = 0
    while i < len(bytes):
        opcode = bytes[i]
        all_bytes = bytes[i:i+OPLEN[opcode]]
        dis = disas_one(all_bytes)
        repr = ' '.join("%02X" % b for b in all_bytes)
        if len(all_bytes) < 3:
            repr += '\t'
        print '%02X\t%s\t%s' % (i, repr, dis)
        i += OPLEN[opcode]

if __name__ == '__main__':
    fn = sys.argv[1]
    off = int(sys.argv[2], 16)
    end_off = int(sys.argv[3], 16)

    bytes = map(ord, open(fn).read()[off:end_off])
    disas(bytes)

After running this on the interesting part of executable2.ndh, we got the following assembly code:

00      0A 06           JMP 06
02      06 00 00        XOR R0, R0
05      0B              STOP
06      02 07 4D        MOVB R7, #0x4D
09      06 00 07        XOR R0, R7
0C      02 07 78        MOVB R7, #0x78
0F      07 00 07        CMP R0, R7
12      09 02           JNE 02
14      02 07 61        MOVB R7, #0x61
17      06 01 07        XOR R1, R7
1A      02 07 02        MOVB R7, #0x02
1D      07 01 07        CMP R1, R7
20      09 02           JNE 02
22      02 07 72        MOVB R7, #0x72
25      06 02 07        XOR R2, R7
28      02 07 43        MOVB R7, #0x43
2B      07 02 07        CMP R2, R7
2E      09 02           JNE 02
30      02 07 31        MOVB R7, #0x31
33      06 03 07        XOR R3, R7
36      02 07 45        MOVB R7, #0x45
39      07 03 07        CMP R3, R7
3C      09 02           JNE 02
3E      02 07 30        MOVB R7, #0x30
41      06 04 07        XOR R4, R7
44      02 07 03        MOVB R7, #0x03
47      07 04 07        CMP R4, R7
4A      09 02           JNE 02
4C      02 07 4C        MOVB R7, #0x4C
4F      06 05 07        XOR R5, R7
52      02 07 7F        MOVB R7, #0x7F
55      07 05 07        CMP R5, R7
58      09 02           JNE 02
5A      02 07 64        MOVB R7, #0x64
5D      06 06 07        XOR R6, R7
60      02 07 0F        MOVB R7, #0x0F
63      07 06 07        CMP R6, R7
66      09 02           JNE 02
68      02 00 01        MOVB R0, #0x01
6B      0B              STOP

We computed the XOR for each character value, and got the following key: 5c1t33k. After testing it, we got in result… the source code of the second VM exercise we already reversed. Yay! Fortunately, after attaching it as an answer to Jessica, we got the points we deserved!