Mobile Application Security: Security Testing
Buffer Overflows
The buffer overflow is one of the oldest and most well-known exploitable bugs in C. Although the iPhone has some built-in preventative measures to prevent buffer overflow exploitation, exploits resulting in code execution are still possible (see http://www.blackhat.com/presentations/bh-europe-09/Miller_Iozzo/BlackHat-Europe-2009-Miller-Iozzo-OSX-IPhone-Payloads-whitepaper.pdf). At their most basic level, buffer overflows occur when data is written into a fixed-size memory space, overflowing into the memory around the destination buffer. This gives an attacker control over the contents of process memory, potentially allowing for the insertion of hostile code. Traditionally, C functions such as strcat() and strcpy() are the APIs most often abused in this fashion. Sadly, these functions are still sometimes used in iPhone applications today.The simplest way for an Objective-C programmer to avoid buffer overflows is to avoid manual memory management entirely, and use Cocoa objects such as NSString for string manipulation. If C-style string manipulation is necessary, the strl family of functions should be used (seehttp://developer.apple.com/documentation/security/conceptual/SecureCodingGuide/Articles/BufferOverflows.html).Integer Overflows
An “integer overflow” occurs when a computed value is larger than the storage space it’s assigned to. This often happens in expressions used to compute the allocation size for an array of objects because the expression is of the form object_size × object_count. Listing 1 shows an example of how to overflow an integer.Listing 1. How to Overflow an Integer
int * x = malloc(sizeof (*x) * n);
for (i = 0; i < n; i++)
x[i] = 0;
sizeof (*x) * n
will be larger than 4 billion and will result in a smaller value than intended. This means the allocation size will be unexpectedly small. When the buffer is later accessed, some reads and writes will be performed past the end of the allocated length, even though they are within the expected limits of the array.It is possible to detect these integer overflows either as they occur or before they are allowed to occur by examining the result of the multiplication or by examining the arguments. Listing 2 shows an example of how to detect an integer overflow.Listing 2. Detecting an Integer Overflow
void *array_alloc(size_t count, size_t size) {
if (0 == count || MAX_UINT / count > size)
return (0);
return malloc(count * size);
}
It’s worth noting at this point that NSInteger will behave exactly the same way: It’s not even actually an object, but simply an Objective-C way to say “int.”Format String Attacks
Format string vulnerabilities are caused when the programmer fails to specify how user-supplied input should be formatted, thus allowing an attacker to specify their own format string. Apple’s NSString class does not have support for the “%n” format string, which allows for writing to the stack of the running program. However, there is still the threat of allowing an attacker to read from process memory or crash the program.NoteValid format strings for the iPhone OS can be found athttp://developer.apple.com/iphone/library/documentation/CoreFoundation/Conceptual/CFStrings/formatSpecifiers.html.
Listing 3 shows an example of passing user-supplied input to NSLog without using a proper format string.Listing 3. No Format Specifier Used
int main(int argc, char *argv[]) {
NSString * test = @"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x";
NSLog(test);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
[Session started at 2009-03-14 22:09:06 -0700.]
2009-03-14 22:09:08.874 DemoApp[2094:20b]
000070408fe0154b10000bffff00cbfffef842a4e1bfffef8cbfffef94bffff00c0
Whoops! Our user-supplied string resulted in memory contents being printed out in hexadecimal. Because we’re just logging this to the console, it isn’t too big a deal. However, in an application where this output would be exposed to a third party, we’d be in trouble. If we change our NSLog to format the user-supplied input as an Objective-C object (using the “%” format specifier), we can avoid this situation, as shown in Listing 4.Listing 4. Proper Use of Format Strings
int main(int argc, char *argv[]) {
NSString * test = @"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x";
NSLog(@"%@", test);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
NSLog makes for a good demo but isn’t going to be used that often in a real iPhone app (given that there’s no console to log to). Common NSString methods to watch out for are stringByAppendingFormat, initWithFormat, stringWithFormat, and so on.One thing to remember is that even when you’re using a method that emits NSString objects, you still must specify a format string. As an example, say we have a utility class that just takes an NSString and appends some user-supplied data:+ (NSString*) formatStuff:(NSString*)myString
{
myString = [myString stringByAppendingString:userSuppliedString];
return myString
}
NSString myStuff = @"Here is my stuff.";
myStuff = [myStuff stringByAppendingFormat:[UtilityClass
formatStuff:unformattedStuff.text]];
Even though we’re both passing in an NSString and receiving one in return, stringByAppendingFormat will still parse any format string characters contained within that NSString. The correct way to call this code would be as follows:NSString myStuff = @"Here is my stuff.";
myStuff = [myStuff stringByAppendingFormat:@"%@", [UtilityClass
formatStuff:unformattedStuff.text]];
When regular C primitives are used, format strings become an even more critical issue because the use of the “%n” format can allow for code execution. If you can, stick with NSString. Either way, remember that you, the programmer, must explicitly define a format string.Double-Frees
C and C++ applications can suffer from double-free bugs, where a segment of memory is already freed from use and an attempt is made to deallocate it again. Typically, this occurs by an extra use of the free() function, after a previously freed memory segment has been overwritten with attacker-supplied data. This results in attacker control of process execution. In its most benign form, this can simply result in a crash. Here’s an example:if (i >= 0) {
...
free(mystuff);
}
...
free(mystuff);
In Objective-C, we can run into a similar situation where an object allocated with an alloc is freed via the release method when it already has a retain count of 0. An example follows:id myRedCar = [[[NSString alloc] init] autorelease];
...
[myRedCar release];
Here, because myRedCar is autoreleased, it will be released after its first reference. Hence, the explicit release has nothing to release. This is a fairly common problem, especially when methods are used that return autoreleased objects. Just follow the usual development advice: If you create an object with an alloc, release it. And, of course, only release once. As an extra precaution, you may wish to set your object to nil after releasing it so that you can explicitly no longer send messages to it.NoteSee http://weblog.bignerdranch.com/?p=2 for more information on debugging retain counts. The majority of information here is applicable to both OS X and the iPhone.
Static Analysis
Most commercial static analysis tools haven’t matured to detect Objective-C-specific flaws, but simple free tools such as Flawfinder (http://dwheeler.com/flawfinder/) can be used to find C API abuses, such as the use of strcpy and statically sized buffers. Apple has documentation on implementing “static analysis,” but it seems to have misunderstood this to mean simply turning on compiler warnings (seehttp://developer.apple.com/TOOLS/xcode/staticanalysis.html).A more promising application is the Clang Static Analyzer tool, available at http://clang.llvm.org/StaticAnalysis.html. Like any static analysis tool, the clang analyzer has its share of false positives, but it can be quite useful for pointing out uninitialized values, memory leaks, and other flaws. To use this tool on iPhone projects, you’ll need to do the following:
- Go to Build. Change “Configuration” to “Debug.”
Consult the clang documentation for more details on interpreting the results. Of course, you should not assume that the use of a static analysis tool will find all or even most of the security or reliability flaws in an application; therefore, you should consider developing fuzzers for your program’s various inputs.The role of a fuzzer is to expose faults through violating program assumptions. For instance, given an application that parses an HTTP response and populates a buffer with its contents, over-long data or format strings can cause the program to fail in a potentially exploitable fashion. Similarly, integers retrieved via external sources could fail to account for negative numbers, thus leading to unexpected effects. You can craft fuzzers in the form of fake servers, fake clients, or programs that generate many test cases to be parsed by an application.
int * x = malloc(sizeof (*x) * n);
for (i = 0; i < n; i++)
x[i] = 0;
sizeof (*x) * n
void *array_alloc(size_t count, size_t size) {
if (0 == count || MAX_UINT / count > size)
return (0);
return malloc(count * size);
}
Note
Valid format strings for the iPhone OS can be found athttp://developer.apple.com/iphone/library/documentation/CoreFoundation/Conceptual/CFStrings/formatSpecifiers.html.
int main(int argc, char *argv[]) {
NSString * test = @"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x";
NSLog(test);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
[Session started at 2009-03-14 22:09:06 -0700.]
2009-03-14 22:09:08.874 DemoApp[2094:20b]
000070408fe0154b10000bffff00cbfffef842a4e1bfffef8cbfffef94bffff00c0
int main(int argc, char *argv[]) {
NSString * test = @"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x";
NSLog(@"%@", test);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
+ (NSString*) formatStuff:(NSString*)myString
{
myString = [myString stringByAppendingString:userSuppliedString];
return myString
}
NSString myStuff = @"Here is my stuff.";
myStuff = [myStuff stringByAppendingFormat:[UtilityClass
formatStuff:unformattedStuff.text]];
NSString myStuff = @"Here is my stuff.";
myStuff = [myStuff stringByAppendingFormat:@"%@", [UtilityClass
formatStuff:unformattedStuff.text]];
if (i >= 0) {
...
free(mystuff);
}
...
free(mystuff);
id myRedCar = [[[NSString alloc] init] autorelease];
...
[myRedCar release];
Note
See http://weblog.bignerdranch.com/?p=2 for more information on debugging retain counts. The majority of information here is applicable to both OS X and the iPhone.
- Go to Build. Change “Configuration” to “Debug.”
No comments:
Post a Comment