Mar
26

## NuitDuHack 2012 Prequals – executable2.ndh

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. 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!