Edward J. Schwartz * , JongHyup Lee ✝, Maverick Woo * , and David Brumley *
Format String Protection David Brumley Sam Wu June 12 th, 2002.
-
date post
21-Dec-2015 -
Category
Documents
-
view
216 -
download
0
Transcript of Format String Protection David Brumley Sam Wu June 12 th, 2002.
Format String Attack Basis Similar to buffer overflow attacks, it
relies on altering flow of control to attacking code
Unlike buffer overflow attacks, it takes advantage of C variable argument macros e.g. printf(“%d %n”, a, b) vs
printf(“%d %n”, a)
Expected Contributions of our proposal
A Metric: Previous work has been ad-hoc, and does not generalize
well. General Variatic Solutions:
We propose a generalized solution to dealing with variatic arguments that rely upon ideas first investigated by StackGuard.
Application Specific Protection: We detail statically inserting dynamic checks into format
string specifiers. Thus, coders who don’t want to pay the price of full pointer safety can pay a smaller price for a different safety guarantee.
Actual arguments
Saved IP for main
Saved FP for main
a
Foo’s frame:
Actual arguments
Custom IP (“/bin/sh”)
Saved FP for main
a
Metric: Different Levels of Attacks Level 1: Common
misuse of printf like functions to rewrite return address or frame pointer
e.g. a simplified versionfoo() {
int a;printf(“%d %d %n”);
}main() {
foo();}
-Also possible: double return, multiple returns attack
Guarding Level 1 Attacks Statically:
Type inferencing: Tainted/untainted
analysis Dynamically:
StackGuard: protect the saved
IP and FP from being overwritten by using a random canary
However…….
a
Actual arguments
Saved IP
Canary
Saved FP
with StackGuard…
“Hello World”
Actual arguments
Saved IP
Saved FP
(buf2[5])
“%d %n”
Actual arguments
Saved IP
Saved FP
“abcde”
“%d %n”
Actual arguments
Crafted IP
Saved FP
“abcde”
Level 2 Attacks Also called a “hybrid
attack” Using buffer overflow to
taint the format string specifier
e.g.int main(int argc, char argv[])
{char buf1[] = “Hello World”;char buf2[5];strcpy(buf2, argv[1]);printf(buf1);
}Tainted/untainted analysis says fine
How StackGuard fails… Consider just a
simple format string: printf(“%d %d
%d %n”, a, b, c);
Canary
Actual arguments
Saved IP
Saved FP
int
In theory, you can have any number of % directivesthat eventually would lead you to write anabitrary location on the stack frame…
Canary
Actual arguments
Crafted IP
Saved FP
int
Guarding Level 2 Attacks LibSafe:
Intercepts most library calls and makes sure all buffer writes are within frame bounds
e.g. strcpy, strcat, getwd, gets, fscanf, scanf, realpath, sprintf…
LibVerify: Wraps all functions such
that the integrity of the return address is checked upon function entry and exit
argc, argv[]
Saved IP
Saved FP
Locals for Main
Actual Arguments
IP for Main
FP for Main
Locals for Foo
main:
foo:LibSafe:Writable area
Locals for Main
Actual Arguments
LibVerify:Check integrity
IP for Main
FP for Main
Still yet….
p
argv[]
Saved IP
Saved FP
(buf[30])
p’
argv[]
Saved IP
Saved FP
(buf[30])
Level 3 Attacks An attacker can
construct an arbitrary pointer during run-time
e.g.int func(char argv[]) {
char *p;char buf[30];p = buf;printf(arg[1]);strncpy(p, arg[2], 29);
}
-can set p’ points to the global offset table (GOT) and change printf() to system(), so executing printf(“/bin/sh”) becomes system(“/bin/sh”)
-demonstrates the ability to alter program control flow to an arbitrary execution point
Guarding Level 3 Attacks
However, in the context of dynamic format string, which allows overwriting of (almost) any arbitrary memory location, point-to analysis cannot conclude anything, since any pointer could be rewritten to point to any other data in memory
Bottom line: Dynamic checking is necessary for ensuring
security integrity
Requires full inter-procedural point-to analysis
A Possible Exploit (where LibSafe failed…)
void foo(int c, char *s, …) {int i;char *j;va_list va;
va_start(va, s);j = va_arg(va, char*);printf(“j: %#x @ %#x\n”, *j, j);printf(“abcd: %d%n\n”, 2, j);va_end(va);
}
int main(int argc, char **argv) {int x=0;printf(“before x: %#x @ %#x\n”, x, &x);foo(4, argv[1]);printf(“after x: %#x @ %#x\n”, x, &x);return 0;
}Running yields:[root]./a.outbefore x: 0 @ 0xbffffa04j: 0 @ 0xbffffa04abcd: 2after x: 0x7 @ 0xbffffa04
A Possible Exploit (cont’d)void foo(int c, char *s, …) {
int i;char *j;va_list va;va_start(va, s);j = va_arg(va, char*);printf(“j: %#x @ %#x\n”, *j, j);printf(“abcd: %d%n\n”, 2, j);va_end(va);
}
int main(int argc, char **argv) {int x=0;printf(“before x: %#x @ %#x\n”, x, &x);foo(4, argv[1]);printf(“after x: %#x @ %#x\n”, x, &x);return 0;
}
argc, **argv
Saved IP
Saved FP
x = 0
main:
s = &(argv[1])
c = 4
IP for Main
FP for Main
foo:
Locals for Foo
argc, **argv
Saved IP
Saved FP
x = 7
main:
va
Locals for Foo
j
Recall: the va_list mechanism Recall (as defined in stdarg.h):
void va_start(va_list ap, arg) Sets up ap to point to first variatic argument (aka.
anonymous arguments) placed upon the stack type va_arg(va_list ap, type)
Returns current values pointed to by ap, then advanc ap by sizeof(type)
void va_end(va_list ap) Cleans up ap
This mechanism allows one function body to work with multiple arguments without code duplication.
Our Proposal
Our motivations and goals: To fix variatic functions such that the program is able
to determine at run time where the end of the variatic arguments actually are.
Finer grained than libsafe because it protects against local variables that may be involved in a level 3 attack (recall previous exploit)
Provide a richer functionality than FormatGuard by eliminating attacks in functions that take va_list
Previous work: Has not solved the generic variatic argument and
va_list problem; they instead focused on eliminating format string exploits found in practice
Three potential solutions…
Canary Protection Canary protection:
Push onto the stack a canary value before pushing the function actuals onto the stack. Thus function arguments are guarded up the stack by the canary, and down the stack by the start of the anonymous arguments (i.e. what returned by va_start)
Since both values are known at compile time, the va_arg macro can be modified to insert a dynamic check to make sure all accesses are within these bounds
Canary Protection (cont’d) Example pseudo code:
#define va_arg(x, type) tmp = real_va_arg(x, type)
if (tmp == canary) {set perm errorreturn error
}else
return tmp
Locals
Canary
Actual arguments
Saved FP
Saved IP
Cons :the false canary problem:two pointers check per va_arg
Pros :access via format string (va_list ultimately) is guarded to only the actuals
Add total size of variatic args Redefine variatic arguments to include a total
size of arguments passed to the function as the first paramter
For va_start, this means also returns the total number of bytes in the va_arg
Add total size of variatic args (cont’d)
Pseudo code:#define va_start(x, y) x = va_list(y); va_arg(y);
$non_anonymous_args
3
2
$total_size
1
Pros :allows precise bounding of the anonymous argument total size :no false canary problem
Cons :recompilation of source, as stack offset is now changed
Reorder the stack Change function invocation to push the
functions actuals onto the stack after the saved IP and FP
Pros :one check per va_arg access
Cons :does not protect local variables from misuse
Plans of Actions Require
a protected libc implementation a compiler that emits the proper code
for the bounds check; which we believe to be a medium-hard fix to gcc
a set of tests indicating the performance difference between the three approaches
Related Work Type Qualifiers (e.g. tainted/untainted static analysis)
Detecting possible flow of tainted buffer to supposedly untainted buffer
Require manual annotations and access to source code FormatGuard
Statically insert code to check dynamically number of % directives with the number of actual arguments to printf functions
Does not support vprintf like functions (or functions that use va_list), does not handle pointer to printf functions, does not check type of % directives
Metal Static analysis to find bugs in program Unsound and incomplete: finding bugs is important to security
applications, but is orthogonal to insuring security safety
Related Work (cont’d) StackGuard
Ensure return address integrity by using a random canary Run time overhead and require access to source code, and
possibly recompilation LibSafe
Intercepting and redirecting common string functions to safe versions, thus enforcing buffer write within stack frame bound
Does not prevent the exploit previously presented Fails to protect programs compiled with -fomit-frame-pointer or -
static, functions not defined in libsafe, kernel code, programs that do not have return address followed by frame pointer
LibVerify Wrap all functions and compare return address on function entry
and exit to ensure integrity Require duplication for each function defined (modified function
stored on heap)