Saturday, March 21, 2009
Dear Diary,
Last week a good friend of mine loaned me his jailbroken,[82] first-generation iPhone. I was very excited. Ever since Apple announced the iPhone, I had wanted to see if I could find a bug in the device, but until last week I had never had access to one.
I finally had an iPhone to play with, and I wanted to search for bugs. But where to start? The first thing I did was make a list of installed applications and libraries that seemed most likely to have bugs. The MobileSafari browser, the MobileMail app, and the audio libraries were at the top of the list. I decided that the audio libraries were the most promising targets since these libraries do a lot of parsing and are heavily used on the phone, so I tried my luck on them.
I performed the following steps when searching the iPhone audio libraries for a bug:
I used a first-generation iPhone with firmware 2.2.1 (5H11) as platform for all the following steps.
Step 1: Research the iPhone’s audio capabilities.
Step 2: Build a simple fuzzer and fuzz the phone.
I installed all the necessary tools—like the Bash, OpenSSH, and the GNU debugger—on the iPhone using Cydia.[83]
The iPhone, with its iPod-based roots, is a powerful audio-capable device. Three frameworks available on the phone provide different levels of sound functionality: the Core Audio,[84] Celestial, and Audio Toolbox[85] frameworks. In addition, the iPhone runs an audio daemon called mediaserverd
, which aggregates the sound output of all applications and governs events such as volume and ringer-switch changes.
The iPhone’s audio system with all its different frameworks seemed a bit complicated, so I decided to start by building a simple fuzzer to search for obvious bugs. The fuzzer that I built does the following:
On a Linux host: Prepares the test cases by mutating a sample target file.
On a Linux host: Serves these test cases via a web server.
On the iPhone: Opens the test cases in MobileSafari.
On the iPhone: Monitors mediaserverd
for faults.
On the iPhone: In the event a fault is uncovered, logs the findings.
Repeats these steps.
I created the following simple, mutation-based file fuzzer to prepare the test cases on a Linux host:
Example 8-1. The code I wrote to prepare test cases on the Linux host (fuzz.c)
01 #include <stdio.h> 02 #include <sys/types.h> 03 #include <sys/mman.h> 04 #include <fcntl.h> 05 #include <stdlib.h> 06 #include <unistd.h> 07 08 int 09 main (int argc, char *argv[]) 10 { 11 int fd = 0; 12 char * p = NULL; 13 char * name = NULL; 14 unsigned int file_size = 0; 15 unsigned int file_offset = 0; 16 unsigned int file_value = 0; 17 18 if (argc < 2) { 19 printf ("[-] Error: not enough arguments\n"); 20 return (1); 21 } else { 22 file_size = atol (argv[1]); 23 file_offset = atol (argv[2]); 24 file_value = atol (argv[3]); 25 name = argv[4]; 26 } 27 28 // open file 29 fd = open (name, O_RDWR); 30 if (fd < 0) { 31 perror ("open"); 32 exit (1); 33 } 34 35 // mmap file 36 p = mmap (0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 37 if ((int) p == −1) { 38 perror ("mmap"); 39 close (fd); 40 exit (1); 41 } 42 43 // mutate file 44 printf ("[+] file offset: 0x%08x (value: 0x%08x)\n", file_offset, file_value); 45 fflush (stdout); 46 p[file_offset] = file_value; 47 48 close (fd); 49 munmap (p, file_size); 50 51 return (0); 52 }
The fuzzer from Example 8-1 takes four arguments: the size of the sample target file, the file offset to manipulate, a 1-byte value that gets written to the given file offset, and the name of the target file. After writing the fuzzer, I compiled it:
linux$ gcc -o fuzz fuzz.c
I then began fuzzing files of the Advanced Audio Coding[86] (AAC) format, which is the default audio format used on the iPhone. I chose the standard iPhone ringtone, named Alarm.m4r, as a sample target file:
linux$ cp Alarm.m4r testcase.m4r
I typed the following line into the terminal to get the size of the test-case file:
linux$ du -b testcase.m4r
415959 testcase.m4r
The command-line options below instruct the fuzzer to replace the byte at file offset 4 with 0xff
(decimal 255):
linux$ ./fuzz 415959 4 255 testcase.m4r
[+] file offset: 0x00000004 (value: 0x000000ff)
I then verified the result with the help of xxd
:
linux$xxd Alarm.m4r | head −1
0000000: 0000 0020 6674 7970 4d34 4120 0000 0000 ... ftypM4A .... linux$xxd testcase.m4r | head −1
0000000: 0000 0020 ff74 7970 4d34 4120 0000 0000 ... .typM4A ....
The output shows that file offset 4 (file offsets are counted starting with 0) was replaced with the expected value (0xff
). Next, I created a bash script to automate the file mutation:
Example 8-2. The bash script I created to automate file mutation (go.sh)
01 #!/bin/bash 02 03 # file size 04 filesize=415959 05 06 # file offset 07 off=0 08 09 # number of files 10 num=4 11 12 # fuzz value 13 val=255 14 15 # name counter 16 cnt=0 17 18 while [ $cnt -lt $num ] 19 do 20 cp ./Alarm.m4r ./file$cnt.m4a 21 ./fuzz $filesize $off $val ./file$cnt.m4a 22 let "off+=1" 23 let "cnt+=1" 24 done
This script, which is just a wrapper for the fuzzer illustrated in Example 8-1, automatically creates four test cases of the target file Alarm.m4r (see line 20). Starting at file offset 0 (see line 7), the first 4 bytes of the target file (see line 10) are each replaced with a 0xff
(see line 13). When executed, the script produced the following output:
linux$ ./go.sh
[+] file offset: 0x00000000 (value: 0x000000ff)
[+] file offset: 0x00000001 (value: 0x000000ff)
[+] file offset: 0x00000002 (value: 0x000000ff)
[+] file offset: 0x00000003 (value: 0x000000ff)
I then verified the created test cases:
linux$xxd file0.m4a | head −1
0000000: ff00 0020 6674 7970 4d34 4120 0000 0000 ... ftypM4A .... linux$xxd file1.m4a | head −1
0000000: 00ff 0020 6674 7970 4d34 4120 0000 0000 ... ftypM4A .... linux$xxd file2.m4a | head −1
0000000: 0000 ff20 6674 7970 4d34 4120 0000 0000 ... ftypM4A .... linux$xxd file3.m4a | head −1
0000000: 0000 00ff 6674 7970 4d34 4120 0000 0000 ....ftypM4A ....
As the output shows, the fuzzer worked as expected and modified the appropriate byte in each test-case file. One important fact I haven’t mentioned yet is that the script in Example 8-2 changes the file extension of the alarm ringtone from .m4r to .m4a (see line 20). This is necessary because MobileSafari doesn’t support the .m4r file extension used by iPhone ringtones.
I copied the modified and unmodified alarm ringtone files into the web root directory of the Apache webserver that I had installed on the Linux host. I changed the file extension of the alarm ringtone from .m4r to .m4a and pointed MobileSafari to the URL of the unmodified ringtone.
As illustrated in Figure 8-1, the unmodified target file Alarm.m4a successfully played on the phone in MobileSafari. I then pointed the browser to the URL of the first modified test-case file, named file0.m4a.
Figure 8-2 shows that MobileSafari opens the modified file but isn’t able to parse it correctly.
So what had I achieved so far? I was able to prepare audio-file test cases via mutation, launch MobileSafari, and instruct it to load the test cases. At this point, I wanted to find a way to automatically open the test-case files in MobileSafari one by one while monitoring mediaserverd
for faults. I created this small Bash script to do the job on the phone:
Example 8-3. Code to automatically open test cases while monitoring mediaserverd
for faults (audiofuzzer.sh)
01 #!/bin/bash 02 03 fuzzhost=192.168.99.103 04 05 echo [+] ================================= 06 echo [+] Start fuzzing 07 echo [+] 08 echo -n "[+] Cleanup: " 09 killall MobileSafari 10 killall mediaserverd 11 sleep 5 12 echo 13 14 origpid=`ps -u mobile -o pid,command | grep /usr/sbin/ mediaserverd | cut -c 0-5` 15 echo [+] Original PID of /usr/sbin/mediaserverd: $origpid 16 17 currpid=$origpid 18 let cnt=0 19 let i=0 20 21 while [ $cnt -le 1000 ]; 22 do 23 if [ $i -eq 10 ]; 24 then 25 echo -n "[+] Restarting mediaserverd.. " 26 killall mediaserverd 27 sleep 4 28 origpid=`ps -u mobile -o pid,command | grep /usr/sbin/ → mediaserverd | cut -c 0-5` 29 currpid=$origpid 30 sleep 10 31 echo "done" 32 echo [+] New mediaserverd PID: $origpid 33 i=0 34 fi 35 echo 36 echo [+] ================================= 37 echo [+] Current file: http://$fuzzhost/file$cnt.m4a 38 openURL http://$fuzzhost/file$cnt.m4a 39 sleep 30 40 currpid=`ps -u mobile -o pid,command | grep /usr/sbin/ mediaserverd | → cut -c 0-5` 41 echo [+] Current PID of /usr/sbin/mediaserverd: $currpid 42 if [ $currpid -ne $origpid ]; 43 then 44 echo [+] POTENTIAL BUG FOUND! File: file$cnt.m4a 45 openURL http://$fuzzhost/BUG_FOUND_file$cnt.m4a 46 origpid=$currpid 47 sleep 5 48 fi 49 ((cnt++)) 50 ((i++)) 51 killall MobileSafari 52 done 53 54 killall MobileSafari
The Bash script illustrated in Example 8-3 works this way:
Line 3 displays the IP address of the web server that hosts the test cases.
Lines 9 and 10 restart mediaserverd
and kill all running MobileSafari instances in order to create a clean environment.
Line 14 copies the process ID of the mediaserverd
audio daemon into the variable origpid
.
Line 21 contains the main loop that is executed for each test case.
Lines 23–34 restart the mediaserverd
after every 10 test cases. Fuzzing the iPhone can be tedious, since some components, including mediaserverd
, are prone to hangs.
Line 38 launches the individual test cases hosted on the web server using the openURL
tool.[87]
Line 40 copies the current process ID of the mediaserverd
audio daemon into the variable currpid
.
Line 42 compares the saved process ID of mediaserverd
(see line 14) and the current process ID of the daemon. The two process IDs differ when mediaserverd
has encountered a fault and restarted while processing one of the test cases. This finding is logged to the phone’s terminal (see line 44). The script will also send a GET request to the web server that includes the text “BUG_FOUND
” as well as the name of the file that crashed mediaserverd
(see line 45).
Line 51 kills the current instance of MobileSafari after each test-case run.
After I implemented this little script, I created 1,000 mutations of the Alarm.m4r ringtone starting at file offset 0, copied them to the web root directory of the web server, and started the audiofuzzer.sh script on the iPhone. From time to time the phone crashed due to memory leaks. Every time that happened, I had to reboot the phone, extract the filename of the last processed test case from the access logfile of the web server, adjust line 18 of Example 8-3, and continue fuzzing. Fuzzing the iPhone can be such a pain . . . but it was worth it! In addition to the memory leaks that froze the phone, I also found a bunch of crashes due to memory corruption.