Buffer-Overflow
This is a report about SEED Software Security lab, Buffer Overflow Vulnerability Lab.
Written by Simon Nie.
The main knowledge involved:
• Buffer overflow vulnerability and attack
• Stack layout in a function invocation
• Shell code
• Address randomization
• Non-executable stack
• Stack Guard
Table of Contents
- Preparation: Some Useful Knowledge
- Task 1: Running Shell code
- Task 2: Exploiting the Vulnerability
- Task 3: Defeating dash’s Countermeasure
- Task 4: Defeating Address Randomization
- Task 5: Turn on the Stack Guard Protection
- Task 6: Turn on the Non-executable Stack Protection
- Reference
Preparation: Some Useful Knowledge
1. Memory Layout:
1 | | Stack |High address |
- Global and static variable are stored in Data Segment or BSS Segment, if the value is initialized, it will be put into Data Segment, otherwise it will be put into BSS Segment.
- BSS Segment, all value will be set Zero. OS will zero out everything in BSS Segment, that’s why all uninitialized variable will be set into zero.
- Local variable will be allocated on the stack. pointers will be allocated in the stack, but where it points at will be allocated on the heap.
2. Virtual Address vs Physical Address
- Every program has its own virtual address. Two different programs can have the same VA, but not the same PA, unless they can share memory.
3. Stack Layout
1 | | value of b |High address |
4. Frame Pointer and Function Call Chain
5. Copy Data to Buffer
strcpy(dst, src)
在拷贝的时候看见\0
的时候才会停止。
6. Turning Off Countermeasures
6.1 Address Space Randomization
Use command below to close address space randomization.
1 | sudo sysctl -w kernel.randomize_va_space=0 |
6.2 The Stack Guard Protection Scheme
Use command below to disable Stack Guard when compiling a program.
1 | gcc -fno-stack-protector example.c |
6.3 Non-Executable Stack
Use command below to choose setting your program can be or not be executable on stack.
1 | For executable stack: |
6.4 Configuring/bin/sh(Ubuntu 16.04 VM only)
Before the tasks, we need to configure /bin/sh in our virtual machine.
1 | sudo rm /bin/sh$ sudo ln -s /bin/zsh /bin/sh |
Task 1: Running Shell code
Shell Code:
1 |
|
Our target is inject this shell code into the stack, and let the program run these part of code. For convenience, we have been offered the assembly version of code above. We get a c program below:
1 | /*call_shellcode.c*/ |
Before compiling the program, you should do what have been showed in 6.1 and 6.4 above.
Compile the code using following command:
1 | gcc -z execstack -o call_shellcode call_shellcode.c |
Then you can run call_shellcode to learn about this.
Task 2: Exploiting the Vulnerability
In this task, we will exploit the vulnerability of strcpy(dest, src)
, it has a drawback as we introduced in 5, Preparation Part. It can lead a buffer overflow vulnerability.
So we need a program that holds such vulnerability:
1 | /*Vunlerable program: stack.c*/ |
See this program, it does own potential risk of buffer overflow, but to exploit this vulnerability, we need to do some work first:
- compile it with
-fno-stack-protector
and-z execstack
to turn off the StackGuard and the non-executable stack protections. - make it a root-owned Set-UID program.
- Change the permission of this program.
You can finish your work by commands below:
1 | gcc -o stack -z execstack -fno-stack-protector stack.c |
So now we have a bomb, next we need to inject our dirty code into the bomb so that it will be triggered automatically.
The core idea is:
- inject dirty code into stack.
- find the position in which the return address is stored.
- modify the value in the position so that the next instruction it supposed to execute will be leading to our injected dirty code.
To inject the dirty code, we are offered a program again:
1 | /*exploit.c*/ |
It’s easy to understand what the program do: generate a badfile
for stack
program we have compiled before to use as its input file. And this exploit.c
program will do some interesting things in badfile
to make it a read ‘bad file’.
What we need do is complete the exploit.c
file and let it generate ‘correct’(i mean real bad) file so that we can trig the bomb.
Solution 1
To complete the program, we need to use gdb
to analyze the stack of the stack
program.
1 | gdb stack |
so we get the address of str_start :0xbfffeb30
;
use disasemble bof
we can find
1 | disassemble bof |
before calling the bof()
, the program push the only parameter into stack(the str[] start address); and then push return address into stack; then coming into the bof
function; push old ebp into stack; ebp -> new ebp; esp -= 0x28; so the stack will looks like:
1 | | ... |high address |
so we can find that the position of return address = buffer start address + 0x24, that is where we need to modify.
So we complete the code:
1 | /* exploit.c */ |
we fill the shellcode starting at buffer_address + 0x100
, and fill the start_address(0xbfffeb30 + 0x100
) into buffer_address + 0x24
。
After doing this, compile and run.
1 | gcc -o exploit exploit.c |
Solution 2:
1 | unsigned long get_sp(){ |
In this solution we define a function unsigned long get_sp()
to get the esp value, it’s equal to what we do by using gdb.
Question Unsolved:
When i tried to compare two solutions above, i found that:
- In solution 2, substitute
memcpy()
function withstrcpy()
function, it did work on my PC but didn’t work in PC in BZ306 (Received a Segmentation fault). I don’t know why. - When i try to turn on the address randomization, the solution 2 doesn’t work. I expected it work but it did not.
Maybe i need help.
Task 3: Defeating dash’s Countermeasure
The dash shell in Ubuntu 16.04 drops privileges when it detects that the effective UID does not equal to the real UID. This can be observed from dash program’s change log. We can see an additional check below, which compares real and effective user/group IDs.
1 | // https://launchpadlibrarian.net/240241543/dash_0.5.8-2.1ubuntu2.diff.gz |
The countermeasure can be defeated. One approach is not to invoke /bin/sh
in our shell code; instead, we can invoke another shell program. This approach requires another shell program,such as zsh
to be present in the system. Another approach is to change the real user ID of the victim process to zero before invoking the dash
program. We can achieve this by invoking setuid(0)
before executing execve()
in the shell code. In this task, we will use this approach. We will first change the /bin/sh
symbolic link, so it points back to/bin/dash:
1 | sudo ln -sf /bin/dash /bin/sh |
After that, create a new program:
1 | // dash_shell_test.c |
The above program can be compiled and set up using the following commands.
1 | gcc dash_shell_test.c -o dash_shell_test |
We need to compile it twice, one with setuid(0);
commented and one with not commented. And we can find the difference:
We can find that with setuid(0);
not commented, the identity changed from SEED to ROOT.
For further thinking, we will inject new shell code(similar to task 2) into stack and invoking dash as root. we need change the shell code in exploit.c
into following code:
1 | char shellcode[] = |
The updated shell code adds 4 instructions:
(1) set ebx
to zero.
(2) set eax
to 0xd5
(0xd5
is setuid()
‘s call number).
(3) execute the system call.
Using this shell code, we can attack on the vulnerable program when /bin/bash
is linked to /bin/dash
. Use the above shell code in exploit.c
and try the attack from Task 2 again, see the difference:
From the result we can see the user has been changed from SEED to ROOT.
Task 4: Defeating Address Randomization
On 32-bit Linux machines, stacks only have 19 bits of entropy, which means that the stack base address can have 2^19 = 524,288 possibilities. This number is not high and can be exhausted easily with brute-force approach. So now we gonna try it.
First let’s open the address randomization:
1 | sudo /sbin/sysctl -w kernel.randomize_va_space=2 |
Then we try the same attack in Task 2, we can find it failed.
Now let’s use a shell script to do this for many times.
1 |
|
After trying 244386 times, we succeed.
Task 5: Turn on the Stack Guard Protection
Before working on this task, we need to turn off the address randomization.
1 | sudo sysctl -w kernel.randomize_va_space=0 |
Recompile the stack.c
program without -fno-stack-protector
option. Then execute task 2 again, we find:
The program was terminated due to stack smashing detected. This is because without the stack guard protection, the stack overflowed.
Actually, in GCC version 4.3.3 and above, Stack Guard is enabled by default. But in early versions, it was disabled by default.
Task 6: Turn on the Non-executable Stack Protection
Again, before working on this task, turn off the address randomization. In this stack, we recompile our vulnerable program using the noexecstack
option and retry the attack in Task 2. Use command below:
1 | gcc -o stack -fno-stack-protector -z noexecstack stack.c |
Then we find,
It met a segmentation fault. That is because with the option noexecstack
, the shell code in the stack cannot be execute. So the program cannot go on when it points at the shell code.
Reference
[1] SEED Project
[2] Wikipedia