Allez on y va. Voici un petit guide pour mettre le pied à l’étrier et donner sa chance à RadaRE2. C’est affolant de voir qu’il soit si avancé sans avoir plus de doc/tutos que cela. A se demander si ce n’est pas juste utilisé par ses développeurs. Tentons d’y remédier.
On va voir comment reverser avec RadaRE2 un tout petit Chall dont, en plus, on a les sources. On part sur du x64 ELF. Le chall est le suivant :
$ cat crackkids.c #include <stdio.h> #include <string.h> int main () { char password[]="pass!"; char input[16+1]; printf("Chall easy for Radare dissection\n"); scanf("%16s", &input); if (strcmp(password,input)==0) { printf("You Win\n"); } else { printf("You Failed\n"); } return(0); }
Compilons ce petit chall et strippons le;
$ gcc crackkids.c -o crackkids && strip crackkids
Bon, on ne va pas faire un «strings crackkids» pour gagner on va voir ce que l’on peut faire avec radaRE2. A partir de maintenant oubliez ce qu’il y a dans ce source, et même ce qu’on a pondu comme exécutable.
Premièrement demandons au Rabin2 ce qu’il pense de ce fichier. Rabin2 est mix de objdump et readelf sous stéroides. Il comprend de base les formats d’exécutables JAVA, ELF et PE (et les MZ).
$ rabin2 -I crackkids file /home/thanat0s/code/helloworld/crackkids type EXEC (Executable file) pic false canary false has_va true root elf class ELF64 lang c arch x86 bits 64 machine AMD x86-64 architecture os linux subsys linux endian little strip true static false linenum false lsyms false relocs false rpath NONE
Ok… on est devant un ELF 64 bits strippé. Demandons au Rabin les fonctions importées (i) et les strings déclarées (z).
$ rabin2 -iz crackkids [Imports] ordinal=001 plt=0x00000480 bind=GLOBAL type=FUNC name=puts ordinal=002 plt=0x00000490 bind=GLOBAL type=FUNC name=__libc_start_main ordinal=003 plt=0x000004a0 bind=GLOBAL type=FUNC name=strcmp ordinal=004 plt=0x00000000 bind=UNKNOWN type=NOTYPE name=__gmon_start__ ordinal=005 plt=0x000004b0 bind=GLOBAL type=FUNC name=__isoc99_scanf 5 imports addr=0x000006f0 off=0x000006f0 ordinal=000 sz=33 len=33 section=.rodata type=A string=Chall easy for Radare dissection addr=0x00000711 off=0x00000711 ordinal=001 sz=5 len=5 section=.rodata type=A string=%16s addr=0x00000716 off=0x00000716 ordinal=002 sz=8 len=8 section=.rodata type=A string=You Win addr=0x0000071e off=0x0000071e ordinal=003 sz=11 len=11 section=.rodata type=A string=You Failed
Masel Tov Rabin !!! je vois un STRCMP, et le You Win/You Failed !… Ça a l’air pas mal. Ouvrons ceci dans RadaRE2.
$ radare2 crackkids -- This page intentionally left blank. [0x004004c0]>
Nous voila donc avec un shell à l’entry point. Et c’est là qu’il faut introduire un concept important ; les Flagspaces. L’addresse d’une string, d’une fonction, d’une référence est un “Flag”. Et vu que cela fait beaucoup d’objets, il sont rangés par “FlagSpaces”. «fs» permet d’afficher ou de sélectionner le Flagspace dans lequel on travaille et «f» affiche les flags.
[0x004004c0]> fs 00 strings 02 symbols 04 relocs 06 * sections [0x004004c0]> fs strings [0x004004c0]> f 0x0040071e 11 str.YouFailed 0x00400716 8 str.YouWin 0x00400711 5 str._16s 0x004006f0 33 str.ChalleasyforRadaredissection
Devant nous donc, 4 strings, ce qu’avait trouvé avec le rabin précédemment. On peut afficher les strings avec «ps» (Print String), et plus précisément «psz» pour une stringZ (Zero terminated).
[0x004004c0]> psz@str.YouWin You Win
Voir meme en hexa avec px (print hexa) dans ce cas, je précise combien de byte j’en veux et le mode d’affichage.
[0x004007c0]> pxs 16 @str.YouWin - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00400716 596f 7520 5769 6e00 596f 7520 4661 696c You Win.You Fail [0x004007c0]> pxw 16 @str.YouWin 0x00400716 0x20756f59 0x006e6957 0x20756f59 0x6c696146 You Win.You Fail
Et il y a bien d’autre print mode hexa. Pour l’aide c’est «?» donc print hexa help c’est «px?». Je vous laisse découvrir tout seul pxe.
[0x004007c0]> px? |Usage: px[afoswqWqQ][f] | px show hexdump | px/ same as x/ in gdb (help x) | pxa show annotated hexdump | pxe emoji hexdump! :) | pxf show hexdump of current function | pxo show octal dump | pxq show hexadecimal quad-words dump (64bit) | pxs show hexadecimal in sparse mode | pxQ same as above, but one per line | pxw show hexadecimal words dump (32bit) | pxW same as above, but one per line
Bon, avançons, allons voir le FlagSpace avec les fonctions, mais avant cela il faut le populer. Si le code n’a pas été analysé, il n’y a pas de flagspace “fonctions”.
[0x004004c0]> fs // Pas de flagspace «functions» 00 strings 02 symbols 04 relocs 06 * sections [0x004004c0]> aa // On lance l'analyse [0x004004c0]> fs // le Flagspace functions est apparu et sélectionné 00 strings 02 symbols 04 relocs 06 sections 08 * functions [0x004004c0]> f // quels sont les flags. 0x0040062e 7 loc.0040062e 0x004006e5 13 fcn.004006e5 0x004006d9 12 fcn.004006d9 0x00400635 13 fcn.00400635 0x0040057f 29 fcn.0040057f 0x0040056c 19 fcn.0040056c 0x00400532 7 fcn.00400532 0x004004b6 52 fcn.004004b6 0x004004a6 16 fcn.004004a6 0x00400486 16 fcn.00400486 0x0040047c 10 fcn.0040047c 0x00400470 12 loc.00400470 0x00400496 16 fcn.00400496
Bon maintenant trouvons où est appelé notre STRCMP. (Analyse Reference Flag)
[0x004004c0]> fs symbols // os sélectionne le flagspace symbol avec les appel aux libs [0x004004c0]> f // Liste les flags 0x004004b0 16 sym.imp.__isoc99_scanf 0x00400000 16 sym.imp.__gmon_start__ 0x004004a0 16 sym.imp.strcmp 0x00400490 16 sym.imp.__libc_start_main 0x00400480 16 sym.imp.puts 0x004004c0 256 entry0 0x004005cc 256 main [0x004004c0]> arf sym.imp.strcmp // Donne moi les références. Finding references of flags matching 'sym.imp.strcmp'... ar 0x004004a0 0x0040060f Macro 'findstref' removed.
Et voila STRCMP est appelé en 0x0040060F. Désassemblons désormais la fonction autour de cette adresse. (Print Disassembled Function)
[0x00400bc0]> pdf@0x0040060F / (fcn) fcn.0040059c 149 | 0x004005a0 48833d68022. cmp qword [rip+0x200268], 0x0 | ,=< 0x004005a8 741b je 0x4005c5 | | 0x004005aa b800000000 mov eax, 0x0 | | 0x004005af 4885c0 test rax, rax | ,==< 0x004005b2 7411 je 0x4005c5 | || 0x004005b4 55 push rbp | || 0x004005b5 bf10086000 mov edi, 0x600810 ; 0x00600810 | || 0x004005ba 4889e5 mov rbp, rsp | || 0x004005bd ffd0 call rax | || 0x00000000(unk) | || 0x004005bf 5d pop rbp | || 0x004005c0 e97bffffff jmp fcn.00400539 | ``-> 0x004005c5 e976ffffff jmp fcn.00400539 | 0x004005ca 90 nop | 0x004005cb 90 nop | ; DATA XREF from 0x004004dd (entry0) / (fcn) main 105 | 0x004005cc 55 push rbp | 0x004005cd 4889e5 mov rbp, rsp | 0x004005d0 4883ec30 sub rsp, 0x30 | 0x004005d4 c745f070617. mov dword [rbp-0x10], 0x73736170 ; 0x73736170 | 0x004005db 66c745f42100 mov word [rbp-0xc], 0x21 ; 0x00000021 | 0x004005e1 bff0064000 mov edi, str.ChalleasyforRadaredissection ; 0x004006f0 | ; CODE (CALL) XREF from 0x00400480 (fcn.0040047c) | 0x004005e6 e895feffff call sym.imp.puts | sym.imp.puts(unk) | 0x004005eb 488d45d0 lea rax, [rbp-0x30] | 0x004005ef 4889c6 mov rsi, rax | 0x004005f2 bf11074000 mov edi, str._16s ; 0x00400711 | 0x004005f7 b800000000 mov eax, 0x0 | 0x004005fc e8affeffff call sym.imp.__isoc99_scanf | sym.imp.__isoc99_scanf() | 0x00400601 488d55d0 lea rdx, [rbp-0x30] | 0x00400605 488d45f0 lea rax, [rbp-0x10] | 0x00400609 4889d6 mov rsi, rdx | 0x0040060c 4889c7 mov rdi, rax | 0x0040060f e88cfeffff call sym.imp.strcmp | sym.imp.strcmp() | 0x00400614 85c0 test eax, eax | ,===< 0x00400616 750c jne 0x400624 | | 0x00400618 bf16074000 mov edi, str.YouWin ; 0x00400716 | | 0x0040061d e85efeffff call sym.imp.puts | | sym.imp.puts() | ,====< 0x00400622 eb0a jmp loc.0040062e | |`---> 0x00400624 bf1e074000 mov edi, str.YouFailed ; 0x0040071e | | 0x00400629 e852feffff call sym.imp.puts | | sym.imp.puts() | | ; CODE (CALL) XREF from 0x00400622 (fcn.0040059c) |- loc.0040062e 7 | `----> 0x0040062e b800000000 mov eax, 0x0 | 0x00400633 c9 leave \ 0x00400634 c3 ret [
C’est un peu large, car il n’a pas détecté la fin inexistante de la fonction fcn.0x0040059c, mais ca passe, on y est ! On peut constater, comme dans un IDA, les petites flèches d’embranchement qui confirment que le STRCMP est la clef de ce chall. Celui ci compare ce qu’il y aura sur le stack en rbp-0x30 avec ce qu’il y a en rbp-0x10. Le 0x30 sera remplis par un scanf en 0X4005fc. Quand rbp-0x10 il est remplis par deux mov en 0X4005d4 et 0x4005db. Mais comment convertir ceci en string. Et bien on peut utiliser «rax2» un autre tools de la suite RadaRE2 qui convertit tout en tout. Et il est aussi possible de l’apeller directement depuis notre RadaRE2 avec un «!».
[0x00400cc0]> !rax2 -h Usage: rax2 [options] [expr ...] int -> hex ; rax2 10 hex -> int ; rax2 0xa -int -> hex ; rax2 -77 -hex -> int ; rax2 0xffffffb3 int -> bin ; rax2 b30 bin -> int ; rax2 1010d float -> hex ; rax2 3.33f hex -> float ; rax2 Fx40551ed8 oct -> hex ; rax2 35o hex -> oct ; rax2 Ox12 (O is a letter) bin -> hex ; rax2 1100011b hex -> bin ; rax2 Bx63 raw -> hex ; rax2 -S < /binfile hex -> raw ; rax2 -s 414141 -b binstr -> bin ; rax2 -b 01000101 01110110 -B keep base ; rax2 -B 33+3 -> 36 -d force integer ; rax2 -d 3 -> 3 instead of 0x3 -e swap endianness ; rax2 -e 0x33 -f floating point ; rax2 -f 6.3+2.1 -h help ; rax2 -h -k randomart ; rax2 -k 0x34 1020304050 -n binary number ; rax2 -e 0x1234 # 34120000 -s hexstr -> raw ; rax2 -s 43 4a 50 -S raw -> hexstr ; rax2 -S < /bin/ls > ls.hex -t tstamp -> str ; rax2 -t 1234567890 -x hash string ; rax2 -x linux osx -u units ; rax2 -u 389289238 # 317.0M -v version ; rax2 -V [0x004004c0]> !rax2 -s 0x2173736170 | rev pass!
Une autre solution capilo-tractée assez violente qui ne servira d’exemple que pour montrer ce que l’on peut faire avec les calculs d’offset de RadaRE2;
[0x00400adb]> 0x4005d4 [0x004005d4]> ps 4 @+$l-4 pass [0x004005d4]> 0x4005db [0x004005db]> ps 2 @+$l-2 !\x00
Ok, je traduis. Je me place en 0x4005d4 et j’affiche une string d’une longueur de 4 depuis (l’offset courant + taille de l’opcode – 4). (4 car c’est la longueur du dword).
Et oui la commande permet de faire des opérations de dingue sur les offsets. Et nombre de substitutions sont disponibles.
[0x004005db]> ?$? |RNum $variables usable in math expressions: | $$ here (current virtual seek) | $o here (current disk io offset) | $s file size | $b block size | $w get word size, 4 if asm.bits=32, 8 if 64, ... | $c,$r get width and height of terminal | $S section offset | $SS section size | $j jump address (e.g. jmp 0x10, jz 0x10 => 0x10) | $f jump fail address (e.g. jz 0x10 => next instruction) | $I number of instructions of current function | $F current function size | $Jn get nth jump of function | $Cn get nth call of function | $Dn get nth data reference in function | $Xn get nth xref of function | $m opcode memory reference (e.g. mov eax,[0x10] => 0x10) | $l opcode length | $e 1 if end of block, else 0 | ${ev} get value of eval config variable # TODO: use ?k too | $? last comparision value
Voila pour une première approche, et on est loin de loin d’avoir tout vu encore. Je n’ai même pas parlé du mode “Visual”.
Retenons, aa, pdf, psz, f, fs et arf. Cela sera déja très bien.
Yop!
Merci pour les tuto! C’est vraiment cool! Ca, c’est pour t’encourager à continuer ton site car c’est une très bonne source d’info.
Bon, par contre, j’ai pas réussi à tout tout faire dans le tuto et celui d’après… En fait, quand je veux lire le contenu de la mémoire d’un variable, j’ai que du ff ff ff ff.. J’ai testé avec la dernière version de RadaRe (git clone) et dans une VM. C’est peut être pour ça? Du coup, impossible d’écrire… J’ai quand même compris le principe et j’ai bien hâte à la prochaine étape!!
Tchô, bonne continuation.
Hey merci pour les explications ! J’adore ce tool
Merci Thanat0s pour m’avoir mis le pied à l’étrier. Yaplukapoutrer maintenant !
Pingback: Insomni’Hack 2017 write-up : Internet Of Fail | Le blog du Phil
Pingback: Insomni’Hack 2017 write-up : Internet Of Fail – 400 | Le blog du Phil
Pingback: Insomni’Hack 2017 write-up : Internet Of Fail – 400 – Securimag