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
HI,
nice writeup :). from where can i get the ‘xor’ tool ?
Author
Here it is, sorry for php :D