11/03/2013

Another NDH quals another VM wait ...

Introduction

Last weekend I participated in the Nuit du Hack CTF Quals 2013 with my teamate delroth, you can find an excellent write-up about escaping a Python sandbox on his blog.

So I decided to post a writeup too about the last crackme challenge "Crackme #1".

This Reverse Engineering challenge was a virtual machine, so I decided to reverse the full vm, but today I figured out something ...

Do you remember last ndh prequals ? no no I will not talk about bmp chall but about VMNDH-2k12, it was exactly the same VM (I'm very sad to have lost some hours to reverse something that I know), the only changes was the opcode values, so this post will not deal with the vm stuff you have all the information on this website.

VM Opcode

You can find the vm dump at :

LOAD:000000000048F2E8 vm_dump         db  7Fh ; 
LOAD:000000000048F2E9                 db  45h ; E
LOAD:000000000048F2EA                 db  4Ch ; L
LOAD:000000000048F2EB                 db  46h ; F
LOAD:000000000048F2EC                 db  72h ; r
LOAD:000000000048F2ED                 db    3
LOAD:000000000048F2EE                 db  0Ah

Real vm code start at offset 0x06 (byte 0x0A) and the size of the vm code is 0x371 (881 bytes).

Do you want to mary me ?

I love IDA, and I like writting plugins, scripts, processor (when I don't have to reverse IDA for understanding how their api works :p) in python, so after reversing almost the same vm, I decided to write my ndh2k13 processor, here is the code :

from idaapi import * class DecodingError(Exception): pass class NDHProcessor(processor_t): id = 0x8000 + 5855 flag = PR_ADJSEGS | PRN_HEX cnbits = 8 dnbits = 8 psnames = ["ndh2k13"] plnames = ["ndh2k13 VM CPU"] segreg_size = 0 instruc_start = 0 assembler = { "flag": AS_NCHRE | ASH_HEXF4 | ASD_DECF1 | ASO_OCTF3 | ASB_BINF2 | AS_NOTAB, "uflag": 0, "name": "NDH assembler", "origin": ".org", "end": ".end", "cmnt": ";", "ascsep": '"', "accsep": "'", "esccodes": "\"'", "a_ascii": ".ascii", "a_byte": ".byte", "a_word": ".word", "a_bss": "dfs %s", "a_seg": "seg", "a_curip": "PC", "a_public": "", "a_weak": "", "a_extrn": ".extern", "a_comdef": "", "a_align": ".align", "lbrace": "(", "rbrace": ")", "a_mod": "%", "a_band": "&", "a_bor": "|", "a_xor": "^", "a_bnot": "~", "a_shl": "<<", "a_shr": ">>", "a_sizeof_fmt": "size %s", } reg_names = regNames = [ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "SP", "BP", "CS", "DS" ] instruc = instrs = [ { 'name': 'PUSH', 'feature': CF_USE1 }, { 'name': 'PUSHB', 'feature': CF_USE1 }, { 'name': 'PUSHW', 'feature': CF_USE1 }, { 'name': 'NOP', 'feature': 0 }, { 'name': 'POP', 'feature': CF_USE1 }, { 'name': 'MOV', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MOVB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MOVW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADD', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADDB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADDW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUBB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUBW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MUL', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MULB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MULW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIV', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIVB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIVW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'INC', 'feature': CF_USE1 }, { 'name': 'DEC', 'feature': CF_USE1 }, { 'name': 'OR', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ORB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ORW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'AND', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ANDB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ANDW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XOR', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XORB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XORW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'NOT', 'feature': CF_USE1 }, { 'name': 'JZ', 'feature': CF_USE1 }, { 'name': 'JNZ', 'feature': CF_USE1 }, { 'name': 'JMPS', 'feature': CF_USE1 | CF_STOP }, { 'name': 'TEST', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMP', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMPB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMPW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CALL', 'feature': CF_USE1 | CF_CALL }, { 'name': 'RET', 'feature': CF_STOP }, { 'name': 'JMPL', 'feature': CF_USE1 | CF_STOP }, { 'name': 'END', 'feature': CF_STOP }, { 'name': 'XCHG', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'JA', 'feature': CF_USE1 }, { 'name': 'JB', 'feature': CF_USE1 }, { 'name': 'SYSCALL', 'feature': 0 }, ] instruc_end = len(instruc) def __init__(self): processor_t.__init__(self) self._init_instructions() self._init_registers() def _init_instructions(self): self.inames = {} for idx, ins in enumerate(self.instrs): self.inames[ins['name']] = idx def _init_registers(self): self.reg_ids = {} for i, reg in enumerate(self.reg_names): self.reg_ids[reg] = i self.regFirstSreg = self.regCodeSreg = self.reg_ids["CS"] self.regLastSreg = self.regDataSreg = self.reg_ids["DS"] def _read_cmd_byte(self): ea = self.cmd.ea + self.cmd.size byte = get_full_byte(ea) self.cmd.size += 1 return byte def _read_reg(self): r = self._read_cmd_byte() if r >= 0x0A: raise DecodingError() return r def _ana_ntypeinstr(self, name, valid): cmd = self.cmd optype = self._read_cmd_byte() if optype not in valid: raise DecodingError() if optype not in (4, 5, 6): cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() if optype == 0x0: cmd.itype = self.inames[name] cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() elif optype == 0x1: cmd.itype = self.inames[name + "B"] cmd[1].type = o_imm cmd[1].dtyp = dt_byte cmd[1].value = self._read_cmd_byte() elif optype == 0x2: cmd.itype = self.inames[name + "W"] cmd[1].type = o_imm cmd[1].dtyp = dt_word cmd[1].value = self._read_cmd_byte() cmd[1].value |= self._read_cmd_byte() << 8 elif optype == 0x3: cmd.itype = self.inames[name] elif optype == 0x4: cmd.itype = self.inames[name + "B"] cmd[0].type = o_imm cmd[0].dtyp = dt_byte cmd[0].value = self._read_cmd_byte() elif optype == 0x5: cmd.itype = self.inames[name + "W"] cmd[0].type = o_imm cmd[0].dtyp = dt_word cmd[0].value = self._read_cmd_byte() cmd[0].value |= self._read_cmd_byte() << 8 elif optype == 0x6: cmd.itype = self.inames[name + "B"] cmd[0].type = o_phrase cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() elif optype == 0xA: cmd.itype = self.inames[name] cmd[1].type = o_phrase cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() else: raise DecodingError() def _ana_one_r(self, name): cmd = self.cmd cmd.itype = self.inames[name] cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() def _ana_two_r(self, name): cmd = self.cmd cmd.itype = self.inames[name] cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() def _ana_jmp(self, name, size=16): cmd = self.cmd cmd.itype = self.inames[name] addr = self._read_cmd_byte() if size == 16: addr |= self._read_cmd_byte() << 8 if (addr & 0x8000): addr -= 0x10000 else: if addr & 0x80: addr -= 0x100 addr += cmd.ea + cmd.size cmd[0].type = o_near cmd[0].dtyp = dt_word cmd[0].addr = addr def _ana(self): cmd = self.cmd opcode = self._read_cmd_byte() if opcode == 0x1F: self._ana_ntypeinstr("PUSH", valid=(3, 4, 5)) elif opcode == 0x0A: cmd.itype = self.inames["JMPL"] self._ana_jmp("JMPL") elif opcode == 0x1C: self._ana_ntypeinstr("MOV", valid=(0, 1, 2, 6, 7, 8, 9, 10)) elif opcode == 0x0C: cmd.itype = self.inames["CALL"] flags = self._read_cmd_byte() if flags == 0x4: addr = self._read_cmd_byte() addr |= self._read_cmd_byte() << 8 if (addr & 0x8000): addr -= 0x10000 addr += cmd.ea + cmd.size cmd[0].type = o_near cmd[0].dtyp = dt_word cmd[0].addr = addr elif flags == 0x3: reg = self._read_reg() cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = reg else: raise DecodingError() elif opcode == 0x30: cmd.itype = self.inames["SYSCALL"] elif opcode == 0x0B: cmd.itype = self.inames["RET"] elif opcode == 0x1A: self._ana_ntypeinstr("SUB", valid=(0, 1, 2)) elif opcode == 0x0D: self._ana_ntypeinstr("CMP", valid=(0, 1, 2)) elif opcode == 0x11: self._ana_jmp("JZ") elif opcode == 0x09: cmd.itype = self.inames["END"] elif opcode == 0x1F: self._ana_jmp("JMPS", size=8) elif opcode == 0x17: self._ana_one_r("INC") elif opcode == 0x10: self._ana_jmp("JNZ") elif opcode == 0x16: self._ana_one_r("DEC") elif opcode == 0x13: self._ana_ntypeinstr("XOR", valid=(0, 1, 2)) elif opcode == 0x0E: self._ana_two_r("TEST") elif opcode == 0x1D: self._ana_one_r("POP") elif opcode == 0x07: self._ana_jmp("JA") elif opcode == 0x0F: self._ana_jmp("JMPS", size=8) elif opcode == 0x06: self._ana_jmp("JB") elif opcode == 0x1B: self._ana_ntypeinstr("ADD", valid=(0, 1, 2)) elif opcode == 0x08: self._ana_two_r("XCHG") elif opcode == 0x19: self._ana_ntypeinstr("MUL", valid=(0, 1, 2)) else: raise DecodingError() return cmd.size def ana(self): try: return self._ana() except DecodingError: return 0 def _emu_operand(self, op): if op.type == o_mem: ua_dodata2(0, op.addr, op.dtyp) ua_add_dref(0, op.addr, dr_R) elif op.type == o_near: if self.cmd.get_canon_feature() & CF_CALL: fl = fl_CN else: fl = fl_JN ua_add_cref(0, op.addr, fl) def emu(self): cmd = self.cmd ft = cmd.get_canon_feature() if ft & CF_USE1: self._emu_operand(cmd[0]) if ft & CF_USE2: self._emu_operand(cmd[1]) if ft & CF_USE3: self._emu_operand(cmd[2]) if not ft & CF_STOP: ua_add_cref(0, cmd.ea + cmd.size, fl_F) return True def outop(self, op): if op.type == o_reg: out_register(self.reg_names[op.reg]) elif op.type == o_imm: OutValue(op, OOFW_IMM) elif op.type in [o_near, o_mem]: ok = out_name_expr(op, op.addr, BADADDR) if not ok: out_tagon(COLOR_ERROR) OutLong(op.addr, 16) out_tagoff(COLOR_ERROR) QueueMark(Q_noName, self.cmd.ea) elif op.type == o_phrase: out_symbol('[') out_register(self.reg_names[op.reg]) out_symbol(']') else: return False return True def out(self): cmd = self.cmd ft = cmd.get_canon_feature() buf = init_output_buffer(1024) OutMnem(15) if ft & CF_USE1: out_one_operand(0) if ft & CF_USE2: OutChar(',') OutChar(' ') out_one_operand(1) if ft & CF_USE3: OutChar(',') OutChar(' ') out_one_operand(2) term_output_buffer() cvar.gl_comm = 1 MakeLine(buf) def PROCESSOR_ENTRY(): return NDHProcessor()

If you have trouble for dumping vm_opcode, you can find the dump here.

Load the vm into IDA, choose "ndh2k13 VM CPU: ndh2k13", when IDA ask you for the memory organisation tell him :

Now you can start reverse :)

Solution

We have got only 3 syscall for this version of the vm :

The code of the vm start here :

ROM:8317                 MOVW           R0, aPassword
ROM:831C                 CALL           write_msg
ROM:8320                 SUBB           SP, $20
ROM:8324                 MOV            R2, SP
ROM:8328                 MOVB           R1, 0
ROM:832C                 MOVB           R3, $1F
ROM:8330                 MOVB           R0, $3
ROM:8334                 SYSCALL                 ; SYSCALL READ
ROM:8335                 MOV            R0, R2
ROM:8339                 CALL           check_password

And the pseudo - code of the function check_password is :

char *key = 0x8342;
if (strlen(buf_password) == 9)	// "\n" include
	if (buf_password[0] ^ key[0] == 'S')
		if (buf_password[1] ^ key[1] == '[')
			if (buf_password[2] ^ key[2] == 'K')
				if (buf_password[3] ^ key[3] == ')')
					... etc ...
					print("GOOD PASSWORD\n")
					exit
print("BAD PASSWORD\n")
exit

And the good password is :

key = [0x12, 0x21, 0x02, 0x19, 0x25, 0x34, 0x29, 0x11]
res = ['S', '[', 'K', ')', 'R', 'v', 'Z', 'I']
password = ""
for i in xrange(0, 8):
	password += chr(ord(res[i]) ^ key[i])
print password

The flag was 'AzI0wBsX'.

Conclusion

I'm an idiot to reverse an entire vm and not figure out it was the same than last year, but during ctf you want to be the fatest and don't (always) think about old challenges.

Another solution was to count the number of instructions executed by the vm, yeah because when you write crackme, I think you should compute hash, or something like that, because consecutive if statements is just lulz.

Enjoy :

#! /bin/sh

charset="a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
charset="$charset _ - ! $ % ^ \\& \\* + = 0 1 2 3 4 5 6 7 8 9 > < ,"

cat > gdbscript <<EOF
set height 0
define countinstrs
	p "instr"
	c
	countinstrs
end

b *0x0000000000400C7F	# call handler opcode
run < guess

countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
EOF

for c in $charset; do
	guess="$c"
	echo -n "$guess" > guess
	echo "AAAAAAA" >> guess
	echo -n "trying key `cat guess`... "
	gdb ./simple < gdbscript 2>&1 \
		| grep '^\$.*= "instr"$' \
		| tail -1 \
		| cut -d ' ' -f 1 \
		| cut -c 2-
done

First letter :

trying key zAAAAAAA... 245
trying key AAAAAAAA... 252
trying key BAAAAAAA... 245

Edit script for replacing first letter by "A"

trying key AyAAAAAA... 252
trying key AzAAAAAA... 259
trying key AAAAAAAA... 252

And do it for the 8 letters.

18/07/2010

Write up RMLL crackme level 2

C'est parti pour le level suivant du RMLL.

Première approche :

w4kfu@vm-debian:~/RMLL$ ./mars1 
serial : 1234556065906954904560950654
Bad serial :/

On sort gdb pour voir tout ca :

w4kfu@vm-debian:~/RMLL$ gdb ./mars1 
(gdb) disas main
....
0x0804888c <main+9>:	        movl   $0xd6eddb56,0x1e(%esp)
0x08048894 <main+17>:		movl   $0x8086fb8b,0x22(%esp)
0x0804889c <main+25>:		movl   $0x9e0847d1,0x26(%esp)
0x080488a4 <main+33>:		movl   $0x638ce745,0x2a(%esp)
0x080488ac <main+41>:		movb   $0x0,0x2e(%esp)
...
0x080488f2 <main+111>:	call   0x804842c <ptrace@plt>
...
0x080489cd <main+330>:	call   0x804844c <strlen@plt>
0x080489d2 <main+335>:	cmp    $0x10,%eax
...
0x08048a76 <main+499>:	call   0x8048554 <chiffre>
...
0x08048ac5 <main+578>:	call   0x804840c <memcmp@plt>
....

On retrouve les même symptômes que le premier challenge, à la différence du ptrace et du code complètement offusqué. Si on s'amuse à disas la fonction chiffre c'est pas beau à voir.

On va devoir nettoyer tout ça car on peut y voir des trucs qui vont bien nous ennuyer :

0x0804857e <chiffre+42>:   rdtsc  
0x0804859e <chiffre+74>:   (bad)  
0x080485a0 <chiffre+76>:   leave  
0x080485a3 <chiffre+79>:   sub    %ecx,%eax
0x080485a5 <chiffre+81>:   cmp    $0x3000,%eax
0x080485aa <chiffre+86>:   ja     0x80485aa <chiffre+86>

J'ai donc écrit le code suivant pour enlever toutes ces cochonneries :

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

void look_pattern(int fd);
void replace_pattern(int fd, off_t offset, int nb);

int
main(void)
{
      int fd;

      if ( (fd = open("./copy_mars1", O_RDWR) ) == -1 )
          perror("open()");
      look_pattern(fd);
      close(fd);
      return 0;
}

void
look_pattern(int fd)
{
      off_t offset = 0x554;
      off_t offset_end = 0x87b;
      char pattern[7];

      while ( offset < offset_end)
      {
      	   if (read(fd, pattern, 7) <= 0)
                perror("read()");
          if ( pattern[0] == 0x77 )
               replace_pattern(fd, offset, 2);
	   else if ( pattern[0] == 0xc9 )
	       replace_pattern(fd, offset, 1);
	   else if ( pattern[0] == 0xf1 )
	       replace_pattern(fd, offset, 1);
	   else if (!strncmp(pattern, "\xdb\xe6", 2))
	       replace_pattern(fd, offset, 2);
	   else if (!strncmp(pattern, "\x0f\x31", 2))
	       replace_pattern(fd, offset, 2);
	   else if(!memcmp(pattern, (void*)"\x2b\xc1\x3d\x00", 4))
	       replace_pattern(fd, offset, 9);
	   offset++;
	   lseek(fd, offset, SEEK_SET);
      }
}

void
replace_pattern(int fd, off_t offset, int nb)
{
      int i = 0;

      lseek(fd, offset,SEEK_SET);
      for ( i = 0 ; i < nb ; i++) 
          if ( write(fd, "\x90", 1) == -1)
	      perror("write()");
}

Il faut aussi s'occuper du ptrace, voici le code du wrapper de ptrace :

int ptrace(int a, int b, int c, int d)
{
   return 0;
}

Pour le compiler et l'utiliser au sein de gdb :

>gcc -shared my_ptrace.c -o ptrace.so
(gdb) set environment LD_PRELOAD ./ptrace.so

Mais malheureusement, ça n'a pas suffit, ca m'a nettoyé un peu le disas.. Alors j'ai décidé de m'écrire un script gdb pour tracer TOUT ce que fait la fonction chiffre :

w4kfu@vm-debian:~$ cat .gdbinit
define test
   x/i $eip
   ni
   if ( $eip != 0x08048a7b)
   	  test

Ce script affiche le code exécuté par mon processeur puis fait un next instruction. Tout ça jusqu'à arriver à l'eip 0x08048a7b ( quand on sort de la fonction chiffre ). Je pose donc un breakpoint, j'active les logs et je bourrine ma touche "ENTREE".

(gdb) set environment LD_PRELOAD ./ptrace.so
(gdb) b* chiffre
Breakpoint 1 at 0x8048554
(gdb) r
Starting program: /home/w4kfu/RMLL/copy_mars1 
serial : 1234567890123456

Breakpoint 1, 0x08048554 in chiffre ()
(gdb) set logging file mars1_disas
(gdb) set logging on
Copying output to mars1_disas.
(gdb) test
....
BOURRINAGE TOUCHE ENTREE \o/
....
(gdb) set logging off

Bon par contre y'a 500 trucs qui se répètent et des lignes useless, bon bah on sort notre couteau suisse :

sed "/(gdb)/d" mars1_disas > mars1_disas2
cat mars1_disas2 | sort | uniq > real_disas

On arrive au résultat suivant : mars1_disas. On retranscrit le tout en C :

#include <stdio.h>

int
main(void)
{
      unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93";
      unsigned char input[16] = "1234567890123456";
      unsigned int ebp_14;
      unsigned int ebp_c;
      unsigned int eax;
      unsigned int ecx;
      unsigned int edx;
      unsigned char wtf[2];

      ebp_14 = 0;
      while ( ebp_14 <= 0xf )
      {
          ebp_c = 0;
          while ( ebp_c <= ebp_14 )
          {
	      input[ebp_14] = in_fct[ebp_c] ^ input[ebp_14];
	      edx = input[ebp_14] << ( ebp_14 & 0x7 );   
	      wtf[0] = edx;
             eax = ebp_14 & 0x7;
	      ecx = 0x8 - eax;
	      edx = input[ebp_14] >> ecx;
	      wtf[1] = edx;
	      input[ebp_14] = wtf[1] | wtf[0];
	      ebp_c++;
	   }
	   printf("%x\n",input[ebp_14]);
	   ebp_14++;
      }
      return 0;
}

Dans le code ci-dessus, j'ai déclaré les noms de façon assez explicite histoire de pas me tromper dans ce que je faisais. Maitenant qu'on sait comment fonctionne la fonction chiffre, j'ai "bruteforcé" le tout étant donné que l'on a les 16 octets auxquels on doit arriver :

void
bruteforce(void)
{
      unsigned char a;
      unsigned char b;
      unsigned char in_fct[16] = "\x06\x7f\x92\x55\xea\x97\x0e\x4a\x22\x21\x07\xc7\x7f\x65\x9c\x93";
      unsigned char input[17] = {0};
      unsigned char result[16] = "\x56\xdb\xed\xd6\x8b\xfb\x86\x80\xd1\x47\x08\x9e\x45\xe7\x8c\x63";
      unsigned int ebp_14;
      unsigned int ebp_c;
      unsigned int eax;
      unsigned int ecx;
      unsigned int edx;
      unsigned char wtf[2];

      ebp_14 = 0;
      while ( ebp_14 <= 0xf )
      {
      	    a = '\x00';
	    while ( b != result[ebp_14])
	    {
		a += 1;
		b = a;
		ebp_c = 0; 
		while ( ebp_c <= ebp_14 )
		{
		    b = in_fct[ebp_c] ^ b;
		    edx = b << ( ebp_14 & 0x7 );   
		    wtf[0] = edx;
		    eax = ebp_14 & 0x7;
		    ecx = 0x8 - eax;
		    edx = b >> ecx;
		    wtf[1] = edx;
		    b = wtf[1] | wtf[0];
		    ebp_c++;
	         }
	     }
	     input[ebp_14] = a;
	     ebp_14++;
       }
       printf(" input = %s\n", input);
}

On teste tout ça :

w4kfu@vm-debian:~/RMLL$ gcc -o reverse reverse.c
w4kfu@vm-debian:~/RMLL$ ./reverse
input = POGddyrbtjYIoXfv
w4kfu@vm-debian:~/RMLL$ ./mars1 
serial : POGddyrbtjYIoXfv
Good job !
Use your serial as password for the next level !

Voilà c'est fini! Je n'ai pas le level 3 :(, si quelqu'un passe sur le blog et l'a disponible je suis intéressé car une fois le level2 résolu la machine était down.

18/07/2010

Write up RMLL crackme level 1

Ce billet aurait dû sortir il y a une semaine mais le week-end dernier se déroulait le smpCTF auquel j'ai participeéau sein de l'équipe BiG-DadDy ( arrivée 12ème ) ou avec dad nous nous sommes occupés surtout des épreuves p0wnMe, bref maintenant j'ai le temps donc je le consacre à faire ce write up.

Du 6 au 11 Juillet 2010, à Bordeaux se déroulait les Rencontres Mondiales du Logiciel Libre. Je fais actuellement un stage dans la boite XXXX et mon maître de stage se rendait sur place ( je n'ai pas eu la chance de l'accompagner :( ). Bref, dès son premier jour d'arrivée il m'envoie un e-mail avec ce lien.

Quoi des challenges de sécurité !?! Je me précipite sur les crackme.

Première approche :

w4kfu@vm-debian:~/RMLL$ ./mars0 
serial : 34534343423432403245-450-435-043-50435
Nice try ! But ... no ...

Allez hop on sort gdb :

w4kfu@vm-debian:~/RMLL$ gdb ./mars0 
...
(gdb) disas main
....
0x0804855b <main+9>:	        movl   $0xc49c6547,0x1e(%esp)
0x08048563 <main+17>:		movl   $0xee987f5e,0x22(%esp)
0x0804856b <main+25>:		movl   $0x4cced336,0x26(%esp)
0x08048573 <main+33>:		movl   $0xee7c11e4,0x2a(%esp)
.....
0x080485a9 <main+87>:	call   0x80483e4 <strlen@plt>
0x080485ae <main+92>:	cmp    $0x10,%eax
....
0x080485d2 <main+128>:	call   0x80484f4 <chiffre>
....
0x080485ee <main+156>:	call   0x80483b4 <memcmp@plt>
0x080485f3 <main+161>:	test   %eax,%eax
0x080485f5 <main+163>:	jne    0x804860a <main+184>
....

Déjà ici on peut commencer à comprendre un peu le fonctionnement de notre crackme :

Posons un breakpoint sur le memcmp() :

(gdb) b* main+156
Breakpoint 1 at 0x80485ee
(gdb) r
Starting program: /home/w4kfu/RMLL/mars0 
serial : 1234567890123456
Breakpoint 1, 0x080485ee in main ()
(gdb) x/4x $esp
0xbfffecc0:	0xbfffecef	0xbfffecde	0x00000010	0x080483a0
(gdb) x/4x 0xbfffecef
0xbfffecef:	0xb5dd0032	0x85f83d00	0x3ca1842f	0xca5e21a6  <<< Notre SERIAL
(gdb) x/4x 0xbfffecde
0xbfffecde:	0xc49c6547	0xee987f5e	0x4cced336	0xee7c11e4  <<< Ce a quoi il est comparee

Regardons maitenant du côté de la fonction chiffre :

(gdb) disas chiffre
0x080484f5 <chiffre+1>:	mov    %esp,%ebp
0x080484f7 <chiffre+3>:	sub    $0x20,%esp
0x080484fa <chiffre+6>:	movl   $0x82e83303,-0x15(%ebp)
0x08048501 <chiffre+13>:       movl   $0xbac50639,-0x11(%ebp)
0x08048508 <chiffre+20>:       movl   $0x19abd6e,-0xd(%ebp)
0x0804850f <chiffre+27>:       movl   $0x8f1d6099,-0x9(%ebp)
0x08048516 <chiffre+34>:       movb   $0x0,-0x5(%ebp)
0x0804851a <chiffre+38>:       movl   $0x0,-0x4(%ebp)
0x08048521 <chiffre+45>:       jmp    0x804854a <chiffre+86>
0x08048523 <chiffre+47>:       mov    -0x4(%ebp),%eax
0x08048526 <chiffre+50>:       add    0x8(%ebp),%eax
0x08048529 <chiffre+53>:       mov    -0x4(%ebp),%edx
0x0804852c <chiffre+56>:       add    0x8(%ebp),%edx
0x0804852f <chiffre+59>:       movzbl (%edx),%ecx
0x08048532 <chiffre+62>:       mov    -0x4(%ebp),%edx
0x08048535 <chiffre+65>:       lea    (%ecx,%edx,1),%edx
0x08048538 <chiffre+68>:       mov    %edx,%ecx
0x0804853a <chiffre+70>:       mov    -0x4(%ebp),%edx
0x0804853d <chiffre+73>:       movzbl -0x15(%ebp,%edx,1),%edx
0x08048542 <chiffre+78>:       xor    %ecx,%edx
0x08048544 <chiffre+80>:       mov    %dl,(%eax)
0x08048546 <chiffre+82>:       addl   $0x1,-0x4(%ebp)
0x0804854a <chiffre+86>:       cmpl   $0xf,-0x4(%ebp)
0x0804854e <chiffre+90>:       jle    0x8048523 <chiffre+47>
0x08048550 <chiffre+92>:       leave  
0x08048551 <chiffre+93>:       ret    

Le disas ci-dessus peut être traduit de la sorte en C :

#include <stdio.h>

char *chiffre(char *cherche, char *sec);

int 
main(void)
{
      int i;
      char *mot_chercher = "1234567890123456";
      char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f";
      char *mot_crypt;

      mot_crypt = chiffre(mot_chercher,second_word);
      for ( i = 0 ; i < 16 ; i++)
      	       printf("%x ",mot_crypt[i]);
      return 0;
}

char *
chiffre(char *cherche, char *sec)
{
      int i = 0;
      static char found[16];

      for ( i = 0; i < 16; i++)
      	       found[i] = (cherche[i] + i)^ sec[i];
      return found;
}

On teste pour voir si on retrouve bien le même résultat :

w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
32 0 dd b5 0 3d f8 85 2f 84 a1 3c a6 21 5e ca

Notre code est correct, bien maintenant il faut retrouver le serial valide, ce n'est pas très compliqué car nous savons à quoi il doit être égal. Nous n'avons qu'à faire l'opération inverse :

#include <stdio.h>

char *chiffre(char *cherche, char *sec);

int
main(void)
{
      char *mot_chercher = "\x47\x65\x9c\xc4\x5e\x7f\x98\xee\x36\xd3\xce\x4c\xe4\x11\x7c\xee"; 
      char *second_word = "\x03\x33\xe8\x82\x39\x06\xc5\xba\x6e\xbd\x9a\x01\x99\x60\x1d\x8f";

      printf("Le serial est  %s\n", chiffre(mot_chercher,second_word));
}

char *
chiffre(char *cherche, char *sec)
{
      int i = 0;
      static char found[16];

      for ( i = 0; i < 16; i++)
      	       found[i] = (cherche[i] ^ sec[i])-i;
      return found;
}

On exécute tout ça :

w4kfu@vm-debian:~/RMLL/mars$ gcc -o mars0 mars0.c
w4kfu@vm-debian:~/RMLL/mars$ ./mars0
Le serial est  DUrCctWMPeJBqdSR

Et on teste notre serial :

w4kfu@vm-debian:~/RMLL$ ./mars0
serial : DUrCctWMPeJBqdSR
Good job !
Use your serial as password for the next level !

Pages : 1