3.1 Introduction
3.2 The Specifications
3.3 Vulnerable Environment Preparation
3.3.1 Disabling the SELinux
3.3.2 Non-Executable Stack and Address Space Randomization
3.4 Preparing the Vulnerable Code
3.1 Introduction
The methodology used will be an experiment of escalating a normal user to root privilege by exploiting a buffer overflow that exists in a vulnerable program in a controlled environment. The flow of event during the demonstration will be analyzed and the analysis uses a reverse engineering technique. First of all a vulnerable environment will be created by disabling the SELinux and address space randomization. Using the Fedora 9 which was installed as a virtual guest OS on Windows XP Pro SP 2, a stack-based buffer overflow program will be created and exploited. The vulnerable code (bofvulcode.c) is a typical setuid type C program that is owned by root but executable by user which will have a strcpy() function that vulnerable to a stack-based buffer overflow. Without any bound checking mechanism implemented, implicitly or explicitly, the buffer declared in the program which will be used by strcpy() function will be over flown with more bytes than it can hold. The last extra bytes will be precisely overwriting the return address of the function and this address is pointing to the environment variable that contains the shellcode (eggcode.c). This shellcode will act as the payload which contains setuid(0) and spawning shell operations. Another simple code (findeggaddr.c) will be used to find the address of the shellcode stored in the environment variable. Briefly, the following flow of event will be demonstrated, observed, documented and analyzed.
3.2 The Specifications
The hardware and software specifications used in this controlled demonstration are listed below. Knowing the specifications is important because the compiled and exploit code should match the architecture, though the source code could be similar. |
1. Fedora 9 as guest OS on Win XP Pro SP2 (VMware 6.0.4).
2. Intel x86 platform.
3. Linux kernel 2.6.25-14.fc9.i686.
4. GNU C Library, glibc 2.8-3.i686.
5. GNU Compiler Collection, GCC 4.3.0 RedHat 4.3.0-8.i386.
6. GNU Debugger, gdb 6.8-1.fc9.i386.
7. GNU Assembler, as 2.18.50.0.6 (i386-redhat-linux).
8. Login as normal user (amad).
9. Using setuid program with strcpy() function as a vulnerable program.
10.It is a local exploit.
11.All the related debuginfo packages [57] were installed.
Take note that by using the Fedora 9 as a virtual machine, the 'reaction' of the OS against the buffer overflow for virtual machine can also be observed mainly from the memory management aspect.
There are already several general buffer overflow protections implemented on the Fedora 9. In this demonstration, the protections will be disabled in order to create a system wide vulnerable environment. Furthermore, by showing this, the current buffer overflow detection and prevention implementations can be explored. However this will be minimized in order to see only the related protections that are really in effect. Take note that protection mechanism is specific to this demonstration environment and others may be different. Some Linux distro for instance has its own patches for hardening the system which include the buffer overflow protection as can be found for Debian [58].
SELinux enforces mandatory access control policies on OS. The policies restrict and control user programs and system daemons (services) to just the minimum amount of privilege they need to complete their tasks.
Firstly, the SELinux [59] will be disabled to create a system wide vulnerable. In this case as root, SELinux will be disabled permanently by editing the /etc/sysconfig/selinux file and changing the SELINUX=permissive setting to SELINUX=disabled and then reboot the machine. The following Figure shows the selinux file content screen snapshot after the SELinux has been set to disabled.
Figure 3.1: Disabling the SELinux permanently by editing /etc/sysconfig/selinux file
Figure 3.2: Screenshot for /etc/sysconfig/selinux file content
Optionally, SELinux can be disabled temporarily by executing the setenforce 1 (Permissive) command. The getenforce command can be used to check the status. However to set it to disabled, the /etc/sysconfig/selinux file need to be edited manually. The following steps show the getenforce and setenforce command examples.
[root@localhost bin]# getenforce
Enforcing
[root@localhost bin]# setenforce 0
[root@localhost bin]# getenforce
Permissive
[root@localhost bin]# setenforce 1
[root@localhost bin]# getenforce
Enforcing
[root@localhost bin]#
The exec-shield that makes the stack non-executable will be disabled so that the injected shellcode in the stack can be run and the ASLR that provides the randomness to the layout of the virtual memory space will also be disabled in order to provide fixed addresses of the memory space. Take note that if the exploit does not depend on the address randomization, this step is 'useless'. In order to disable it temporarily do the following steps:
su -
Password: (your root password)
#/sbin/sysctl -w kernel.exec-shield=0
#/sbin/sysctl -w kernel.randomize_va_space=0
[root@localhost ~]# /sbin/sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
[root@localhost ~]# /sbin/sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@localhost ~]#
The exec-shield can be disabled permanently by editing the /etc/sysctl.conf file and adding the following lines. This task may need a system reboot.
kernel.exec-shield=0
kernel.randomize_va_space=0
Figure 3.3 shows the sysctl.conf file content screen snapshot.
Figure 3.3: Disabling exec-shield for address space randomization by editing the sysctl.conf file
The vulnerable program (bofvulcode.c) is shown below. This code contains strcpy() function which is a public knowledge, vulnerable to the stack-based buffer overflow if not properly used. strcpy() does not do the bound checking for the destination buffer while copying data.
This program will receive an input from the second command line argument, argv[1]. The mybuff buffer, which is the destination buffer of the strcpy() parameter will be over flown. Then, the strcpy() return address can be 'precisely' overwritten with an address which point to the new address. Hopefully after finish copying the string from the command line argument, the program execution flow will be re-directed to the new return address instead of returning to main().
[amad@localhost projectbof11]$ cat bofvulcode.c
/* bofvulcode.c */
#include <unistd.h>
int main(int argc, char **argv)
{
/* declare a buffer with max 512 bytes in size*/
char mybuff[512];
/* verify the input */
if(argc < 2)
{
printf("Usage: %s <string_input_expected>\n", argv[0]);
exit (0);
}
/* else if there is an input, copy the string into the buffer */
strcpy(mybuff, argv[1]);
/* display the buffer's content */
printf("Buffer's content: %s\n", mybuff);
return 0;
}
[amad@localhost projectbof11]$
Figure 3.4: The bofvulcode code screenshot
Without any null byte as string terminator within the 512 and/or the input is more than 512 bytes, or any bound checking mechanism implemented explicitly, the 512 bytes buffer can be over flown. The extra string still be stored in the strcpy()’s stack frame, however without any protection mechanism, this extra string will overwrite the adjacent data in the allocated buffer.
As declared in the program, the buffer (array) in the program is 512 bytes long. If there is a string input, the program will copy the argument into the buffer. In the following steps, the program is tested with several number of string inputs. In this case thePerl command was used to stuff "A" characters into the buffer (the Ruby and other similar commands also can be used).
[amad@localhost projectbof11]$ gcc -g -w bofvulcode.c -o bofvulcode
[amad@localhost projectbof11]$ ./bofvulcode `perl -e 'print "A"x508'`
Buffer's content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[amad@localhost projectbof11]$ ./bofvulcode `perl -e 'print "A"x512'`
Buffer's content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[amad@localhost projectbof11]$ ./bofvulcode `perl -e 'print "A"x516'`
Buffer's content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
[amad@localhost projectbof11]$
Figure 3.5: Running the bofvulcode program with three different inputs
The increment of 4 bytes were used in the example because of the word alignment of the memory for 32 bit system (4 bytes x 8 bits = 32 bits = 1 word). When the input is 516 bytes (4 bytes extra), the program exit with Segmentation fault.
By debugging the core dump file, what actually happened when inputting more than 512 bytes of string can be observed. Firstly, set the core dump file if needed. Then, re-run the program with more than 512 bytes of string input.
[amad@localhost projectbof11]$ ulimit -c unlimited
[amad@localhost projectbof11]$ ./bofvulcode `perl -e 'print "A"x516'`
Buffer's content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
[amad@localhost projectbof11]$
[amad@localhost projectbof11]$ ls -l
total 232
-rwxrwxr-x 1 amad amad 6536 2008-09-27 00:39 bofvulcode
-rwxr-xr-x 1 amad amad 470 2008-09-27 00:36 bofvulcode.c
-rw-r--r-- 1 amad amad 468 2008-09-27 00:28 bofvulcode.c~
-rw------- 1 amad amad 155648 2008-09-27 00:54 core.2986
-rwxr-xr-x 1 amad amad 640 2008-09-27 00:02 egg1.c
-rwxr-xr-x 1 amad amad 640 2008-09-27 00:02 egg1.c~
-rwxr-xr-x 1 amad amad 92 2008-09-27 00:02 eggfind.c
-rwxr-xr-x 1 amad amad 90 2008-09-27 00:02 eggfind.c~
-rwxr-xr-x 1 amad amad 1401 2008-09-27 00:02 info.txt
-rwxr-xr-x 1 amad amad 1417 2008-09-27 00:02 info.txt~
-rwxr-xr-x 1 amad amad 7311 2008-09-27 00:02 steps.txt
-rwxr-xr-x 1 amad amad 7311 2008-09-27 00:02 steps.txt~
-rwxr-xr-x 1 amad amad 528 2008-09-27 00:02 testshell.c
-rwxr-xr-x 1 amad amad 528 2008-09-27 00:02 testshell.c~
-rwxr-xr-x 1 amad amad 528 2008-09-27 00:02 testshellmercy.c
-rwxr-xr-x 1 amad amad 509 2008-09-27 00:02 testshellmercy.c~
-rwxr-xr-x 1 amad amad 95 2008-09-27 00:02 turnfullroot.c
Figure 3.6: Creating the core dumped file |
Next, use the gdb to view the core dump file (core.2986 in this case) and the content of the registers when the segmentation fault happened.
[amad@localhost projectbof11]$ gdb -c core.2986 bofvulcode
GNU gdb Fedora (6.8-1.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc-2.8.so...Reading symbols from /usr/lib/debug/lib/libc-2.8.so.debug...done.
done.
Loaded symbols for /lib/libc-2.8.so
Reading symbols from /lib/ld-2.8.so...Reading symbols from /usr/lib/debug/lib/ld-2.8.so.debug...done.
done.
Loaded symbols for /lib/ld-2.8.so
Core was generated by `./bofvulcode AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
[New process 2986]
#0 0x080484b6 in main (argc=Cannot access memory at address 0x41414141
) at bofvulcode.c:20
20 }
(gdb)
(gdb) info reg
eax 0x0 0
ecx 0x41414141 1094795585
edx 0x2950d0 2707664
ebx 0x293ff4 2703348
esp 0x4141413d 0x4141413d
ebp 0xbffff200 0xbffff200
esi 0x0 0
edi 0x8048370 134513520
eip 0x80484b6 0x80484b6 <main+146>
eflags 0x10286 [ PF SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)
Figure 3.7: The core dumped file content
Figure 3.8: Viewing the registers contents
Take note that from this paragraph and that follows must be cross referenced with the information discussed in section 2.5 and 2.6 for better understanding on the stack construction and destruction mechanisms.
The eip (instruction pointer) is pointing to (holding) the address of 0x41414141 ("AAAA"). This eip hold the return address, however, this is not a valid address anymore. To have the detail, the basic stack memory layout for x86 Linux is shown in the following Figure when the standard function call in C was invoked. This is important in determining the exact return address in the stack. This stack frame will be constructed when a function call was triggered. The frame is bounded by the stack frame pointer (ebp) at the bottom and the stack pointer (esp) at the top. The return address, pointed to or hold by the instruction pointer (eip = ebp + 4) will be pushed into the stack after the arguments. During the execution the stack frame may shrink and grow and after the function call completed, the return address will be used to return to the caller and program execution continues. Then, the stack frame will be destroyed, releasing the memory to the system for other use. A simple idea is, if the return address can be changed, the program execution flows also can be changed.
Figure 3.9: A typical stack frame layout for C function call
Referring to Figure 3.9, when the buffer was over flown by 4 more bytes after the ebp (ebp + 4), theeip that hold the return address can be overwritten. Let verify this statement by re-running the previous vulnerable program with 8 more bytes than the buffer can hold.
[amad@localhost projectbof11]$ ./bofvulcode `perl -e 'print "A"x520'`
Buffer's content: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
[amad@localhost projectbof11]$ ls -l
total 388
-rwxrwxr-x 1 amad amad 6536 2008-09-27 00:39 bofvulcode
-rwxr-xr-x 1 amad amad 470 2008-09-27 00:36 bofvulcode.c
-rw-r--r-- 1 amad amad 468 2008-09-27 00:28 bofvulcode.c~
-rw------- 1 amad amad 155648 2008-09-27 00:54 core.2986
-rw------- 1 amad amad 155648 2008-09-27 01:01 core.3000
-rwxr-xr-x 1 amad amad 640 2008-09-27 00:02 egg1.c
-rwxr-xr-x 1 amad amad 640 2008-09-27 00:02 egg1.c~
-rwxr-xr-x 1 amad amad 92 2008-09-27 00:02 eggfind.c
-rwxr-xr-x 1 amad amad 90 2008-09-27 00:02 eggfind.c~
[Trimmed]
-rwxr-xr-x 1 amad amad 528 2008-09-27 00:02 testshellmercy.c
-rwxr-xr-x 1 amad amad 509 2008-09-27 00:02 testshellmercy.c~
-rwxr-xr-x 1 amad amad 95 2008-09-27 00:02 turnfullroot.c
[amad@localhost projectbof11]$ gdb -c core.3000 bofvulcode
GNU gdb Fedora (6.8-1.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc-2.8.so...Reading symbols from /usr/lib/debug/lib/libc-2.8.so.debug...done.
done.
Loaded symbols for /lib/libc-2.8.so
Reading symbols from /lib/ld-2.8.so...Reading symbols from /usr/lib/debug/lib/ld-2.8.so.debug...done.
done.
Loaded symbols for /lib/ld-2.8.so
Core was generated by `./bofvulcode AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
[New process 3000]
#0 0x080484b6 in main (argc=Cannot access memory at address 0x41413f39
) at bofvulcode.c:20
20 }
(gdb) info reg
eax 0x0 0
ecx 0x41414141 1094795585
edx 0x2950d0 2707664
ebx 0x293ff4 2703348
esp 0x4141413d 0x4141413d
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x8048370 134513520
eip 0x80484b6 0x80484b6 <main+146>
eflags 0x10286 [ PF SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)
Figure 3.10: Generating the core dumped file
Figure 3.11: The core.3000 file content
Figure 3.12: Viewing the registers contents
Viewing the dump core file content, with 8 more bytes, the ebp was completely and the eip was partially overwritten. The partially overwritten eip happened because of the stack boundary alignment. This issue will be resolved later and other related information that should be read together was discussed in section 2.5.3.2.
Well, if this return address can be overwritten, then the program’s flow can be controlled and pointed to other desired address. In this case, the eip was overwritten by "A" characters which does not represent a valid address where the program can continue execution. This is why the segmentation fault was generated.
With this knowledge, an address that would point to a new code and start a new program execution (the malicious one!) can be supplied. Figure 3.13 shows some of the creative ways that have been used and re-used in pointing the return address. In this demonstration, the environment variable will be used to store the shellcode.
Figure 3.13: Overwriting the return address and pointing to other location