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 !
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).
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 | |
---|---|---|
N° | 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.
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 !
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 :
0xef
opcode[i] ^ 0x50 ^ retour_ptrace
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()
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
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!
!