Partie 5 - Solutions des exercices
Solutions des exercices
Plusieurs exercices ont été proposés dans ce cours. Cette section comporte des solutions pour chacun d’eux. Il est à noter que les manières de résoudre un même exercice peuvent être multiples. Il ne s’agit donc pas de solutions “optimales” mais seulement de solutions qui permettent de résoudre un exercice donné.
Dans tous les scripts proposés, il est normal que vous n’ayez pas les mêmes adresses car cela dépend en partie de votre compilateur.
Il suffit de les adapter en fonction de ce que vous avez comme adresses dans le code désassemblé.
Il n’est pas très utile de regarder les solutions aux exercices si vous n’avez pas cherché par vous même à une manière de résoudre l’exercice, vous n’apprendrez pas grand chose 😅 …
Exercice 1️⃣ - Introduction
Objectif
Trouver le bon input de ce programme :
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
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
unsigned long long algo(unsigned long long arg)
{
unsigned long long result = 0;
unsigned char x =0;
unsigned long long temp =0;
unsigned long long key =0xef9e8bd8f3afe9eb;
for (int i =0;i<8;i++)
{
x = (arg >> (i*8)) &0xff;
switch(x % 2)
{
case 0:
temp = 0xff;
break;
case 1:
temp = x ^ (unsigned char)((key >> (i*8)) &0xff);
break;
}
result = result | (temp << (i*8));
}
return result;
}
int main()
{
char key_buffer[16] = {0};
puts("Give me the key in hexadecimal : ");
read(0,key_buffer,16);
unsigned long long arg = strtoull(key_buffer,NULL,16);
if (algo(arg) == 0xdeadbeefcafebabe)
{
puts("Win !");
return 1337;
}
else
{
puts("Loose !");
return -1;
}
}
Solution
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
import angr
import claripy
p = angr.Project("./exe")
arg_symb = claripy.BVS('input', 8*8)
state_0 = p.factory.blank_state(addr= 0x401245) # Adresse de "push rbp" dans le "main"
sm = p.factory.simulation_manager(state_0)
def hook_strtoull(state):
print("[i] La fonction strtoull a été hookée")
state.regs.rax = arg_symb
p.hook(0x4012a7,hook_strtoull,5)
print("[+] Exploration en Cours ....")
sm.explore( find = 0x4012cb, avoid = 0x4012e1)
print("[+] Arrivé à destination")
if len(sm.found) == 0:
print("[-] Il n'a pas été possible d'atteindre la destination")
quit()
else :
print("[+] Détermination de l'input valide")
# Récupération de l'état qui est arrivé dans le bon bloc
found = sm.found[0]
# Appel au solveur pour retourner au moins une solution
res = found.solver.eval(arg_symb)
print("[+] Le bon input est : ",hex(res))
Commentaire
Ce qui posait problème dans ce script était principalement la fonction strtoull
. Il s’agit d’une fonction qui ressemble à atoi
mais qui retourne un entier de 64 bits. On l’a donc hookée afin qu’angr puisse poursuivre l’exécution sans problème.
Pas besoin de hook puts
et read
qui sont hookées correctement de base par angr.
Résultat
1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Exploration en Cours ....
WARNING | 2023-09-16 15:30:04,501 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior.
WARNING | 2023-09-16 15:30:04,501 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2023-09-16 15:30:04,501 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2023-09-16 15:30:04,501 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2023-09-16 15:30:04,501 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2023-09-16 15:30:04,502 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbp with 8 unconstrained bytes referenced from 0x401245 (main+0x4 in exe (0x401245))
[i] La fonction strtoull a été hookée
[+] Arrivé à destination
[+] Détermination de l'input valide
[+] Le bon input est : 0x3133353739515355
Exercice 2️⃣ - Lecture de la stack
Objectif
Ecrire une fonctionread_from_stack(state,n)
qui affiche les n
première valeurs ( de 64 bits par ex) sur la stack de l’état state
.
Solution
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
import archinfo
import angr
def read_from_stack(state, n):
stack_pointer = state.regs.rsp
values = []
for _ in range(n):
# Lecture de 8 octets (64 bits)
value = state.memory.load(stack_pointer, 8, endness=archinfo.Endness.LE)
values.append(value)
# Incrémentation de RSP pour lire la prochaine valeur
stack_pointer += 8
return values
# Programme quelconque
binary_path = "/bin/true"
proj = angr.Project(binary_path)
initial_state = proj.factory.entry_state()
# Lecture des 10 premières valeurs de 64 bits de la pile
values_on_stack = read_from_stack(initial_state, 5)
for i, value in enumerate(values_on_stack):
print(f"Valeur {i + 1}: {value}")
Commentaire
Déjà, merci ChatPGT pour les travaux ;) !
Ensuite, on utilise la lecture en mémoire pour lire les différentes valeurs de 8 octets sur la stack.
Le programme /bin/true
est utilisé ici mais vous pouvez spécifier n’importe quel programme.
Résultat
1
2
3
4
5
Valeur 1: <BV64 0x1>
Valeur 2: <BV64 0x7fffffffffeffc8>
Valeur 3: <BV64 0x0>
Valeur 4: <BV64 0x0>
Valeur 5: <BV64 0x19>
Exercice 3️⃣ - Gestion de l’input et output
Objectif
Tester la gestion de stdin
en compilant un programme basique en C qui lit, par exemple, 8 octets et vérifie qu’il s’agit du bon mot de passe.
Utiliser ensuite angr afin de trouver le mot de passe automatiquement sans avoir à hook les fonctions qui lisent depuis stdin
.
Programme utilisé
Voici le programme C utilisé pour cet exercice compilé avec gcc -no-pie main.c -o exe
:
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
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
unsigned long long algo(unsigned char *arg)
{
unsigned long long result = 0;
unsigned char x =0;
unsigned long long temp =0;
unsigned long long key =0xef9e8bd8f3afe9eb;
for (int i =0;i<8;i++)
{
x = arg[i] ;
switch(x % 2)
{
case 0:
temp = 0xff;
break;
case 1:
temp = x ^ (unsigned char)((key >> (i*8)) &0xff);
break;
}
result = result | (temp << (i*8));
}
return result;
}
int main()
{
// solution : USQ97531
unsigned char key_buffer[8] = {0};
puts("Give me the key in hexadecimal : ");
read(0,key_buffer,8);
if (algo(key_buffer) == 0xdeadbeefcafebabe)
{
puts("Win !");
return 1337;
}
else
{
puts("Loose !");
return -1;
}
}
Solution
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
import angr
import sys
import claripy
p = angr.Project("./exe")
flag = claripy.BVS('flag', 8*8)
# Utilisation d'un buffer symbolique dans stdin
state_0 = p.factory.blank_state(addr= 0x4011ea,stdin=flag)
sm = p.factory.simulation_manager(state_0)
def is_output_good(state):
# Est-ce que "Win !" est présent dans l'output ?
output = state.posix.dumps(sys.stdout.fileno())
return b'Win !' in output
def is_output_bad(state):
# Est-ce que "Loose !" est présent dans l'output ?
output = state.posix.dumps(sys.stdout.fileno())
return b'Loose !' in output
print("[+] Exploration en Cours ....")
sm.explore( find = is_output_good, avoid = is_output_bad)
if len(sm.found) == 0:
print("[-] Il n'a pas été possible d'atteindre la destination")
quit()
else :
print("[+] Détermination de l'input valide")
# Récupération de l'état qui est arrivé dans le bon bloc
found = sm.found[0]
# Conversion en bytes du résultat
res = found.solver.eval(flag,cast_to=bytes)
print("[+] Le bon input est : ",res.decode())
Commentaire
Les points importants sont les suivants :
- Utilisation d’un buffer symbolique dans l’input via
state_0 = p.factory.blank_state(addr= 0x4011ea,stdin=flag)
- Exploration en fonction de l’output et non pas en utilisant des adresses avec
sm.explore( find = is_output_good, avoid = is_output_bad)
Et au final, aucun hook n’a été nécessaire !
Résultat
1
2
3
4
5
6
7
8
9
10
11
12
13
WARNING | 2023-09-16 15:03:47,468 | angr.simos.simos | stdin is constrained to 8 bytes (has_end=True). If you are only providing the first 8 bytes instead of the entire stdin, please use stdin=SimFileStream(name='stdin', content=your_first_n_bytes, has_end=False).
[+] Exploration en Cours ....
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior.
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2023-09-16 15:03:47,479 | angr.storage.memory_mixins.default_filler_mixin | Filling register rbp with 8 unconstrained bytes referenced from 0x4011ea (PLT.__cxa_finalize+0x19a in exe (0x11ea))
[+] Détermination de l'input valide
[+] Le bon input est : USQ97531
Exercice 4️⃣ - Gestion des fichiers
Objectif
Le programme suivant lit depuis un fichier des données afin de les valider ou non. A vous de trouver le contenu adéquat grâce à angr !
Cet exercice vous permettra de comprendre le fonctionnement global des SimFiles
.
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
#include <stdio.h>
#include <stdint.h>
int main() {
FILE *file = fopen("mdp.bin", "rb");
if (file == NULL) {
perror("Erreur lors de l'ouverture du fichier");
return 1;
}
uint64_t win_value = 0xdeadbeefcafebabe;
uint64_t read_value;
// Lecture de 8 octets depuis le fichier
size_t bytes_read = fread(&read_value, 8, 1, file);
if (bytes_read != 1) {
perror("Erreur lors de la lecture du fichier");
fclose(file);
return 1;
}
// Fermeture du fichier
fclose(file);
if (read_value == win_value) {
printf("Win\n");
} else {
printf("Loose\n");
}
return 0;
}
Pour le compiler : gcc -no-pie main.c -o exe
. Indice : aucun hook n’est nécessaire pour la réussite de cet exercice ;) !
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import angr
import claripy
p = angr.Project("./exe")
data = claripy.BVS('mdp', 8 * 8)
simfile = angr.storage.SimFile("mdp.bin", content=data)
state = p.factory.entry_state(addr = 0x4011E9, fs={ "mdp.bin" : simfile})
sm = p.factory.simulation_manager(state)
sm.explore(find=0x4012b9, avoid=0x4012c7)
found = sm.found[0]
print("[+] Les données à utiliser sont : ",found.solver.eval(data, cast_to=bytes))
Commentaire
- On crée un
SimFile
contenant des données symboliques - On insère le
SimFile
dans l’état initial (son filesystem) avecfs={ "mdp.bin" : simfile}
- On explore jusqu’à arriver à destination
On aurait également pu utiliser l’output pour explorer au lieu d’utiliser des adresses en dur.
Résultat
1
[+] Les données à utiliser sont : b'\xbe\xba\xfe\xca\xef\xbe\xad\xde'