[BreizhCTF] Reverse - La Key Dans La Stone

6 minute read

Le binaire

Après avoir décompilé le binaire du challenge key en utilisant un outil tel que Binja ou Ghidra, et constaté que la fonction principale (main) était assez simple, on commence à chercher d’autres fonctions plus intéressantes. En effet, la majeure partie du challenge se trouvait dans l’une des fonctions appelées par la fonction main.

ks_asm quésaco ?

ks_asm est une fonction permettant de compiler de l’assembleur via le module keystone.

Notre binaire compile du code assembleur, met le résultat sous forme d’opcode (code assembleur compilé) dans une variable et call ce code puis vérifie si le “code” nous renvoie 0 auquel cas il nous affiche “Bien joué tu as le flag du chall !” et si la condition n’est pas vérifiée il nous affiche “Encore un petit d’effort”.

TL;DR

Il compile de l’assembleur et l’exécute puis check si l’ “assembleur” return 0.

Comment solve cet enfer ?

On va commencer par extirper du programme le code assembleur qui est compilé par la fonction ks_asm en utilisant Binary Ninja puis en enlevant toutes les impuretés avec VsCode à grand coup de Ctrl+F et replace-all :thumbsup:

Ce qui nous donne comme code:

  1push    rbp
  2mov     rbp, rsp
  3sub     rsp, 0x20
  4mov     rax, 0
  5mov     rdi, 0
  6mov     rsi, rsp
  7mov     rdx, 0x19
  8syscall
  9mov     rcx, rax
 10cmp     rax, 0x19
 11jnz     _end
 12mov     rsi ,rsp
 13mov     rdi, 0
 14mov     al, byte [rsi + rdi]
 15cmp     al, 0x42
 16jnz     _end
 17inc     rdi
 18mov     al, byte [rsi + rdi]
 19cmp     al, 0x5a
 20jnz     _end
 21inc     rdi
 22mov     al, byte [rsi + rdi]
 23xor     al, 0x78
 24cmp     al, 0x30
 25jnz     _end
 26inc     rdi
 27mov     al, byte [rsi + rdi]
 28sub     al, 0x30
 29cmp     al, 0x13
 30jnz     _end
 31inc     rdi
 32mov     al, byte [rsi + rdi]
 33add     al, 0x30
 34cmp     al, 0x84
 35jnz     _end
 36inc     rdi
 37mov     al, byte [rsi + rdi]
 38xchg    al, al
 39cmp     al, 0x46
 40jnz     _end
 41inc     rdi
 42mov     al, byte [rsi + rdi]
 43cmp     al, 0x7b
 44jnz     _end
 45inc     rdi
 46mov     al, byte [rsi + rdi]
 47xor     al, 0x60
 48cmp     al, 0x36
 49jnz     _end
 50inc     rdi
 51mov     al, byte [rsi + rdi]
 52add     al, 0x40
 53cmp     al, 0x70
 54jnz     _end
 55inc     rdi
 56mov     al, byte [rsi + rdi]
 57sub     al, 0x44
 58cmp     al, 0x31
 59jnz     _end
 60inc     rdi
 61mov     al, byte [rsi + rdi]
 62cmp     al, 0x35
 63jnz     _end
 64inc     rdi
 65mov     al, byte [rsi + rdi]
 66cmp     al, 0x5f
 67jnz     _end
 68inc     rdi
 69mov     al, byte [rsi + rdi]
 70xchg    al, al
 71cmp     al, 0x34
 72jnz     _end
 73inc     rdi
 74mov     al, byte [rsi + rdi]
 75cmp     al, 0x76
 76jnz     _end
 77inc     rdi
 78mov     al, byte [rsi + rdi]
 79sub     al, 0x33
 80cmp     al, 0
 81jnz     _end
 82inc     rdi
 83mov     al, byte [rsi + rdi]
 84add     al, 2
 85cmp     al, 0x7c
 86jnz     _end
 87add     rdi, 2
 88mov     al, byte [rsi + rdi]
 89xchg    al, al
 90cmp     al, 0x31
 91jnz     _end
 92dec     rdi
 93mov     al, byte [rsi + rdi]
 94cmp     al, 0x5f
 95jnz     _end
 96add     rdi, 2
 97mov     al, byte [rsi + rdi]
 98sub     al, 0x33
 99cmp     al, 1
100jnz     _end
101mov     al, byte [rsi + rdi + 3]
102cmp     al, 0x31
103jnz     _end
104mov     al, byte [rsi + rdi + 2]
105add     al, 1
106cmp     al, 0x64
107jnz     _end
108mov     al, byte [rsi + rdi  + 1]
109cmp     al, 0x5f
110jnz     _end
111add     rdi, 0x4
112mov     al, byte [rsi + rdi]
113cmp     al, 0x65
114jnz     _end
115mov     al, byte [rsi + rdi + 1]
116sub     al, 0x19
117cmp     al, 0x64
118jnz     _end
119mov     rax, 1
120mov     rdi, 1
121mov     rdx, rcx
122syscall
123add     rsp, 0x20
124pop     rbp
125xor     rax, rax
126ret
127_end:
128add     rsp, 0x20
129pop     rbp
130mov     rax, 1
131ret

On le compile utilisant nasm (version 2.15.05) et ld

1number@rev$ nasm -f elf64 key_asm
2number@rev$ ld key_asm.o -o challenge_asm -z execstack
3ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
4

Puis on le re-décompile avec binary ninja pour avoir un code mangeable.

 1int64_t start(){
 2char var_28;
 3int64_t rax = syscall(sys_read {0}, 0, &var_28, 0x19);
 4int64_t rcx = rax;
 5if (rax == 0x19)
 6{
 7          void* rsi_1 = &var_28;
 8          rax = var_28;
 9          if (rax == 0x42)
10          {
11              char var_27;
12              rax = var_27;
13              if (rax == 0x5a)
14              {
15                ...
16
17            syscall(sys_write {1}, 1, rsi_1, rcx);
18            return 0;
19
20                  }
21          }
22    }
23    return 1;
24}

La fonction start effectue un appel au syscall read pour récupérer 25 caractères en entrée et stocke la longueur de cette entrée dans rcx ainsi que son contenu dans la variable var_28. Ensuite, elle effectue plusieurs tests sur cette entrée et si elle est valide, elle affiche le flag en utilisant le syscall write et renvoie 0.

Les tests effectués suivent un pattern (modèle) récurrants: le programme itére sur l’entièreté de notre flag et fait un test sous la forme

 1              rax = input[26];
 2              if (rax == 0x5a)
 3              {
 4                  rax = input[25];;
 5                  rax = (rax ^ 0x78);
 6                  if (rax == 0x30)
 7                  {
 8                      etc ...
 9                      }
10              }
11
12

J’avais la flemme de faire ça à la main donc j’ai opté pour un script angr.

Un solve puni par la loi

A partir de là il ne me manquait plus rien, j’avais la longeur du flag et l’adresse à éviter (le return 0 qui aurait fait que la condition initiale ne serait pas remplie) donc c’est facile à faire avec

 1import angr
 2import claripy
 3p = angr.Project('./chal')
 4state = p.factory.entry_state(args=["./chal.bin"])
 5s = p.factory.simulation_manager(state)
 6find_addr = 0x0040117a # adresse de l'instruction: return 0
 7avoid_addr = 0x0040118f #     adresse de l'instruction: return 1
 8s.explore(find=find_addr,avoid=avoid_addr)
 9print(s.found[0].posix.stdin.concretize()[0].split(b'\0')[0])
10

Et en un temps record on obtient le flag: BZHCTF{V0u5_4v3z_14_c1e}

Merci au créateur du challenge et merci d’avoir lu jusqu'à la fin mes palabres :) .

Si vous avez une question ou une remarque à faire sur mon writeup n’hésitez pas à m’envoyer un message sur Twitter ou sur Discord: @numb3rss.