In this tutorial we are going to investigate the way in which a number of Unix system calls work. This will allow you to more clearly understand the security implications mentioned in the lectures. Additionally, we'll walk through a buffer overflow example and look at the way in which it works in detail.
In all cases you should carefully examine the source code and make sure that you can understand how it works!
This example demonstrates how the fork(2)
system call functions,
also making use of wait(2)
. The source code is available here -
fork.c
gcc -o fork fork.c
./fork
The fork(2)
system call will result in a second identical process
being cloned, one being the parent, the other the child. Run
ps xa | grep fork
[1]
to observe both processes, after running the binary.
The parent process wait(2)
's for the child to complete, hence both
processes will terminate at the "same time"[2].
This example demonstrates how the execl(2)
system call can be
used to invoke another binary and replace the process that is currently
executing. This example builds on the previous fork code,
with the child process executing /bin/date
. The code can be found
here - exec.c
$ gcc -o exec exec.c
$ ./exec
A second process will be spawned, which is then replaced with the
/bin/date
binary. Note that the code after the
execl(2)
system call is never executed, since the entire
process image is replaced with the new binary. Try modifying the
execl(2)
code so that it attempts to execute a non-existent
file (or one that is not executable). What happens?
This example demonstrates one of the potential gotchas with using
hardcoded filenames and not checking to see if the file already exists.
The code is available here - open.c
$ gcc -o open open.c
$ ./open
test
will have been created, made world
readable and writable, before being deleted (unlinked).
test
which points to your file. For example:
touch passwd
ln -s passwd test
$ chmod 600 passwd
$ ls -l passwd test
This example demonstrates the way in which real and effective UIDs
impact on code execution. For more information read the man pages for
setuid(2)
. The code is available here - rs.c
. Please note that you will
need a system on which you are permitted to have root access (eg. your
own!).
$ gcc -o rs rs.c
$ su root
$ chown root rs
$ chmod 4711 rs
$ ./rs
id(1)
or whoami(1)
system tools might help you figure things out...
PS. It would be a sensible idea to remove the binary (or at least the setuid permissions) once you've finished playing!
Okay, now it is time to investigate an effective buffer overflow.
Grab the source code from here -
overflow.c
-g
option
to gcc
):
$ gcc -g -o overflow overflow.c
$ gdb overflow
dumbFunction
function:
(gdb) break dumbFunction
dumbFunction
:
(gdb) run
(gdb) info frame
Stack level 0, frame at 0xbfef85d0:
eip = 0x80483ba in dumbFunction (overflow.c:15); saved eip 0x8048436
x/4w 0xbfef85d0
0xbfef85d0
is the address of the stack frame, as
given by the info frame
command.
x/16w 0xbfef85a0
0x00000003
, 0x00000002
,
0x00000001
). The next word should be the return address,
followed by the frame pointer. The return address will match the
saved eip
value as displayed by the info frame
command.
print
command:
print &buffer
step
command to step execution of the program.
step
reinspect the stack and note any changes
that have occurred. You should be able to explain the changes (compare
the source code to the observed behaviour as you go).
quit
to exit GDB.
The program should print out 5
since increasing the return
address by seven bytes should skip the machine instructions that assigns
6
to the variable i
. You can view the assembly
code in question by running gcc -S -o overview.S overview.c
and inspecting the overview.S
file. Alternatively, run
disassemble main
at the GDB prompt. The latter will give
you the addresses/offsets of the machine instructions.
Couple of hints - gdb
has a help
command
and a reasonable man page. The code and examples given have been
designed with the Intel x86 (IA32) architecture in mind. It will work on
other architectures, however some changes may need to be made regarding
memory address offsets. You may also prefer to use a visual debugger -
DDD - a visual frontend
to GDB, is one such option and is installed on the machines in
B1.11.
1. The arguments to ps
vary between operating systems. Linux
and Mac OS typically use ps xa
whereas you may need to use
ps -edalf
on Solaris or IRIX. Consult man ps
if
in doubt.
2. Nothing happens at exactly the same time on a timeshared system, however it is close enough for a human observer.