Cet article présente le write-up du challenge Matriochka Step-1 du CTF de qualification pour la Nuit du Hack 2017.
Il s’agit de la traduction du write-up que j’avais écris sur le github de la Team Cryptis.
Challenge
Category: Reverse Points: 35 Solves: 330
Description : Can you… Reverse it ? Analyse it ? Calculate it ? Keygen it ? Modify it ? Enjoy yourself 🙂 This challenge is separated in four steps with four separate flags to guide you. Challenge : https://quals.nuitduhack.com/challenges/quals-ndh2k17/matriochka-step-1/.
Write-up
Le challenge consiste en un unique fichier binaire : step1.bin. Commençons donc par l’exécuter.
1 2 3 4 5 |
teamcryptis@debian:/var/ctf/NDH XV/reverse/$ chmod +x step1.bin teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step1.bin Usage: ./step1.bin <pass> teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step1.bin flag! Try again :( |
Comme souvent, le programme attends le flag comme argument et affiche un message en fonction de celui-ci.
N’ayant pas de windows sous la main, j’avais démarré avec radare2. Nous commencons donc par analyser le binaire avec la commande aa (Analyse All) puis on affiche la fonction main (sym.main) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
teamcryptis@debian:/var/ctf/NDH XV/reverse/$ r2 step1.bin [0x00400570]> aa [x] Analyze all flags starting with sym. and entry0 (aa) [0x00400570]> pdf @ sym. sym.deregister_tm_clones sym.register_tm_clones sym.__do_global_dtors_aux sym.frame_dummy sym.__libc_csu_fini sym.touch sym._fini sym.mmm sym.you sym.__libc_csu_init sym._start sym.main sym._init sym.my sym.imp.puts sym.imp.strlen sym.imp.printf sym.imp.fputc sym.imp.__libc_start_main sym.imp.strcmp [0x00400570]> pdf @ sym.main ;-- main: / (fcn) sym.main 74 | sym.main (); | ; var int local_10h @ rbp-0x10 | ; var int local_4h @ rbp-0x4 | ; DATA XREF from 0x0040058d (entry0) | 0x00400666 55 push rbp | 0x00400667 4889e5 mov rbp, rsp | 0x0040066a 4883ec10 sub rsp, 0x10 | 0x0040066e 897dfc mov dword [rbp - local_4h], edi | 0x00400671 488975f0 mov qword [rbp - local_10h], rsi | 0x00400675 837dfc02 cmp dword [rbp - local_4h], 2 ; [0x2:4]=0x102464c | ,=< 0x00400679 741b je 0x400696 | | 0x0040067b 488b45f0 mov rax, qword [rbp - local_10h] | | 0x0040067f 488b00 mov rax, qword [rax] | | 0x00400682 4889c6 mov rsi, rax | | 0x00400685 bfa0464300 mov edi, 0x4346a0 | | 0x0040068a b800000000 mov eax, 0 | | 0x0040068f e88cfeffff call sym.imp.printf ; int printf(const char *format); | ,==< 0x00400694 eb13 jmp 0x4006a9 | |`-> 0x00400696 488b45f0 mov rax, qword [rbp - local_10h] | | 0x0040069a 4883c008 add rax, 8 | | 0x0040069e 488b00 mov rax, qword [rax] | | 0x004006a1 4889c7 mov rdi, rax | | 0x004006a4 e807000000 call sym.mmm | | ; JMP XREF from 0x00400694 (sym.main) | `--> 0x004006a9 b800000000 mov eax, 0 | 0x004006ae c9 leave \ 0x004006af c3 ret [0x00400570]> |
On peut déjà remarquer un cmp à l’adresse 0x00400675 qui effectue un branchement après l’appel de printf . Il compare la constante entière 2 avec le premier argument de la fonction main (c’est à dire argc ). Ce bloc est certainement une vérification du nombre d’argument de ligne de commande (celui qui avait précédemment expliqué le mode d’usage du programme).
Ainsi, la partie importante de la fonction main se situe entre les offsets 0x00400696 et 0x004006a4. Ce block prépare la pile (stack) et appelle la fonction mmm . Nous allons donc simplement désassembler cette fonction :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[0x00400570]> pdf @ sym.mmm / (fcn) sym.mmm 29 | sym.mmm (); | ; var int local_8h @ rbp-0x8 | ; CALL XREF from 0x004006a4 (sym.main) | 0x004006b0 55 push rbp | 0x004006b1 4889e5 mov rbp, rsp | 0x004006b4 4883ec10 sub rsp, 0x10 | 0x004006b8 48897df8 mov qword [rbp - local_8h], rdi | 0x004006bc 488b45f8 mov rax, qword [rbp - local_8h] | 0x004006c0 4889c7 mov rdi, rax | 0x004006c3 e805000000 call sym.you | 0x004006c8 83c001 add eax, 1 | 0x004006cb c9 leave \ 0x004006cc c3 ret |
Encore une fois, la fonction ne semble pas faire quoi que ce soit, mit à part appeler une autre fonction. Nous allons donc continuer à explorer le programme en suivant simplement les appels de fonction :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
[0x00400570]> pdf @ sym.you / (fcn) sym.you 29 | sym.you (); | ; var int local_8h @ rbp-0x8 | ; CALL XREF from 0x004006c3 (sym.mmm) | 0x004006cd 55 push rbp | 0x004006ce 4889e5 mov rbp, rsp | 0x004006d1 4883ec10 sub rsp, 0x10 | 0x004006d5 48897df8 mov qword [rbp - local_8h], rdi | 0x004006d9 488b45f8 mov rax, qword [rbp - local_8h] | 0x004006dd 4889c7 mov rdi, rax | 0x004006e0 e805000000 call sym.touch | 0x004006e5 83c001 add eax, 1 | 0x004006e8 c9 leave \ 0x004006e9 c3 ret [0x00400570]> pdf @ sym.touch / (fcn) sym.touch 29 | sym.touch (); | ; var int local_8h @ rbp-0x8 | ; CALL XREF from 0x004006e0 (sym.you) | 0x004006ea 55 push rbp | 0x004006eb 4889e5 mov rbp, rsp | 0x004006ee 4883ec10 sub rsp, 0x10 | 0x004006f2 48897df8 mov qword [rbp - local_8h], rdi | 0x004006f6 488b45f8 mov rax, qword [rbp - local_8h] | 0x004006fa 4889c7 mov rdi, rax | 0x004006fd e805000000 call sym.my | 0x00400702 83c001 add eax, 1 | 0x00400705 c9 leave \ 0x00400706 c3 ret [0x00400570]> pdf @ sym.my / (fcn) sym.my 276 | sym.my (); | ; var int local_28h @ rbp-0x28 | ; var int local_19h @ rbp-0x19 | ; var int local_18h @ rbp-0x18 | ; var int local_10h @ rbp-0x10 | ; var int local_8h @ rbp-0x8 | ; CALL XREF from 0x004006fd (sym.touch) | 0x00400707 55 push rbp | 0x00400708 4889e5 mov rbp, rsp | 0x0040070b 4883ec30 sub rsp, 0x30 ; '0' | 0x0040070f 48897dd8 mov qword [rbp - local_28h], rdi | 0x00400713 488b45d8 mov rax, qword [rbp - local_28h] | 0x00400717 4889c7 mov rdi, rax | 0x0040071a e8f1fdffff call sym.imp.strlen ; size_t strlen(const char *s); | 0x0040071f 488945e8 mov qword [rbp - local_18h], rax | 0x00400723 48837de801 cmp qword [rbp - local_18h], 1 ; [0x1:8]=0x10102464c45 | ,=< 0x00400728 766a jbe 0x400794 | | 0x0040072a 48c745f80000. mov qword [rbp - local_8h], 0 | | 0x00400732 488b45e8 mov rax, qword [rbp - local_18h] | | 0x00400736 4883e801 sub rax, 1 | | 0x0040073a 488945f0 mov qword [rbp - local_10h], rax | ,==< 0x0040073e eb47 jmp 0x400787 | .---> 0x00400740 488b55d8 mov rdx, qword [rbp - local_28h] | ||| 0x00400744 488b45f8 mov rax, qword [rbp - local_8h] | ||| 0x00400748 4801d0 add rax, rdx ; '(' | ||| 0x0040074b 0fb600 movzx eax, byte [rax] | ||| 0x0040074e 8845e7 mov byte [rbp - local_19h], al | ||| 0x00400751 488b55d8 mov rdx, qword [rbp - local_28h] | ||| 0x00400755 488b45f8 mov rax, qword [rbp - local_8h] | ||| 0x00400759 4801c2 add rdx, rax ; '#' | ||| 0x0040075c 488b4dd8 mov rcx, qword [rbp - local_28h] | ||| 0x00400760 488b45f0 mov rax, qword [rbp - local_10h] | ||| 0x00400764 4801c8 add rax, rcx ; '&' | ||| 0x00400767 0fb600 movzx eax, byte [rax] | ||| 0x0040076a 8802 mov byte [rdx], al | ||| 0x0040076c 488b55d8 mov rdx, qword [rbp - local_28h] | ||| 0x00400770 488b45f0 mov rax, qword [rbp - local_10h] | ||| 0x00400774 4801c2 add rdx, rax ; '#' | ||| 0x00400777 0fb645e7 movzx eax, byte [rbp - local_19h] | ||| 0x0040077b 8802 mov byte [rdx], al | ||| 0x0040077d 488345f801 add qword [rbp - local_8h], 1 | ||| 0x00400782 48836df001 sub qword [rbp - local_10h], 1 | ||| ; JMP XREF from 0x0040073e (sym.my) | |`--> 0x00400787 488b45e8 mov rax, qword [rbp - local_18h] | | | 0x0040078b 48d1e8 shr rax, 1 | | | 0x0040078e 483b45f8 cmp rax, qword [rbp - local_8h] | `===< 0x00400792 77ac ja 0x400740 | `-> 0x00400794 488b45d8 mov rax, qword [rbp - local_28h] | 0x00400798 beb2464300 mov esi, str.Tr4laLa___ ; "Tr4laLa!!!" @ 0x4346b2 | 0x0040079d 4889c7 mov rdi, rax | 0x004007a0 e8abfdffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2); | 0x004007a5 85c0 test eax, eax | ,=< 0x004007a7 7562 jne 0x40080b | | 0x004007a9 bfbd464300 mov edi, str.Well_done_:_ ; "Well done :)" @ 0x4346bd | | 0x004007ae e84dfdffff call sym.imp.puts ; loc.imp.__gmon_start__-0x60 | | 0x004007b3 48c745f80000. mov qword [rbp - local_8h], 0 | ,==< 0x004007bb eb42 jmp 0x4007ff | .---> 0x004007bd 488b0d344323. mov rcx, qword [obj.stderr] ; [0x634af8:8]=0x654428203a434347 LEA obj.stderr ; "GCC: (Debian 4.9.2-10) 4.9.2" @ 0x634af8 | ||| 0x004007c4 488b45f8 mov rax, qword [rbp - local_8h] | ||| 0x004007c8 480500094000 add rax, obj.nextStep | ||| 0x004007ce 0fb630 movzx esi, byte [rax] | ||| 0x004007d1 488b45f8 mov rax, qword [rbp - local_8h] | ||| 0x004007d5 ba00000000 mov edx, 0 | ||| 0x004007da 48f775e8 div qword [rbp - local_18h] | ||| 0x004007de 488b45d8 mov rax, qword [rbp - local_28h] | ||| 0x004007e2 4801d0 add rax, rdx ; '(' | ||| 0x004007e5 0fb600 movzx eax, byte [rax] | ||| 0x004007e8 31f0 xor eax, esi | ||| 0x004007ea 83f030 xor eax, 0x30 | ||| 0x004007ed 0fbec0 movsx eax, al | ||| 0x004007f0 4889ce mov rsi, rcx | ||| 0x004007f3 89c7 mov edi, eax | ||| 0x004007f5 e836fdffff call sym.imp.fputc ; int fputc(int c, | ||| 0x004007fa 488345f801 add qword [rbp - local_8h], 1 | ||| ; JMP XREF from 0x004007bb (sym.my) | |`--> 0x004007ff 48817df89f3d. cmp qword [rbp - local_8h], 0x33d9f ; [0x33d9f:8]=0x517c515c04426411 | `===< 0x00400807 76b4 jbe 0x4007bd | ,==< 0x00400809 eb0a jmp 0x400815 | |`-> 0x0040080b bfca464300 mov edi, str.Try_again_:_ ; "Try again :(" @ 0x4346ca | | 0x00400810 e8ebfcffff call sym.imp.puts ; loc.imp.__gmon_start__-0x60 | | ; JMP XREF from 0x00400809 (sym.my) | `--> 0x00400815 488b45e8 mov rax, qword [rbp - local_18h] | 0x00400819 c9 leave \ 0x0040081a c3 ret [0x00400570]> |
Quatre fonction sont appelée successivement : mmm , you , touch et my , la dernière étant clairement plus complexe que les précédentes.
En regardant le code assembleur de la fonction my, nous remarquons l’utilisation de la chaîne de caractères “Tr4laLa” à l’offset 0x00400798. Nous avons ici un bel exemple du sens de l’humour français 🙂 : “mmm you touch my Tr4laLa”.
Cette fonction semble comparer le flag d’entrée avec la chaîne “Tr4laLa”, mais beaucoup d’opérations sont effectuée sur le flag avant cette comparaison. Le cmp à 0x00400728 après l’appel à strlen pourrait être un moyen d’éviter ces opérations ?
Dans tous les cas, la taille de la fonction est supérieure à 10 instructions et c’est clairement trop si on prend en compte notre flemme… Il est temps d’appeler IDA à la rescousse !
1 minute pour lancer la machine virtuelle Windows, une autre pour lancer la version totalement légale d’IDA Pro et nous y sommes. Ce qui suit est le pseudo-code C de la fonction my donnée par IDA (notez qu’il s’agit d’un binaire 64 bits, vous devez donc utiliser le lanceur idaq64 ) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
unsigned __int64 __fastcall my(const char *a1) { char v1; // ST17_1@3 unsigned __int64 v3; // [sp+18h] [bp-18h]@1 signed __int64 v4; // [sp+20h] [bp-10h]@2 unsigned __int64 v5; // [sp+28h] [bp-8h]@2 unsigned __int64 i; // [sp+28h] [bp-8h]@6 v3 = strlen(a1); if ( v3 > 1 ) { v5 = 0LL; v4 = v3 - 1; while ( v3 >> 1 > v5 ) { v1 = a1[v5]; a1[v5] = a1[v4]; a1[v4] = v1; ++v5; --v4; } } if ( !strcmp(a1, "Tr4laLa!!!") ) { puts("Well done :)"); for ( i = 0LL; i <= 0x33D9F; ++i ) fputc((char)(*(_BYTE *)(i + 4196608) ^ a1[i % v3] ^ 0x30), _bss_start); } else { puts("Try again :("); } return v3; } |
La partie du code située avant la condition !strcmp(a1, "Tr4laLa!!!") correspond aux opération sur le chaîne d’entrée. Nous pourrions lire ce pseudo-code et essayer de comprendre son effet concret sur la chaîne v1 . Cependant, nous allons commencer par réécrire ce code en un programme C valide et l’essayer sur différente chaîne pour tenter de le comprendre plus simplement.
Quelques modifications sont nécessaires pour obtenir un code C compilable. La boucle à l’intérieur de la condition est assez complexe et n’est pas requit pour nos tests, elle peut donc être retirée sans problème. Vous pouvez trouver le code C final à cette adresse.
Pour finir, nous compilons le programme puis nous le testons avec quelques chaînes :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
teamcryptis@debian:/var/ctf/NDH XV/reverse/$ gcc main.c teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./a.out Enter the string : test tset Try again :( teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./a.out Enter the string : a_string gnirts_a Try again :( teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./a.out Enter the string : Nuit_du_hack_2017 7102_kcah_ud_tiuN Try again :( teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./a.out Enter the string : Tr4laLa!!! !!!aLal4rT Try again :( |
Cette fonction semble simplement inverser les chaînes de caractères. Puisque l’entrée est comparée avec le chaîne “Tr4laLa!!!”, nous allons simplement tenter avec son inverse : “!!!aLal4rT” :
1 2 3 4 |
teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step1.bin !!!aLal4rT bash: !aLal4rT: event not found teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step1.bin \!\!\!aLal4rT Well done :) |
Bien joué, le flag est !!!aLal4rT
Notez que lorsque le bon flag est entré (c’est à dire ./step1.bin \!\!\!aLal4rT), un grand nombre de caractères sont affichés sur la sortie standard. La raison est que le programme affiche le challenge suivant (matriochka-step2) sur stderr quand le bon flag est passé. Cette opération est faite dans la boucle que nous avions précédemment ignorée dans notre programme C :
1 2 |
for ( i = 0LL; i <= 0x33D9F; ++i ) fputc((char)(*(_BYTE *)(i + 4196608) ^ a1[i % v3] ^ 0x30), _bss_start); |
Pour créer l’exécutable du second challenge, il suffit de rediriger stderr dans un fichier (vous pouvez trouver l’exécutable du second challenge ici) :
1 2 3 4 |
teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step1.bin \!\!\!aLal4rT 2> step2.bin teamcryptis@debian:/var/ctf/NDH XV/reverse/$ chmod +x step2.bin teamcryptis@debian:/var/ctf/NDH XV/reverse/$ ./step2.bin Usage: ./step2.bin <pass> |
Et c’est reparti ! 😉
Autres write-ups et resources
Aucune autre resource pour le moment.
You must log in to post a comment.