Outils pour utilisateurs

Outils du site


writeup:trust_the_future:reverse3

Crackme 500

Unpack

On a un binaire 64bits strippé

$ file crackme3
crackme3: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

En ouvrant le binaire avec IDA on se rend compte qu'il est probablement packé. Les commandes readelf et strings permettent d'en être sur.

$ readelf -a --wide crackme3
En-tête ELF:
  Magique:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00 
  Classe:                            ELF64
  Données:                          complément à 2, système à octets de poids faible d'abord (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  Version ABI:                       0
  Type:                              EXEC (fichier exécutable)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Adresse du point d'entrée:         0x401df0
  Début des en-têtes de programme:   64 (octets dans le fichier)
  Début des en-têtes de section:          0 (octets dans le fichier)
  Fanions:                           0x0
  Taille de cet en-tête:             64 (bytes)
  Taille de l'en-tête du programme:  56 (bytes)
  Nombre d'en-tête du programme:     2
  Taille des en-têtes de section:    64 (bytes)
  Nombre d'en-têtes de section:      0
  Table d'indexes des chaînes d'en-tête de section: 0

Il n'y a pas de section dans ce fichier.

Il n'y a pas de section à grouper dans ce fichier.

En-têtes de programme:
  Type           Décalage Adr. vir.          Adr.phys.          T.Fich.  T.Mém.   Fan Alignement
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0025c4 0x0025c4 R E 0x200000
  LOAD           0x0031b8 0x00000000006031b8 0x00000000006031b8 0x000000 0x000000 RW  0x200000

Il n'y a pas de section dynamique dans ce fichier.

Il n'y a pas de réadressage dans ce fichier.

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Aucune information de version repérée dans ce fichier.
$ strings crackme3
...
F4B!
/lib64
nux-x86-
so.2
!C$2!
H3L 
exit
strn
puts
calloc
PROT_EXEC|PROT_WRITE failed.
$Info: This file is packed with the F4B executable packer http://F4B.sf.net $
$Id: F4B 3.91 Copyright (C) 1996-2013 the F4B Team. All Rights Reserved. $
F4B!
F4B!

Le copyright laisse penser que c'est un packer connu. En cherchant un peu sur google, on se rend compte que la signature ressemble beaucoup à celle d'UPX sauf que la chaine UPX est remplacée par F4B. Essayons de modifier tout ça.

[tlk:~/CTF/trusthefuture/CRACKM3]$ sed s/F4B/UPX/g crackme3 > crackme3-realupx
[tlk:~/CTF/trusthefuture/CRACKM3]$ upx -d crackme3-realupx                    
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     16300 <-     10372   63.63%  linux/ElfAMD   crackme3-realupx

Unpacked 1 file.

Hop la ! C'était juste un binaire packé avec UPX dont les chaines UPX ont été remplacées par F4B !

Machine virtuelle

En regardant globalement ce que fait le programme unpacké, on remarque une fonction (en 0x400C28) comportant un grand nombre de blocks et de branchements conditionnels, le tout situé dans une boucle.

On reconnait facilement le schéma d'une machine virtuelle. Le premier block de la boucle permet de récupérer l'opcode courant, puis des comparaisons successives permettent d'arriver au block exécutant l'instruction (une sorte de fetch, decode, execute).

Initialisation

Avant de démarrer l’exécution de la VM le programme fait un certain nombre d'initialisation que nous allons regarder de plus prêt.

.text:0000000000401319                 mov     eax, 0
.text:000000000040131E                 call    alloc_text
.text:0000000000401323                 mov     cs:sections, rax
.text:000000000040132A                 mov     eax, 0
.text:000000000040132F                 call    alloc_ram
.text:0000000000401334                 mov     rax, [rbp+argv]
.text:0000000000401338                 add     rax, 8
.text:000000000040133C                 mov     rax, [rax]
.text:000000000040133F                 mov     edx, 10h        ; n
.text:0000000000401344                 mov     rsi, rax        ; src
.text:0000000000401347                 mov     edi, offset user_input 
.text:000000000040134C                 call    _strncpy
.text:0000000000401351                 mov     eax, 0
.text:0000000000401356                 call    init_pc
.text:000000000040135B                 mov     eax, 0
.text:0000000000401360                 call    debut_vm

On remarque donc deux appels à des fonctions particulières. La première initialise en fait une liste doublement chainée et la seconde ajoute un élément à la fin de cette liste. Parce que j'ai déjà regardé à quoi sert cette liste, je sais en fait que c'est la liste des zones mémoire de la machine virtuelle. Chaque élément décrit une zone mémoire de la machine, ils contiennent tous : l'adresse utilisée pour adresser dans la VM, la taille de la zone et l'adresse réelle dans le programme.

ROM RAM
0 1
virtual_address 0x0 0x2000
size 0x1000 0x1000
real_address @0x602080 @0x602160
next @RAM @ROM
prev @RAM @ROM

On remarque aussi que le premier argument du programme est copié en 0x6022E0 soit à l'offset 0x180 de la RAM de la VM. Le pointeur d'instruction est initialisé à 0 puis la VM est démarrée.

Execution

Chaque cycle de la VM commence par appeler une fonction qui récupère l'opcode situé à l'adresse ROM+PC puis incrémente PC. Cette valeur est ensuite comparée aux valeurs se trouvant aux offsets 0x6013170, 0x603171, … 0x60317B afin de déterminer l'opération à effectuer. Il est important de noter que les valeurs des opcodes peuvent changer à tout moment en modifiant la valeur stockée à ces offsets et c'est exactement ce qui va se passer ! :-D

Je ne rentre pas dans les détails de comment trouver la correspondance entre opcode et opération effectuée car c'est assez fastidieux … Mais je vous encourage à essayer :-) Voici un tableau récapitulatif des opcodes utilisés dans ce challenge :

op semantic example opcode opt1 opt2 opt3 opt4
mov reg1, reg2 reg1 ← reg2 mov r3, r4 → \x00\x03\x00\x04 0x00 reg1 0x00 reg2
mov byte reg1, value reg1 ← value mov r2, 0xFF → \x00\x02\x01\x00\xFF 0x00 reg1 0x01 0x00 value
mov word reg1, value reg1 ← value mov r2, 0xFFFF → \x00\x02\x01\x01\xFF\xFF 0x00 reg1 0x01 0x01 value
xor reg1, reg2 reg1 ← reg1 xor reg2 xor r3, r4 → \x01\x03\x00\x04 0x01 reg1 0x00 reg2
xor byte reg1, value reg1 ← reg1 xor value xor r2, 0xFF → \x01\x02\x01\x00\xFF 0x01 reg1 0x01 0x00 value
xor word reg1, value reg1 ← reg1 xor value xor r2, 0xFFFF → \x01\x02\x01\x01\xFF\xFF 0x01 reg1 0x01 0x01 value
add reg1, reg2 reg1 ← reg1 + reg2 add r3, r4 → \x02\x03\x00\x04 0x02 reg1 0x00 reg2
add byte reg1, value reg1 ← reg1 + value add r2, 0xFF → \x02\x02\x01\x00\xFF 0x02 reg1 0x01 0x00 value
add word reg1, value reg1 ← reg1 + value add r2, 0xFFFF → \x02\x02\x01\x01\xFF\xFF 0x02 reg1 0x01 0x01 value
sub reg1, reg2 reg1 ← reg1 - reg2 sub r3, r4 → \x03\x03\x00\x04 0x03 reg1 0x00 reg2
sub byte reg1, value reg1 ← reg1 - value sub r2, 0xFF → \x03\x02\x01\x00\xFF 0x03 reg1 0x01 0x00 value
sub word reg1, value reg1 ← reg1 - value sub r2, 0xFFFF → \x03\x02\x01\x01\xFF\xFF 0x03 reg1 0x01 0x01 value
not reg1 reg1 ← not reg1 not r1 → \x06\x01 0x06 reg1
mov byte reg1, [addr]  reg1 ← byte @addr mov r1, [0x1337] → \x07\x00\x01\x37\x13 0x07 0x00 reg1 addr1 addr2
mov word reg1, [addr] reg1 ← word @addr mov r1, [0x1337] → \x07\x01\x01\x37\x13 0x07 0x01 reg1 addr1 addr2
mov byte [addr], reg1 byte @addr ← reg1 mov [0x1337], r1 → \x08\x00\x01\x37\x13 0x08 0x00 reg1 addr1 addr2
mov word [addr], reg1 word @addr ← reg1 mov [0x1337], r1 → \x08\x01\x01\x37\x13 0x08 0x01 reg1 addr1 addr2
jmp -value pc ← pc - value jmp -0x1337 → \x09\x00\x01\x37\x13 0x09 0x00 0x01 value1 value2
jmp +value pc ← pc + value jmp +0x1337 → \x09\x00\x00\x37\x13 0x09 0x00 0x00 value1 value2
jnz -value pc ← if flag!=0 then pc-value jnz -0x1337 → \x09\x01\x01\x37\x13 0x09 0x01 0x01 value1 value2
jnz +value pc ← if flag!=0 then pc+value jnz +0x1337 → \x09\x01\x00\x37\x13 0x09 0x01 0x00 value1 value2
jz -value pc ← if flag=0 then pc-value jnz -0x1337 → \x09\x02\x01\x37\x13 0x09 0x02 0x01 value1 value2
jz +value pc ← if flag=0 then pc+value jnz +0x1337 → \x09\x02\x00\x37\x13 0x09 0x02 0x00 value1 value2
halt stop la vm halt → \x0B 0x0B

Il y a un dernier opcode qui est particulièrement intéressant, j'ai nommé special_operation ! En fait, cette fonction est appelée trois fois pendant l'exécution de la VM avec trois paramètres différents :

  • 0x3 : fait un XOR sur la RAM avec 0xef
  • 0x2 : appel à ptrace puis utilise la valeur de retour pour modifier les opcodes → opcode[i] ^ 0x50 ^ retour_ptrace
  • 0x1 : modifie encore les opcodes → décalage circulaire

Avec l'aide de ce jolie tableau, il est possible de coder la VM facilement en python. Mon code utilise deux fichiers représentants la ROM et la RAM. Ils sont créés à l'aide de la commande dumpmem sur gdb (peda ftw!) juste au avant de lancer la VM (notre input se trouve donc dans la RAM).

from struct import unpack, pack
 
class VM():
 
	def __init__(self):
		self.pc = 0
		self.reg = {}
		self.zero_flag = 0
		self.RAM = open('ram', 'r+b')
		self.ROM = open('rom', 'r')
		self.OP_CODES = {
			0x0: self.mov_val,
			0x1: self.xor,
			0x2: self.add,
			0x3: self.sub,
			0x6: self.not_reg,
			0x7: self.mov_reg,
			0x8: self.mov_mem,
			0x9: self.jump,
			0xA: self.special_operation,
			0xB: self.halt
		}
 
	def _load_from_section_by_addr(self, addr, size=0):
		if addr >= 0x2000:
			addr &= 0xFFF
			self.RAM.seek(addr)
			if size == 0:
				return unpack('<B', self.RAM.read(1))[0]
			elif size == 1:
				return unpack('<H', self.RAM.read(2))[0]
		else:
			self.ROM.seek(addr)
			if size == 0:
				return unpack('<B', self.ROM.read(1))[0]
			elif size == 1:
				return unpack('<H', self.ROM.read(2))[0]
 
	def _write_to_section_by_addr(self, addr, size, data):
		if addr >= 0x2000:
			addr &= 0xfff
			self.RAM.seek(addr)
			if size == 0:
				self.RAM.write(pack('<B', data))
			elif size == 1:
				self.RAM.write(pack('<H', data))
			return
		raise Exception("write bad addr")
 
	def _get_from_rom(self, size=0):
		ret = self._load_from_section_by_addr(self.pc, size)
		if size == 0:
			self.pc += 1
		else:
			self.pc += 2
		return ret
 
	def _update_zero_flag(self, reg):
		if self.reg[reg] == 0:
			self.zero_flag = 1
		else:
			self.zero_flag = 0
 
	def _print_op(self, string):
		print "0x%04x : %s" % (self.pc, string)
 
	def mov_val(self): # 0x00
		dest = self._get_from_rom()
		src_type = self._get_from_rom()
		if src_type == 0: # by register
			src = self._get_from_rom()
			self.reg[dest] = self.reg[src]
			self._print_op("mov r%d, r%d ; 0x%x" % (dest, src, self.reg[src]))
		elif src_type == 1: # direct value
			size = self._get_from_rom()
			value = self._get_from_rom(size=size)
			self.reg[dest] = value
			self._print_op("mov %s r%d, 0x%x" % ("byte" if size == 0 else "word", dest, value))
 
	def xor(self): # 0x01
		dst = self._get_from_rom()
		src_type = self._get_from_rom()
		if src_type == 0: # register
			src = self._get_from_rom()
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] ^ self.reg[src]) & 0xffff
			self._print_op("xor r%d, r%d ; 0x%x ^ 0x%x = 0x%x" % (dst, src, before, self.reg[src], self.reg[dst]))
		elif src_type == 1: # value
			size = self._get_from_rom()
			value = self._get_from_rom(size=size)
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] ^ value) & 0xffff
			self._print_op("xor r%d, 0x%X ; 0x%x ^ 0x%x = 0x%x" % (dst, value, before, value, self.reg[dst]))
 
 
	def add(self): # 0x02
		dst = self._get_from_rom()
		src_type = self._get_from_rom()
		if src_type == 0: # register
			src = self._get_from_rom()
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] + self.reg[src]) & 0xffff
			self._print_op("add r%d, r%d ; 0x%x + 0x%x = 0x%x" % (dst, src, before, self.reg[src], self.reg[dst]))
		elif src_type == 1: # value
			size = self._get_from_rom()
			value = self._get_from_rom(size=size)
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] + value) & 0xffff
			self._print_op("add r%d, 0x%X ; 0x%x + 0x%x = 0x%x" % (dst, value, before, value, self.reg[dst]))
 
	def sub(self): # 0x03
		dst = self._get_from_rom()
		src_type = self._get_from_rom()
		if src_type == 0: # register
			src = self._get_from_rom()
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] - self.reg[src]) & 0xffff
			self._print_op("sub r%d, r%d ; 0x%x - 0x%x = 0x%x" % (dst, src, before, self.reg[src], self.reg[dst]))
		elif src_type == 1: # value
			size = self._get_from_rom()
			value = self._get_from_rom(size=size)
			before = self.reg[dst]
			self.reg[dst] = (self.reg[dst] - value) & 0xffff
			self._print_op("sub r%d, 0x%X ; 0x%x - 0x%x = 0x%x" % (dst, value, before, value, self.reg[dst]))
		self._update_zero_flag(dst)
 
	def not_reg(self): # 0x6
		register = self._get_from_rom()
		before = self.reg[register]
		self.reg[register] = (~self.reg[register]) & 0xffff
		self._print_op("not r%d ; 0x%x - 0x%x" % (register, before, self.reg[register]))
		self._update_zero_flag(0)
 
	def mov_reg(self): # 0x07
		size_load = self._get_from_rom()
		register_load = self._get_from_rom()
		addr_load = self._get_from_rom(size=1)
		data = self._load_from_section_by_addr(addr_load, size_load)
		self.reg[register_load] = data
		self._update_zero_flag(register_load)
		aff = pack('<H', data) if size_load == 1 else pack('<B', data)
		typ = "byte" if size_load == 0 else "word"
		self._print_op("mov %s r%d, [0x%x] ; %s" % (typ, register_load, addr_load, repr(aff)))
 
	def mov_mem(self): # 0x08
		size_write = self._get_from_rom()
		register_load = self._get_from_rom()
		addr = self._get_from_rom(size=1)
		data = self.reg[register_load]
		self._write_to_section_by_addr(addr, size_write, data)
		self._print_op("mov [0x%x], r%d ; 0x%x" % (addr, register_load, data))
 
	def jump(self): # 0x09
		typ = self._get_from_rom()
		signe = self._get_from_rom()
		value = self._get_from_rom(size=1)
		if typ == 0: # jmp
			self._print_op("jmp pc%s0x%x" % ("-" if signe != 0 else "+", value))
			if signe == 0:
				self.pc += value
			else:
				self.pc -= value
		elif typ == 1: # jnz
			self._print_op("jnz 0x%x ; flag = 0x%x - %s" % (value, self.zero_flag, "taken" if self.zero_flag != 0 else "not taken"))
			if self.zero_flag != 0:
				if signe == 0:
					self.pc += value
				else:
					self.pc -= value
		elif typ == 2: # jz
			self._print_op("jz 0x%x ; flag = 0x%x - %s" % (value, self.zero_flag, "taken" if self.zero_flag == 0 else "not taken"))
			if self.zero_flag == 0:
				if signe == 0:
					self.pc += value
				else:
					self.pc -= value
 
	def special_operation(self): # 0xA
		what = self._get_from_rom()
		if what == 3:
			self._print_op("special_operation 0x3")
			self.RAM.seek(0)
			for i in range(0x1000):
				val = unpack('<B', self.RAM.read(1))[0]
				self.RAM.seek(-1, 1)
				self.RAM.write(pack('<B', (val ^ self.reg[0]) & 0xff))
			return
		if what == 1:
			self._print_op("special_operation 0x1")
			ops = self.OP_CODES
			self.OP_CODES = {}
			for k, v in ops.items():
				self.OP_CODES[k ^ 0x50] = v
			return
		if what == 2:
			self._print_op("special_operation 0x2")
			ops = self.OP_CODES
			self.OP_CODES = {}
			for k, v in ops.items():
				self.OP_CODES[k + 2 if k+2 <= 0x5b else k - 0xa] = v
			return
 
	def halt(self): # 0xB
		self._print_op("halt")
		self.running = False
 
	def start(self):
		self.running = True
		while self.running:
			opcode = self._get_from_rom()
			self.OP_CODES[opcode]()
 
vm = VM()
vm.start()

Validation du retour

Voici la trace d'exécution que l'on obtient lorsqu'on lance la VM :

0x0005 : mov word r0, [0x2180] ; 'AB'
0x000a : mov word r1, [0x2182] ; 'CD'
0x0010 : mov word r2, 0x1337
0x0014 : add r0, r2 ; 0x4241 + 0x1337 = 0x5578
0x0018 : sub r1, r2 ; 0x4443 - 0x1337 = 0x310c
0x001d : mov [0x2180], r0 ; 0x5578
0x0022 : mov [0x2182], r1 ; 0x310c
0x0028 : mov word r0, 0xbeef
0x002a : special_operation 0x3
0x002f : mov word r3, [0x2184] ; '\xaa\xa9'
0x0034 : mov word r4, [0x2186] ; '\xa8\xa7'
0x0036 : not r3 ; 0xa9aa - 0x5655
0x003c : xor r4, 0xDE4D ; 0xa7a8 ^ 0xde4d = 0x79e5
0x0040 : add r3, r1 ; 0x5655 + 0x310c = 0x8761
0x0044 : sub r4, r1 ; 0x79e5 - 0x310c = 0x48d9
0x0049 : mov [0x2186], r3 ; 0x8761
0x004e : mov [0x2184], r4 ; 0x48d9
0x0050 : special_operation 0x1
0x0055 : mov word r1, [0x2188] ; '\xa6\xa5'
0x005a : mov word r2, [0x218a] ; '\xa4\xa3'
0x0060 : mov word r0, 0x4
0x0064 : mov r3, r1 ; 0xa5a6
0x0068 : add r3, r2 ; 0xa5a6 + 0xa3a4 = 0x494a
0x006e : xor r3, 0xCAFE ; 0x494a ^ 0xcafe = 0x83b4
0x0074 : add r2, 0xBAD ; 0xa3a4 + 0xbad = 0xaf51
0x0078 : xor r2, r4 ; 0xaf51 ^ 0x48d9 = 0xe788
0x007c : mov r1, r3 ; 0x83b4
0x0082 : sub r0, 0x1 ; 0x4 - 0x1 = 0x3
0x0087 : jz 0x27 ; flag = 0x0 - taken
0x0064 : mov r3, r1 ; 0x83b4
0x0068 : add r3, r2 ; 0x83b4 + 0xe788 = 0x6b3c
0x006e : xor r3, 0xCAFE ; 0x6b3c ^ 0xcafe = 0xa1c2
0x0074 : add r2, 0xBAD ; 0xe788 + 0xbad = 0xf335
0x0078 : xor r2, r4 ; 0xf335 ^ 0x48d9 = 0xbbec
0x007c : mov r1, r3 ; 0xa1c2
0x0082 : sub r0, 0x1 ; 0x3 - 0x1 = 0x2
0x0087 : jz 0x27 ; flag = 0x0 - taken
0x0064 : mov r3, r1 ; 0xa1c2
0x0068 : add r3, r2 ; 0xa1c2 + 0xbbec = 0x5dae
0x006e : xor r3, 0xCAFE ; 0x5dae ^ 0xcafe = 0x9750
0x0074 : add r2, 0xBAD ; 0xbbec + 0xbad = 0xc799
0x0078 : xor r2, r4 ; 0xc799 ^ 0x48d9 = 0x8f40
0x007c : mov r1, r3 ; 0x9750
0x0082 : sub r0, 0x1 ; 0x2 - 0x1 = 0x1
0x0087 : jz 0x27 ; flag = 0x0 - taken
0x0064 : mov r3, r1 ; 0x9750
0x0068 : add r3, r2 ; 0x9750 + 0x8f40 = 0x2690
0x006e : xor r3, 0xCAFE ; 0x2690 ^ 0xcafe = 0xec6e
0x0074 : add r2, 0xBAD ; 0x8f40 + 0xbad = 0x9aed
0x0078 : xor r2, r4 ; 0x9aed ^ 0x48d9 = 0xd234
0x007c : mov r1, r3 ; 0xec6e
0x0082 : sub r0, 0x1 ; 0x1 - 0x1 = 0x0
0x0087 : jz 0x27 ; flag = 0x1 - not taken
0x008c : mov [0x2188], r2 ; 0xd234
0x0091 : mov [0x218a], r1 ; 0xec6e
0x0093 : special_operation 0x2
0x0098 : mov word r6, [0x218c] ; '\xa2\xa1'
0x009d : mov word r5, [0x218e] ; '\xa0\xbf'
0x00a2 : mov word r1, [0x2180] ; '\x97\xba'
0x00a7 : mov word r2, [0x2182] ; '\xe3\xde'
0x00ab : xor r5, r1 ; 0xbfa0 ^ 0xba97 = 0x537
0x00af : xor r5, r2 ; 0x537 ^ 0xdee3 = 0xdbd4
0x00b3 : add r6, r1 ; 0xa1a2 + 0xba97 = 0x5c39
0x00b7 : sub r6, r5 ; 0x5c39 - 0xdbd4 = 0x8065
0x00bc : mov [0x218e], r5 ; 0xdbd4
0x00c1 : mov [0x218c], r6 ; 0x8065
0x00c6 : jmp pc+0x15
0x00dc : halt

On peut donc remonter facilement à la valeur à entrer pour avoir la sortie souhaitée (notre input se trouve à l'adresse virtuelle de la vm 0x2180). Lorsque la VM se termine, le programme commence par faire un xor entre ce qui se trouve à l'addresse 0x2180 (notre input modifié par la VM) et 0x2280 (une constante). Le retour de ce xor est ensuite comparé avec la valeur se situant en 0x2FF0. En posant des breakpoints on retrouve facilement les valeurs aux adresses 0x2280 et 0x2FF0, ainsi on retrouve aussi ce qui doit se trouver à l'adresse 0x2180 lorsque la machine virtuelle s'arrête.

from struct import unpack, pack
 
def xor_str(ch,k):
	ret = ""
	for i in range(len(ch)):
		ret += chr(ord(ch[i]) ^ ord(k[i%len(k)]))
	return ret
 
FIN_NEED = '\xf81W$\xe4@\x99\xfd`$\xba\xd9\xfa\xed\xc3G' 
first_key = "\x4f\x5d\x98\xf0\x3f\x73\xf6\x45\x40\xbe\xe7\x32\x50\x5c\x75\x30"
 
fin_vm = xor_str(FIN_NEED, first_key)
print repr(fin_vm)
 
base = 0x2180
values = []
for i in range(0,16,2):
	print "0x%x = 0x%x" % (base+i, unpack('<H', fin_vm[i:i+2])[0]) 
	values.append(unpack('<H', fin_vm[i:i+2])[0])
$ python xor.py 
'\xb7l\xcf\xd4\xdb3o\xb8 \x9a]\xeb\xaa\xb1\xb6w'
0x2180 = 0x6cb7
0x2182 = 0xd4cf
0x2184 = 0x33db
0x2186 = 0xb86f
0x2188 = 0x9a20
0x218a = 0xeb5d
0x218c = 0xb1aa
0x218e = 0x77b6

Flag

Commençons par les 4 premiers caractères. On doit avoir en sortie : 0x6cb7 et 0xd4cf. En n'oubliant pas que la RAM a été xorée avec 0xEF par la special_operation 0x3 et avec ce morceaux d'assembleur issu de la VM on retrouve facilement les 4 premiers caractères :

0x0005 : mov word r0, [0x2180] ; 'AB'
0x000a : mov word r1, [0x2182] ; 'CD'
0x0010 : mov word r2, 0x1337
0x0014 : add r0, r2 ; 0x4241 + 0x1337 = 0x5578
0x0018 : sub r1, r2 ; 0x4443 - 0x1337 = 0x310c
0x001d : mov [0x2180], r0 ; 0x5578
0x0022 : mov [0x2182], r1 ; 0x310c
password.append((values[0] ^ 0xefef) - 0x1337)
password.append((values[1] ^ 0xefef) + 0x1337)

De la même façon avec tous les caractères, on obtient :

from struct import unpack, pack
 
def xor_str(ch,k):
	ret = ""
	for i in range(len(ch)):
		ret += chr(ord(ch[i]) ^ ord(k[i%len(k)]))
	return ret
 
FIN_NEED = "f8315724e44099fd6024bad9faedc347".decode("hex")
first_key = "4f5d98f03f73f64540bee732505c7530".decode("hex")
 
fin_vm = xor_str(FIN_NEED, first_key)
print repr(fin_vm)
 
base = 0x2180
values = []
for i in range(0,16,2):
	print "0x%x = 0x%x" % (base+i, unpack('<H', fin_vm[i:i+2])[0]) 
	values.append(unpack('<H', fin_vm[i:i+2])[0])
 
password = []
password.append((values[0] ^ 0xefef) - 0x1337)
password.append((values[1] ^ 0xefef) + 0x1337)
password.append(((~(values[3] - (values[1] ^ 0xefef))) & 0xFFFF) ^ 0xefef)
password.append(((values[2] + (values[1] ^ 0xefef)) ^0xDE4D) ^ 0xefef)
 
r1 = values[5]
r2 = values[4]
r4 = values[2]
for i in range(4):
	r3 = r1
	r2 ^= r4
	r2 &= 0xFFFF
	r2 -= 0xBAD
	r2 &= 0xFFFF
	r3 ^= 0xCAFE
	r3 &= 0xFFFF
	r3 -= r2
	r3 &= 0xFFFF
	r1 = r3
password.append(r1 ^ 0xefef)
password.append(r2 ^ 0xefef)
 
password.append((values[6] + values[7] - values[0]) ^ 0xefef)
password.append(values[7] ^ values[1] ^ values[0] ^ 0xefef)
 
ret = ""
for i in password:
	ret += pack('<H', i)
print repr(ret)
$ python ponce.py
'\xb7l\xcf\xd4\xdb3o\xb8 \x9a]\xeb\xaa\xb1\xb6w'
0x2180 = 0x6cb7
0x2182 = 0xd4cf
0x2184 = 0x33db
0x2186 = 0xb86f
0x2188 = 0x9a20
0x218a = 0xeb5d
0x218c = 0xb1aa
0x218e = 0x77b6
'!pWN_mY_vm_FFS! '

FIOU ! Le flag est donc !pWN_mY_vm_FFS! !

writeup/trust_the_future/reverse3.txt · Dernière modification: 2014/11/30 10:46 par tlk