This document provides an introduction to software exploitation on Linux 32-bit systems. It covers common exploitation techniques like buffer overflows, format strings, and ret2libc attacks. It discusses the Linux memory layout and stack structure. It explains buffer overflows on the stack and heap, and how to leverage them to alter control flow and execute arbitrary code. It also covers the format string vulnerability and how to leak information or write to arbitrary memory locations. Tools mentioned include GDB, exploit-exercises, and Python. Overall it serves as a crash course on the basic techniques and concepts for Linux exploitation.
3. What we will cover
We are covering software exploitation in
a Linux 32-bit environment
Topics
Buffer overflows (stack and heap)
Format string
Ret2libc
4. Tools
Virtualization software
VMware Workstation or VMware Player
(free)
Virtualbox (free)
Linux Environment
GDB
Editor (Vim, emacs, etc.)
Python
5. Exploit-Exercises - Protostar
Great learning environment
Stack, heap, format string, etc.
vulnerabilities
Can be downloaded and run from
VMware or Virtualbox
10. Common Register
Conventions
ESP – Stack Pointer
EBP – Stack Frame Base Pointer
EIP – Instruction Pointer
EAX, EBX, ECX, EXD – Fairly widely used,
often used as function arguments
11. Function Calls
The x86 instruction CALL is used, which
automatically does the following:
Put all the arguments onto the stack (right to left)
Put instruction pointer (EIP) onto stack
Now in the callee, push base pointer onto stack
and set base pointer to stack pointer (function
prologue)
Execute instructions in new function
Clean up locals
Set stack pointer to base and restore base
pointer by popping the stack (function epilogue)
12. Function Calls Visualized
Function A has a stack comprised of a
base, contents, and a top (stack pointer)
ESP
EBP
Stack
Contents A
Base A
13. Function Calls Visualized
Function A calls Function B, so pushes its
arguments and instruction pointer on the
stack
ESP
Stack
Contents A
Base A
EIP for A
Arguments
for B
EBP
14. Function Calls Visualized
Function B executes its prologue to set
up its own stack frame. First, push the
old EBP
EBP for A
Stack
Contents A
Base A
EIP for A
Arguments
for B
ESP
EBP
15. Function Calls Visualized
Now change EBP to ESP’s value. We
have a new stack frame
ESPEBPEBP for A
Stack
Contents A
Base A
EIP for A
Arguments
for B
16. Function Calls Visualized
Function B sets up its own locals
EBP
EBP for A
Stack
Contents A
Base A
EIP for A
Arguments
for B
Stack
Contents B
ESP
17. Function Calls Visualized
On finishing, function B first cleans up
locals
EBP for A
Stack
Contents A
Base A
EIP for A
Arguments
for B
ESPEBP
18. Function Calls Visualized
Next, it enters its function epilogue, in
which it restores the previous base
pointer
Stack
Contents A
Base A
EIP for A
Arguments
for B
EBP
ESP
19. Function Calls Visualized
Finally, the EIP is popped and execution
continues at its location
Stack
Contents A
Base A
Arguments
for B
EBP
ESP
22. Overview
Stack based buffer overflows occur when
writing goes past the boundaries of local
variables
Example:
char buf[64];
strcpy(buf, argv[1]);
The previous example will overflow buf’s
boundaries and write onto stack memory if
argv[1] contains more than 64 bytes
This allows us to alter local variables and also
change the execution of the program
23. Altering Code Execution
Flow
Since we can write over stack memory,
we can write over the saved EIP address
Stored EIP
Stored EBP
buf[64]
Paddin
g
New EIP
25. Stack Smashing
Now we want to achieve execution of
arbitrary code
Instead of giving EIP the address of another
function, we give it the address of our buffer
Our buffer contains machine code
instructions (known as shellcode)
For reliability, we use 0x90 opcodes to pad
before our shellcode, these are NOP
instructions and execution will slide past
them if it lands there
29. Overview
What happens if we’re working with a
slightly more modern machine with non-
executable stack memory?
Rather than write our own code to spawn
a shell, let’s use libc!
We will use system() for the
demonstration for simplicity, but bear in
mind that system drops privileges, so
execv() is needed for privilege escalation
34. Background
printf() and similar format string functions
such as sprintf() are used for string
interpolation in many languages including C
Intended use:
Give a string containing text and format flags
Format flags are replaced with arguments to
printf
Example:
printf(“%d %dn”, 3, 3+4); // Prints 3 7
35. What could go wrong?
How are user provided strings printed?
Good: printf(“%s”, str);
Bad: printf(str);
Why is this important? The user could
supply their own format flags and your
program will trust it.
GCC will give a very stern warning if you
try to do this
36. What can we do with this?
Giving lots of flags will mean that
variables will be pulled from the stack –
even if they weren’t passed to printf
This leads to information leakage.
Lets look at an example…
37. sf.c
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 0xDEADBEEF;
int b = 0xABCDABCD;
int c = 0x12345678;
printf(argv[1]);
printf("n");
return 0;
}
39. sf.c Exploited
There one of the local variables. Is there
an easier way?
./sf AAAA%5$x
Output: AAAAdeadbeef
./sf AAAA%6$x
Output: AAAAabcdabcd
40. It gets worse
One of the flags, %n, has the following effect:
The number of characters written so far is stored into
the integer indicated by the int * (or variant) pointer
argument. No argument is converted.
So now we can write where ever we want!
What’s the process?
First, put the address to write to in the payload
Increment the number of characters written using the
%x flag with a number. Ex: %10x prints ten spaces.
Call %n on the appropriate stack byte containing the
pointer
Repeat
41. Example with sf.c – Finding
offsets
Modified sf.c checks if a==c, so
0xabcdabcd needs to be replaced with
0xdeadbeef
First, find how many %x flags needed to
reach your payload on the stack.
With our python harness to keep
environment constant, we get 107
Looking in GDB gives an address of
0xbffffe58 for the the value 0xabcdabcd
42. Example with sf.c –
Calculations
The last byte should be 0xEF (239)
We’ve written 5 bytes (4 and the spam
one we’re crafting), so we want to write
234 more
Due to having to pad our string with As
to line the address up, our payload now
looks like:
x58xfexffxbfA%234x%108$n
Running this confirms that now there’s a
0x000000ef where 0xabcdabcd was
What now?
43. Example with sf.c – 2 at a
time
We could write the last byte, then the one
before, and so on. This takes four writes, so
it wastes shell code space. It clobbers the
preceding byte, but we probably don’t care
too much
Example:
00000000000000ef Wrote 0xEF
000000000000beef Wrote 0xBE
0000000000adbeef Wrote 0xAD
00000000deadbeef Wrote 0xDE
44. Example with sf.c – Using hc
Luckily, adding h in front of n writes a
16-bit integer instead
For 0xdeadbeef, that means two writes.
Increment by 48874, write the 0xbeef
half, then increment that by 8126 to
write the 0xdead half.
This all gets a bit messy and the
addresses will start to shift around
depending on the length of the exploit,
so use a script to pad it to the same
length every time.
45. Example with sf.c – Script
After having to tweak addresses again,
final payload building script:
payload = “x38xfexffxbfx3axfexffxbfA%48870x%108$hn%8126x
%109$hn”
payload += "A" * (40 - len(sploit))
print payload
Running it, we see in gdb that we were
successful, and we also get the victory
message.
46. Other nefarious uses
Overwrites return addresses
Overwrites GOT entries
Overwrite terminators to cause overflow
Write shellcode to non-stack memory
Leaking information to bypass ASLR
49. Heap Overview
We learned about the stack. Programs
store local variables there.
Heaps are for storing globals as well as
variables too large for the stack
In Linux:
.data – Initialized globals
.bss – Uninitialized globals
Heap – Dynamically allocated space, grows
upwards
Stack – Local variables, grows down
50. Memory Allocation Data
Structures
glibc uses a version of the popular dlmalloc algorithm
Memory is allocated in chunks. Each chunk is 8-byte aligned
with a header and data.
Each chunk contains:
Size information before and after the chunk – Easy to combine
chunks and allows bidirection traversal from any chunk. Trailer
fields are sometimes omitted in recent implementations
Chunks are stored in a linked list of bins, sorted by size in
continuous increments of 8 for sizes under 512. Over 512 can
be any multiple of 8.
Upon freeing a chunk, it is combined with freed neighbors to
lower fragmentation
The final “top” chunk is empty and its size records the
remaining about of free space
52. Some Consequences
Locality Preservation
Chunks allocated at the same time tend to be
referenced similarly and have coexistent lifetimes
Important for good performance, reduces cache
misses
A tweaked version of nearest-fit is used that results
in consecutive blocks when there is space
This is good for us
Easy to create overflows, as memory allocated after
vulnerable variables is often given to other variables
nearby that may be for things such as function
pointers or file paths
53. Malloc Example
We want to see what the heap looks like as more
memory is allocated
We will allocate three strings: a, b, c. Each is 32
bytes.
Code:
char *a, *b, *c;
a = malloc(32);
b = malloc(32);
c = malloc(32);
This will serve as an introduction to the heap3
problem
55. Second Allocation
0x0804c000
8 Byte Size – 0x29
0x0804c008
32 Byte Data
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
32 Byte Data
Chunk 2
b
Remaining Space: 0xFB1
56. Third Allocation
0x0804c000
8 Byte Size – 0x29
0x0804c008
32 Byte Data
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
32 Byte Data
Chunk 2
b
0x0804c050
8 Byte Size – 0x29
0x0804c058
32 Byte Data
Chunk 3
c
Remaining Space: 0xF89
57. Now, the interesting (and exploitable)
part involves the free() implementation
in dlmalloc
Let’s examine the contents of memory
after successive free() commands are
executed.
Code:
free(c);
free(b);
free(a);
Free Example
58. Before First Free
0x0804c000
8 Byte Size – 0x29
0x0804c008
“AAAA0”
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
“BBBB0”
Chunk 2
b
0x0804c050
8 Byte Size – 0x29
0x0804c058
“CCCC0”
Chunk 3
c
Remaining Space: 0xF89
63. Heap3
We know from Heap3’s description that
it “introduces the Doug Lea Malloc
(dlmalloc) and how heap meta data can
be modified to change program
execution.”
What metadata in particular?
Let’s review the process of freeing a
chunk from the last presentation…
64. Before First Free
0x0804c000
8 Byte Size – 0x29
0x0804c008
“AAAA0”
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
“BBBB0”
Chunk 2
b
0x0804c050
8 Byte Size – 0x29
0x0804c058
“CCCC0”
Chunk 3
c
Remaining Space: 0xF89
68. Free Algorithm on Chunk p
1. Load the chunk’s size as psize,
previous chunk is p-psize
2. A few scenarios can occur, but what
we’re interested in is the case in which
the surrounding chunk is also free
3. To consolidate, call:
unlink(nextchunk, bck, fwd)
70. Disassembled…
0x80498fa <free+214>: mov edx,DWORD PTR [ebp-0x18]
0x80498fd <free+217>: mov DWORD PTR [eax+0xc],edx
0x8049900 <free+220>: mov eax,DWORD PTR [ebp-0x18]
0x8049903 <free+223>: mov edx,DWORD PTR [ebp-0x14]
0x8049906 <free+226>: mov DWORD PTR [eax+0x8],edx
The underlined instructions correspond with
the previous two lines in unlink.
bk is 12 bytes offset, fd is 8 bytes offset.
71. Exploit Idea
We overwrite the size information for our
b pointer, so that when it tries to find the
previous memory chunk (previously c),
we point it elsewhere.
Giving it a negative size means it will go
forward to a “fake” previous chunk with
fd and bk of our choosing.
72. Crafting the Fake Chunk
Give a size of 0xFFFFFFFC = -4 to chunk
2
This pushes it forward 4 bytes, so it will
then look to b+16 for the bk pointer and
b+12 for the forward pointer.
Adjusting for header, that means fd and
bk will be 4 and 8 bytes into our body
respectively
chunk2 =
“BBBB”+word1+word2+”B”*20
73. What Now?
So now we can:
Load word1’s address into the memory at
location word2 + 8
Load word2’s address into the memory at
location word1 + 12
Actually pretty restrictive, as word1 and
word2 need to be able to serve as a
pointer to a writable region as well as
data to write
74. Ideas
One word is the address of winner() – 12,
the other is the stored EIP
Doesn’t work – We can’t write at winner() –
12
Overwrite the GOT entry for puts
Still doesn’t work. Any time we are trying
to put winner()’s address somewhere, we
can’t because that memory is not writable
75. Better Idea
One word will be a pointer to stack memory,
the other will be a pointer to the stored EIP
Both locations are writable
Our stack is executable, and our program
arguments are there. Let’s just overflow
chunk 3’s contents to store the payload.
Exploit strategy:
Hijack EIP and send it to the heap
Execute custom shellcode that goes to winner()
Victory!
76. Writing the Shellcode
All we want our shellcode to do is jump
to winner() and exit cleanly
There are some space considerations.
Because of the double write, one byte
gets clobbered after 16 bytes
Some trial-and-error shows that winner()
returns to a location that’s under ESP in
the stack as soon as our shellcode
begins execution
77. Payload Pseudocode
Pop the stack
Push original stored EIP
Push winner
Return
Why do we push original stored EIP? It’s
nice to avoid segfaulting when we can.
78. Payload ASM Code
POP eax
PUSH 0xb7eadc76
PUSH 0x8048864
RET
(Note: Your addresses may vary)
80. Slightly Nicer Shellcode
Something I use since originally giving
the Heap3 presentation is the 0xEB
opcode. It takes a single byte and jumps
that far ahead.
So EB 06 would skip ahead 1 byte (and a
half for the other half of this byte)
83. Future Reading
We didn’t cover any Windows exploitation. If
you want to learn the tools and techniques for
the Windows environment, check out the
Corelan tutorials at:
https://www.corelan.be/index.php/2009/07/19/ex
ploit-writing-tutorial-part-1-stack-based-over
flows
/
We covered Ret2libc, but the next step in
mitigation evasion, Return Oriented
Programming (ROP) wasn’t covered. It’s worth
it to read up on this, as it’s complex, but
extremely powerful.
84. Thanks for coming
Let me know if there are any questions
The next CSG Crash Course will cover
Cryptography and will be at 2pm on
September 22 in ECSS 2.415
Weekly meetings are 7pm on
Wednesdays in ECSS 4.619
Come join one of our teams for the
upcoming CSAW2012 Capture the Flag
competition and put these skills to use