Exercise 1: Optimise

Question 1

Optimise 1

gcc -o bin/optimise1 src/optimise.c
./bin/optimise1
## a+b : 12 
## no detected error 
## a+b : -2147483647 
## overflow error

The overflow is detected

Optimise 2

gcc -O2 -o bin/optimise2 src/optimise.c
./bin/optimise2
## a+b : 12 
## no detected error 
## a+b : -2147483647 
## no detected error

The overflow is not detected

Optimise 3

gcc -fno-strict-overflow -o bin/optimise3 src/optimise.c
./bin/optimise3
## a+b : 12 
## no detected error 
## a+b : -2147483647 
## overflow error

The overflow is detected

Optimise 4

gcc -O2 -fno-strict-overflow -o bin/optimise4 src/optimise.c
./bin/optimise4
## a+b : 12 
## no detected error 
## a+b : -2147483647 
## overflow error

The overflow is detected

Conclusion

When only compiling with the -O2 flag, the compiler does not ensure that there is no overflow.

The -O2 flag enables the -fstrict-overflow flag which makes the compiler assume that overflows are impossible.

This results in the compiler dropping all the checks of overflow.

Adding the -fno-strict-overflow counters the fact that -O2 enables -fstrict-overflow.

Assembly Code Comparison

When optimised with -O2, the addition of a and b is:

lea ebx, [rdi+rsi*1]

where rdi and rsi contain the values of a and b.

When the -O2 flag is not present, the addition uses the add instruction.

The lea does not trigger the overflow flag (OF) from the ALU where add will.

This can be obversed if we run the command info registers eflags in gdb after the lea or add instruction.

Question 2

We can check for overflows by doing the following:

int sum;
if ((b > 0) && (a > (INT_MAX - b))) {
    printf("Would produce overflow\n");
    return (-1);
} else {
    sum = a + b;
}
printf("a+b : %i \n", sum);

To be sure, we could use the -ftrapv flag which would trap if there is an overflow.

gcc -O2 -ftrapv -o bin/optimise_trap src/optimise.c

Exercise 2: WinLoose

gcc -fno-stack-protector -o bin/winloose src/winloose.c

Local Variable Positions in the Stack

Expected -fno-stack-protector -fstack-protector
Top of the stack Top of the stack Top of the stack
i t1 x
t1 x i
x i t1
Bottom of the stack Bottom of the stack Bottom of the stack

It is thus possible to rewrite i and x by overflowing t1 when using the -fno-stack-protector flag.

How to win ?

In order to win (i.e. change the value of x), we need to overflow the buffer t1.

If we overflow the buffer by a single element (which is not zero), we will change the value of x.

The condition is:

argv[2] > 7

The following command leads to a win:

./bin/winloose 42 8
## You win !

We loose if argv[2] <= 7

./bin/winloose 42 7
## You loose ...

How to make an infinite loop ?

To make an infinite loop, we need to overwrite i.

So when we overflow the buffer, we will rewrite the first 8 bits of i with the value of argv[1] which is a signed char.

The loop will stop if i - 1 >= argv[2].

But i == argv[1].

To overflow the buffer, argv[2] > 7

Thus the loop is infinite if

argv[1] <= 7 < argv[2]

One example:

./bin/winloose 8 9

-fstack-protector

gcc -fstack-protector -o bin/winloose_protect src/winloose.c

It is now impossible to overflow the buffer and thus to win or to do an infinite loop…

The compiler added some stack overflow protection (canaries).

At run time it will check if this new word in the stack has been overwriten. If it has been overwriten, then it crashes. The program stops its execution and prints the stack.

Exercise 3: isPasswordOK ?

gets behaviour

It reads stdin until it receives the end-of-file character.

It can lead to buffer overflows as it does not allow to specify the maximum size of the read on stdin.

Attack

gcc -o bin/isPasswordOK src/isPasswordOK.c
## src/isPasswordOK.c: In function ‘IsPasswordOK’:
## src/isPasswordOK.c:7:5: warning: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
##      gets(Password);
##      ^~~~
##      fgets
## /tmp/ccN1VpOv.o: In function `IsPasswordOK':
## isPasswordOK.c:(.text+0x15): warning: the `gets' function is dangerous and should not be used.

The compiler even tells us that the function gets is unsecure…

As CTRL-D produces a end-of-file char, if we use the following inputs we will be able to log in without having to write the password twice

$ ./isPasswordOK
goodpass
CTRL-D
OK
OK

This works because after the first time we enter the password, the buffer Password now contains the actual password.

Pressing CTRL-D just tells the gets function to terminate, and does not overwrite the content of the buffer Password.

Correcting the program

One way to prevent this would be to reset the values in the Password buffer.

We could also make Password point to NULL once the comparison has been done.

fgets instead of gets

Using fgets still makes it possible to use the previous attack.

Indeed, CTRL-D makes fgets terminate, but do not overwrite the Password buffer.

Exercise 4: ExecShell

gcc -o bin/exec_shell src/exec_shell.c

This program can be attacked in two ways

Use after free

Description

If the length of the directory given as argument is greater than 16, then p is freed and we return.

However, after the function terminated, a new call malloc is made.

It will start allocating at the same address as for p.

So, when we type the log message into p3, we are writing some bytes at the same location where p was before being free.

When the call to system(p) is executed, the value of p is still the start of the allocated memory, which now contains the log message.

Thus system(p) will interprete the log message that we wrote as a bash command, and execute it.

Example

This is a scenario to start a shell using the use after free.

$ ./bin/exec_shell ~/Documents/Folder1/SubFolder2
filename too long!
Error ! Enter your message (<24 characters)
/bin/sh
sh-4.2$

How to fix it ?

We could make p point to NULL after the free.

Code Injection

Description

The program actually takes the argument given by the user, concatenates it with the string “ls” and run the command represented by the resulting string.

However, we can give several commands to execute with this principle.

Example

./bin/exec_shell .;/bin/sh

We can of course use any command after the ;.

The only constraint is that the argument has less than 16 characters.

How to fix it ?

We could black list the ; character.