NDH2K12 Prequals: executable2.ndh writeup (port 4002)
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!