\documentclass{article}
\usepackage{../style}
\usepackage{../langs}
\begin{document}
\section*{Handout 3 (Buffer Overflow Attacks)}
By far the most popular attack method on computers are buffer
overflow attacks or simple variations thereof. The popularity is
unfortunate because we nowadays have technology in place to prevent them
effectively. But these kind of attacks are still very relevant
even today since there are many legacy systems out there and
also many modern embedded systems do not take any precautions
to prevent such attacks.
To understand how buffer overflow attacks work, we have to have
a look at how computers work ``under the hood'' (on the
machine level) and also understand some aspects of the C/C++
programming language. This might not be everyday fare for
computer science students, but who said that criminal hackers
restrict themselves to everyday fare? Not to mention the
free-riding script-kiddies who use this technology without
even knowing what the underlying ideas are. If you want to be
a good security engineer who needs to defend such attacks,
then better you know the details.
For buffer overflow attacks to work, a number of innocent
design decisions, which are really benign on their own, need
to conspire against you. All these decisions were pretty much
taken at a time when there was no Internet: C was introduced
around 1973; the Internet TCP/IP protocol was standardised in
1982 by which time there were maybe 500 servers connected (and
all users were well-behaved, mostly academics); Intel's first
8086 CPUs arrived around 1977. So nobody of the
``forefathers'' can really be blamed, but as mentioned above
we should already be way beyond the point that buffer overflow
attacks are worth a thought. Unfortunately, this is far from
the truth. I let you ponder why?
One such ``benign'' design decision is how the memory is laid
out into different regions for each process.
\begin{center}
\begin{tikzpicture}[scale=0.7]
%\draw[step=1cm] (-3,-3) grid (3,3);
\draw[line width=1mm] (-2, -3) rectangle (2,3);
\draw[line width=1mm] (-2,1) -- (2,1);
\draw[line width=1mm] (-2,-1) -- (2,-1);
\draw (0,2) node {\large\tt text};
\draw (0,0) node {\large\tt heap};
\draw (0,-2) node {\large\tt stack};
\draw (-2.7,3) node[anchor=north east] {\tt\begin{tabular}{@{}l@{}}lower\\ address\end{tabular}};
\draw (-2.7,-3) node[anchor=south east] {\tt\begin{tabular}{@{}l@{}}higher\\ address\end{tabular}};
\draw[->, line width=1mm] (-2.5,3) -- (-2.5,-3);
\draw (2.7,-2) node[anchor=west] {\tt grows};
\draw (2.7,-3) node[anchor=south west] {\tt\footnotesize older};
\draw (2.7,-1) node[anchor=north west] {\tt\footnotesize newer};
\draw[|->, line width=1mm] (2.5,-3) -- (2.5,-1);
\end{tikzpicture}
\end{center}
\noindent The text region contains the program code (usually
this region is read-only). The heap stores all data the
programmer explicitly allocates. For us the most interesting
region is the stack, which contains data mostly associated
with the control flow of the program. Notice that the stack
grows from a higher addresses to lower addresses. That means
that older items on the stack will be stored behind, or after,
newer items. Let's look a bit closer what happens with the
stack when a program is running. Consider the following simple
C program.
\lstinputlisting[language=C]{../progs/example1.c}
\noindent The \code{main} function calls \code{foo} with three
arguments. \code{Foo} contains two (local) buffers. The
interesting point for us will be what will the stack loke
like after Line 3 has been executed? The answer is as follows:
\begin{center}
\begin{tikzpicture}[scale=0.65]
\draw[gray!20,fill=gray!20] (-5, 0) rectangle (-3,-1);
\draw[line width=1mm] (-5,-1.2) -- (-5,0.2);
\draw[line width=1mm] (-3,-1.2) -- (-3,0.2);
\draw (-4,-1) node[anchor=south] {\tt main};
\draw[line width=1mm] (-5,0) -- (-3,0);
\draw[gray!20,fill=gray!20] (3, 0) rectangle (5,-1);
\draw[line width=1mm] (3,-1.2) -- (3,0.2);
\draw[line width=1mm] (5,-1.2) -- (5,0.2);
\draw (4,-1) node[anchor=south] {\tt main};
\draw[line width=1mm] (3,0) -- (5,0);
%\draw[step=1cm] (-3,-1) grid (3,8);
\draw[gray!20,fill=gray!20] (-1, 0) rectangle (1,-1);
\draw[line width=1mm] (-1,-1.2) -- (-1,7.4);
\draw[line width=1mm] ( 1,-1.2) -- ( 1,7.4);
\draw (0,-1) node[anchor=south] {\tt main};
\draw[line width=1mm] (-1,0) -- (1,0);
\draw (0,0) node[anchor=south] {\tt arg$_3$=3};
\draw[line width=1mm] (-1,1) -- (1,1);
\draw (0,1) node[anchor=south] {\tt arg$_2$=2};
\draw[line width=1mm] (-1,2) -- (1,2);
\draw (0,2) node[anchor=south] {\tt arg$_1$=1};
\draw[line width=1mm] (-1,3) -- (1,3);
\draw (0,3.1) node[anchor=south] {\tt ret};
\draw[line width=1mm] (-1,4) -- (1,4);
\draw (0,4) node[anchor=south] {\small\tt last sp};
\draw[line width=1mm] (-1,5) -- (1,5);
\draw (0,5) node[anchor=south] {\tt buf$_1$};
\draw[line width=1mm] (-1,6) -- (1,6);
\draw (0,6) node[anchor=south] {\tt buf$_2$};
\draw[line width=1mm] (-1,7) -- (1,7);
\draw[->,line width=0.5mm] (1,4.5) -- (1.8,4.5) -- (1.8, 0) -- (1.1,0);
\draw[->,line width=0.5mm] (1,3.5) -- (2.5,3.5);
\draw (2.6,3.1) node[anchor=south west] {\tt back to main()};
\end{tikzpicture}
\end{center}
\noindent On the left is the stack before \code{foo} is
called; on the right is the stack after \code{foo} finishes.
The function call to \code{foo} in Line 7 pushes the arguments
onto the stack in reverse order---shown in the middle.
Therefore first 3 then 2 and finally 1. Then it pushes the
return address to the stack where execution should resume once
\code{foo} has finished. The last stack pointer (\code{sp}) is
needed in order to clean up the stack to the last level---in
fact there is no cleaning involved, but just the top of the
stack will be set back. The two buffers are also on the stack,
because they are local data within \code{foo}. So in the
middle is a snapshot of the stack after Line 3 has been
executed. In case you are familiar with assembly instructions
you can also read off this behaviour from the machine
code that the \code{gcc} compiler generates for the program
above:\footnote{You can make \pcode{gcc} generate assembly
instructions if you call it with the \pcode{-S} option,
for example \pcode{gcc -S out in.c}\;. Or you can look
at this code by using the debugger. This will be explained
later.}.
\begin{center}\small
\begin{tabular}[t]{@{}c@{\hspace{8mm}}c@{}}
{\lstinputlisting[language={[x86masm]Assembler},
morekeywords={movl},xleftmargin=5mm]
{../progs/example1a.s}} &
{\lstinputlisting[language={[x86masm]Assembler},
morekeywords={movl,movw},xleftmargin=5mm]
{../progs/example1b.s}}
\end{tabular}
\end{center}
\noindent On the left you can see how the function
\pcode{main} prepares in Lines 2 to 7 the stack, before
calling the function \pcode{foo}. You can see that the
numbers 3, 2, 1 are stored on the stack (the register
\code{$esp} refers to the top of the stack). On the right
you can see how the function \pcode{foo} stores the two local
buffers onto the stack and initialises them with the given
data (Lines 2 to 9). Since there is no real computation
going on inside \pcode{foo} the function then just restores
the stack to its old state and crucially sets the return
address where the computation should resume (Line 9 in the
code on the left hand side). The instruction \code{ret} then
transfers control back to the function \pcode{main} to the
teh instruction just after the call, namely Line 9.
Another part of the ``conspiracy'' is that library functions
in C look typically as follows:
\begin{center}
\lstinputlisting[language=C,numbers=none]{../progs/app5.c}
\end{center}
\noindent This function copies data from a source \pcode{src}
to a destination \pcode{dst}. The important point is that it
copies the data until it reaches a zero-byte (\code{"\\0"}).
The central idea of the buffer overflow attack is to overwrite
the return address on the stack which states where the control
flow of the program should resume once the function at hand
has finished its computation. So if we have somewhere in a
function a local a buffer, say
\begin{center}
\code{char buf[8];}
\end{center}
\noindent
then the corresponding stack will look as follows
\begin{center}
\begin{tikzpicture}[scale=0.65]
%\draw[step=1cm] (-3,-1) grid (3,8);
\draw[gray!20,fill=gray!20] (-1, 0) rectangle (1,-1);
\draw[line width=1mm] (-1,-1.2) -- (-1,6.4);
\draw[line width=1mm] ( 1,-1.2) -- ( 1,6.4);
\draw (0,-1) node[anchor=south] {\tt main};
\draw[line width=1mm] (-1,0) -- (1,0);
\draw (0,0) node[anchor=south] {\tt arg$_3$=3};
\draw[line width=1mm] (-1,1) -- (1,1);
\draw (0,1) node[anchor=south] {\tt arg$_2$=2};
\draw[line width=1mm] (-1,2) -- (1,2);
\draw (0,2) node[anchor=south] {\tt arg$_1$=1};
\draw[line width=1mm] (-1,3) -- (1,3);
\draw (0,3.1) node[anchor=south] {\tt ret};
\draw[line width=1mm] (-1,4) -- (1,4);
\draw (0,4) node[anchor=south] {\small\tt last sp};
\draw[line width=1mm] (-1,5) -- (1,5);
\draw (0,5) node[anchor=south] {\tt buf};
\draw[line width=1mm] (-1,6) -- (1,6);
\draw (2,5.1) node[anchor=south] {\code{$esp}};
\draw[<-,line width=0.5mm] (1.1,6) -- (2.5,6);
\draw[->,line width=0.5mm] (1,4.5) -- (2.5,4.5);
\draw (2.6,4.1) node[anchor=south west] {\code{??}};
\draw[->,line width=0.5mm] (1,3.5) -- (2.5,3.5);
\draw (2.6,3.1) node[anchor=south west] {\tt jump to \code{\\x080483f4}};
\end{tikzpicture}
\end{center}
\noindent We need to fill this over its limit of
8 characters so that it overwrites the stack pointer
and then overwrites the return address. If, for example,
we want to jump to a specific address in memory, say,
\pcode{\\x080483f4} then we need to fill the
buffer for example as follows
\begin{center}
\code{char buf[8] = "AAAAAAAABBBB\\xf4\\x83\\x04\\x08";}
\end{center}
\noindent The first 8 \pcode{A}s fill the buffer to the rim;
the next four \pcode{B}s overwrite the stack pointer (with
what data we overwrite this part is usually not important);
then comes the address we want to jump to. Notice that we have
to give the address in the reverse order. All addresses on
Intel CPUs need to be given in this way. Since the string is
enclosed in double quotes, the C convention is that the string
internally will automatically be terminated by a zero-byte. If
the programmer uses functions like \pcode{strcpy} for filling
the buffer \pcode{buf}, then we can be sure it will overwrite
the stack in this manner---since it will copy everything up
to the zero-byte.
What the outcome of such an attack is can be illustrated with
the code shown in Figure~\ref{C2}. Under ``normal operation''
this program ask for a login-name and a password (both are
represented as strings). Both of which are stored in buffers
of length 8. The function \pcode{match} tests whether two such
strings are equal. If yes, then the function lets you in
(by printing \pcode{Welcome}). If not, it denies access
(by printing \pcode{Wrong identity}). The vulnerable function
is \code{get_line} in Lines 11 to 19. This function does not
take any precautions about the buffer of 8 characters being
filled beyond this 8-character-limit. The buffer overflow
can be triggered by inputing something, like \pcode{foo}, for
the login name and then the specially crafted string as
password:
\begin{center}
\code{AAAAAAAABBBB\\x2c\\x85\\x04\\x08\\n}
\end{center}
\noindent The address happens to be the one for the function
\pcode{welcome()}. This means even with this input (where the
login name and password clearly do not match) the program will
still print out \pcode{Welcome}. The only information we need
for this attack is to know where the function
\pcode{welcome()} starts in memory. This information can be
easily obtained by starting the program inside the debugger
and disassembling this function.
\begin{lstlisting}[numbers=none,language={[x86masm]Assembler},
morekeywords={movl,movw}]
$ gdb C2
GNU gdb (GDB) 7.2-ubuntu
(gdb) disassemble welcome
\end{lstlisting}
\noindent
The output will be something like this
\begin{lstlisting}[numbers=none,language={[x86masm]Assembler},
morekeywords={movl,movw}]
0x0804852c <+0>: push %ebp
0x0804852d <+1>: mov %esp,%ebp
0x0804852f <+3>: sub $0x4,%esp
0x08048532 <+6>: movl $0x8048690,(%esp)
0x08048539 <+13>: call 0x80483a4 <puts@plt>
0x0804853e <+18>: movl $0x0,(%esp)
0x08048545 <+25>: call 0x80483b4 <exit@plt>
\end{lstlisting}
\noindent indicating that the function \pcode{welcome()}
starts at address \pcode{0x0804852c}.
\begin{figure}[p]
\lstinputlisting[language=C]{../progs/C2.c}
\caption{A suspicious login implementation.\label{C2}}
\end{figure}
This kind of attack was very popular with commercial programs
that needed a key to be unlocked. Historically, hackers first
broke the rather weak encryption of these locking mechanisms.
After the encryption had been made stronger, hackers used
buffer overflow attacks as shown above to jump directly to
the part of the program that was intended to be only available
after the correct key was typed in by the user.
\subsection*{Paylods}
Unfortunately, much more harm can be caused by buffer overflow
attacks. This is achieved by injecting code that will be run
once the return address is appropriately modified. Typically
the code that will be injected is for running a shell. In
order to be send as part of the string that is overflowing the
buffer, we need the code to be encoded as a sequence of
characters
\lstinputlisting[language=C,numbers=none]{../progs/o1.c}
\noindent These characters represent the machine code
for opening a shell. It seems obtaining such a string
requires higher-education in the architecture of the
target system. But it is actually relatively simple: First
there are many ready-made strings available---just a quick
Google query away. Second, tools like the debugger can help
us again. We can just write the code we want in C, for
example this would be the program to start a shell
\lstinputlisting[language=C,numbers=none]{../progs/shell.c}
\noindent Once compiled, we can use the debugger to obtain
the machine code, or even the ready made encoding as character
sequence.
While easy, obtaining this string is not entirely trivial.
Remember the functions in C that copy or fill buffers work
such that they copy everything until the zero byte is reached.
Unfortunately the ``vanilla'' output from the debugger for the
shell-program will contain such zero bytes. So a
post-processing phase is needed to rewrite the machine code
such that it does not contain any zero bytes. This is like
some works of literature that have been rewritten so that the
letter 'i', for example, is avoided. For rewriting the machine
code you might need to use clever tricks like
\begin{lstlisting}[numbers=none,language={[x86masm]Assembler}]
xor %eax, %eax
\end{lstlisting}
\noindent This instruction does not contain any zero byte when
encoded, but produces a zero byte on the stack.
Having removed the zero bytes we can craft the string that
will be send to our target computer. It is typically of the
form
\begin{center}
\begin{tikzpicture}[scale=0.7]
\draw[line width=1mm] (-2, -1) rectangle (2,3);
\draw[line width=1mm] (-2,1.9) -- (2,1.9);
\draw (0,2.5) node {\large\tt shell code};
\draw[line width=1mm,fill=black] (0.3, -1) rectangle (2,-0.7);
\draw[->,line width=0.3mm] (1.05, -1) -- (1.05,-1.7) --
(-3,-1.7) -- (-3, 3.7) -- (-1.9, 3.7) -- (-1.9, 3.1);
\draw (-2, 3) node[anchor=north east] {\LARGE\tt "};
\draw ( 2,-0.9) node[anchor=west] {\LARGE\tt "};
\end{tikzpicture}
\end{center}
\bigskip\bigskip
\subsubsection*{A Crash-Course for GDB}
\begin{itemize}
\item \texttt{(l)ist n} -- listing the source file from line
\texttt{n}
\item \texttt{disassemble fun-name}
\item \texttt{run args} -- starts the program, potential
arguments can be given
\item \texttt{(b)reak line-number} -- set break point
\item \texttt{(c)ontinue} -- continue execution until next
breakpoint in a line number
\item \texttt{x/nxw addr} -- print out \texttt{n} words starting
from address \pcode{addr}, the address could be \code{$esp}
for looking at the content of the stack
\item \texttt{x/nxb addr} -- print out \texttt{n} bytes
\end{itemize}
\bigskip\bigskip \noindent If you want to know more about
buffer overflow attacks, the original Phrack article
``Smashing The Stack For Fun And Profit'' by Elias Levy (also
known as Aleph One) is an engaging read:
\begin{center}
\url{http://phrack.org/issues/49/14.html}
\end{center}
\noindent This is an article from 1996 and some parts are
not up-to-date anymore. The article called
``Smashing the Stack in 2010''
\begin{center}
\url{http://www.mgraziano.info/docs/stsi2010.pdf}
\end{center}
\noindent updates, as the name says, most information to 2010.
\end{document}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: t
%%% End: