NuitDuHack 2012 Prequals – executable2.ndh

Our anonymous guy managed to get access to another
bunch of files. 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.

executable2.ndh
NDH Virtual Machine

Summary: VM in the NDH VM, crackme

This crackme is harder than the first one – it has another simple VM embedded.

[Console]#> bp 0x81e8
Breakpoint set in 0x81e8
[Console]#> run
...
[BreakPoint 1 - 0x81e8]
0x81e8 > syscall (r0 = 0x0003 - read)
test
[SYSCALL output]: 5
0x81e9 > ret
0x8622 > pop r2
0x8624 > pop r1
0x8626 > ret
0x8655 > call 0xfed8
0x8531 > push r2

The inner VM code is located starting at 0x000a, right after the user’s 9-byteinput and :

[Console]#> x/x 0:80
0x0000: 74 65 73 74 0a 00 00 00 00 0a 0a 06 06 00 00 0b 
0x0010: 02 07 4d 06 00 07 02 07 78 07 00 07 09 02 02 07 
0x0020: 61 06 01 07 02 07 02 07 01 07 09 02 02 07 72 06 
0x0030: 02 07 02 07 43 07 02 07 09 02 02 07 31 06 03 07 
0x0040: 02 07 45 07 03 07 09 02 02 07 30 06 04 07 02 07 
0x0050: 03 07 04 07 09 02 02 07 4c 06 05 07 02 07 7f 07 
0x0060: 05 07 09 02 02 07 64 06 06 07 02 07 0f 07 06 07 
0x0070: 09 02 02 00 01 0b 00 00 00 00 00 00 00 00 00 00

The opcode switch is located at 0x8531. Disassembler has a mistake here, so let’s disassemble from 0x8532:
notice: I patched a disassembler to show call’s absolute addresses; though due to disasm fails we need to increment the addresses sometimes

[Console]#> dis 8532:100
0x8532: pop r2
0x8534: call 0xfdf9 //8331
0x8538: cmpb r0, #0b
0x853c: jnz 0x0003 //8542
0x853f: jmpl 0x008f //85d1
0x8542: cmpb r0, #01
0x8546: jnz 0x0007 //8550
0x8549: call 0xfeec //8439
0x854d: jmpl 0xffe4 //8534
0x8550: cmpb r0, #02
0x8554: jnz 0x0007 //855e
0x8557: call 0xff0e //8469
0x855b: jmpl 0xffd6 //8534
0x855e: cmpb r0, #03
0x8562: jnz 0x0007 //856c
0x8565: call 0xfe7c //83e5
0x8569: jmpl 0xffc8 //8534
0x856c: cmpb r0, #04
0x8570: jnz 0x0007 //857a
0x8573: call 0xfe98 //840f
0x8577: jmpl 0xffba //8534
...

There are 11 opcodes: from 01 up to 11. Let’s learn opcode #1:

0x843c: push r1
0x8440: pop r2
0x8442: call 0xfeeb //8331
0x8446: mov r1, r0
0x844a: call 0xfee3 //8331
0x844e: call 0xfeb9 //830b
0x8452: mov r2, r0
0x8456: mov r0, r1
0x845a: mov r1, r2
0x845e: call 0xfebc //831e
0x8462: pop r2
0x8464: pop r1
0x8466: pop r0
0x8468: ret

It contains calls to three functions.

  • func_8331 checks a code pointer at [0x0009], gets next byte and increases the pointer:

    [Console]#> dis 8331:20
    0x8331: push r1
    0x8335: pop r2
    0x8337: movl r0, #0x0009
    0x833c: mov r1, [r0]
    0x8340: mov r2, [r1]
    0x8344: inc r1
    0x8346: mov [r0], r1
    0x834a: mov r0, r2
    0x834e: pop r2
    0x8350: pop r1
  • func_830b gets byte by address: [r0]

    [Console]#> dis 830c:20
    0x830c: pop r1
    0x830e: movl r1, #0x0000
    0x8313: add r1, r0
    0x8317: mov r0, [r1]
    0x831b: pop r1
    0x831d: ret
  • func_831e stores a byte: [r0] = r1

    [Console]#> dis 831f:20
    0x831f: pop r2
    0x8321: movl r2, #0x0000
    0x8326: add r2, r0
    0x832a: mov [r2], r1
    0x832e: pop r2
    0x8330: ret

Now we can guess what opcode #1 is doing:

0x843c: push r1
0x8440: pop r2
0x8442: call 0xfeeb //8331 get next byte (operand 1)
0x8446: mov r1, r0
0x844a: call 0xfee3 //8331 get next byte (operand 2)
0x844e: call 0xfeb9 //830b load [op2]
0x8452: mov r2, r0
0x8456: mov r0, r1
0x845a: mov r1, r2
0x845e: call 0xfebc //831e store [op1] = [op2]
0x8462: pop r2
0x8464: pop r1
0x8466: pop r0
0x8468: ret
01 xx yy -> [xx] = [yy]

And so on, after analyzing all the opcodes, we can write a disassembler:

code = """
	02  07 4d 06 00 07 02 07 78 07 00 07 09 02 02 07 
	61  06 01 07 02 07 02 07 01 07 09 02 02 07 72 06 
	02  07 02 07 43 07 02 07 09 02 02 07 31 06 03 07 
	02  07 45 07 03 07 09 02 02 07 30 06 04 07 02 07 
	03  07 04 07 09 02 02 07 4c 06 05 07 02 07 7f 07 
	05  07 09 02 02 07 64 06 06 07 02 07 0f 07 06 07
"""
code = map(lambda s: int(s, 16), code.strip().split())
 
ip = 0
while ip < len(code):
	opcode = code[ip]
	op1, op2 = code[ip+1:ip+3]
	print ip,":", "%02x %02x %02x:\t" % (opcode, op1, op2),
 
	if opcode == 1:
		print "[%2x] = [%2x]" % (op1, op2)
		ip += 2
	if opcode == 2:
		print "[%2x] = %2x" % (op1, op2)
		ip += 2
	if opcode == 3:
		print "inc [%2x]" % op1
		ip += 1
	if opcode == 4:
		print "dec [%2x]" % op1
		ip += 1
	if opcode == 5:
		print "[%2x] = [%2x] + [%2x]" % (op1, op1, op2)
		ip += 2
	if opcode == 6:
		print "[%2x] = [%2x] ^ [%2x]" % (op1, op1, op2)
		ip += 2
	if opcode == 7:
		print "[%2x] == [%2x]" % (op1, op2)
		ip += 2
	if opcode == 8:
		print "je %2x" % (op1)
		ip += 1
	if opcode == 9:
		print "jne %2x" % (op1)
		ip += 1
	ip += 1

Result:

 
$ py dis.py 
0 : 02 07 4d:	[ 7] = 4d
3 : 06 00 07:	[ 0] = [ 0] ^ [ 7]
6 : 02 07 78:	[ 7] = 78
9 : 07 00 07:	[ 0] == [ 7]
12 : 09 02 02:	jne  2
14 : 02 07 61:	[ 7] = 61
17 : 06 01 07:	[ 1] = [ 1] ^ [ 7]
20 : 02 07 02:	[ 7] =  2
23 : 07 01 07:	[ 1] == [ 7]
26 : 09 02 02:	jne  2
28 : 02 07 72:	[ 7] = 72
31 : 06 02 07:	[ 2] = [ 2] ^ [ 7]
34 : 02 07 43:	[ 7] = 43
37 : 07 02 07:	[ 2] == [ 7]
40 : 09 02 02:	jne  2
42 : 02 07 31:	[ 7] = 31
45 : 06 03 07:	[ 3] = [ 3] ^ [ 7]
48 : 02 07 45:	[ 7] = 45
51 : 07 03 07:	[ 3] == [ 7]
54 : 09 02 02:	jne  2
56 : 02 07 30:	[ 7] = 30
59 : 06 04 07:	[ 4] = [ 4] ^ [ 7]
62 : 02 07 03:	[ 7] =  3
65 : 07 04 07:	[ 4] == [ 7]
68 : 09 02 02:	jne  2
70 : 02 07 4c:	[ 7] = 4c
73 : 06 05 07:	[ 5] = [ 5] ^ [ 7]
76 : 02 07 7f:	[ 7] = 7f
79 : 07 05 07:	[ 5] == [ 7]
82 : 09 02 02:	jne  2
84 : 02 07 64:	[ 7] = 64
87 : 06 06 07:	[ 6] = [ 6] ^ [ 7]
90 : 02 07 0f:	[ 7] =  f
93 : 07 06 07:	[ 6] == [ 7]

Hmm it’s again just a xor check! Do all ctf-vm creators know only xor? ;)

$ xor -h 4d617231304c64 -h 78024345037f0f
5c1t33k
$ nc sci.nuitduhack.com 4002
Please enter Sciteek admin password: 5c1t33k
...
<<< a source for 4004 here >>>
...

Challenge solved!

2 comments

    • xGeek on September 11, 2012 at 00:44
    • Reply

    HI,
    nice writeup :). from where can i get the ‘xor’ tool ?

    1. Here it is, sorry for php :D

Leave a Reply

Your email address will not be published.