IFSF CTF - Write-up challenge 10
By awe on Monday, February 13 2012, 22:59 :: IFSF Prequals :: Permalink
A l'occasion du IFSF CTF j'ai participé aux qualifs avec la team de Zenk-Security, qui a fini 10ème. http://ctf.forbiddenbits.net/?scoreboard.
Je n'ai pas eu trop le temps de participer mais le peu que j'ai fait a été fun (je n'ai pas eu affaire à du guess).
Je présente donc ci-dessous ma solution pour le challenge 10, un binaire DOS, 16 bits. Le binaire: 10.exe
Il s'exécute sans souci sur un Windows XP 32 bits.
Pour le désassembler, IDA bug, et les autres habituels tels que Olly ne prennent pas le 16-bit.
On utilise « Turbo Debugger for DOS », que l'on retrouve ici:
http://www.woodmann.com/collaborative/tools/images/Bin_Turbo_Debugger_2009-6-17_13.16_tdbg55.zip
Le binaire n'a aucun symboles, pas d'appels vers des libs Win etc, juste des interruptions.
On va donc chercher à quel moment il lit les données, pour commencer.
On lit http://en.wikipedia.org/wiki/BIOS_interrupt_call, on se rend compte qu'il nous faut une interruption 16h, qui est celle communiquant avec le clavier. On s'attend à avoir AH set à 0 avant l'interruption.
On trouve alors ce que l'on cherche, en 0x025D:
xor ax, ax int 16 ret
On va donc break sur le ret, en 0x0261, pour revenir dans la fonction qui a demandé à lire le char, pour étudier celle-ci.
Après un run, avoir entré un char (on n'entre que des 'a'), et step, on retombe juste en dessous, en 0x026F:
0x026F: mov si, [0114] mov bl, 09 +> call [0104] ; call 0x25D / read char | test al, al | je 028B ----+ | cmp al, 0D | | je 028B ---+ | rol al, 03 | | xor al, 19 | | mov [si], al | | inc si | | ... | | test bl, bl | | je 028B ----+ +- jmp 026B | ret <---+
Cette fonction lit 9 char, sauf si elle reçoit un 0x0 ou 0xD ( '\r' ).
Cela nous donne une fonction similaire à la suivante en C:
void get_input(void) { char s[10] = 0; // &s = 0x138 ( si ) char c; int bl = 9; int i = 0; while (1) { c = getchar(); // al = c if (c == 0x00 || c == '\r') return s; if (bl-- == 0) return s; asm volatile("rol $0x03, %%al\r\n xor $0x19, %%al\r\n" : "=a"(c) : "a"(c) : "ax"); // Probablement n'importe quoi niveau input / output / clobber :D s[i++] = c; // mov [si], al ; inc si } }
On retient surtout que notre chaîne est stockée « cryptée » en mémoire, avec un rol / xor appliqué sur le char reçu.
Entrer 'a' écrit 0x12 en mémoire.
On break sur le ret, en 0x28b, puis on step pour arriver sur:
call [010A] ; Affiche "Authorization code :" call [0106] ; Lit le code call [010E] ; ? Check code ? ret
On suit donc l'exécution, on tombe sur:
mov bx, [0x114] mov si, [0x116] mov cx, 0x9 xor dx, dx xor ax, ax mov al, [bx] xor al, [si] test al, al jz 0x145
Cette partie compare 2 chaînes, et on retrouve notre chaîne « cryptée » 0x12 * 9.

Elle est comparée à celle pointée par si (0148): 0xA0 0x8B 0x98 0x7B 0x63 0x90 0x98 0x90 0x63
On sort python pour inverser le xor / rol et:
>>> def ror(byte, count): ... while count > 0: ... byte = (byte >> 1 | byte << 7) & 0xFF ... count -= 1 ... return byte ... >>> l = [0xA0, 0x8B, 0x98, 0x7B, 0x63, 0x90, 0x98, 0x90, 0x63] >>> ''.join(chr(ror(0x19 ^ i, 3)) for i in l) '7R0LO101O'
On a notre flag: 7R0LO101O.
