Binary patching ... in Mac OS X
Time and time again, we came across binary-only executables doing things against our likings. Like asking for registration number to use it, or open it. With no viable replacement solution or no source code, it turns out that we are left with only one option and that to patch the binary to override some of it's functionality to make it work as per our likings.
In other words, most of the bad programs these days comes in binary-only version. With no source code available it gets very frustrating when we are forced to use programs, which are so obviously wrong, but which can not be fixed and rebuilt. But there is a very simple way available where you need to flip few bits of the program to solve the issue on hand.
Remember the last time you had to patch a binary program to make it work as per your liking? Don't worry, I'll show you how to patch a binary on Mac OS X.
Lets first write our own test program. Here it is...[remember, this is just a sample program ;-)]
#include
int verify(char* cUser, char* cPass)
{
if(cUser != NULL && cPass != NULL)
{
if(strcmp(cPass, "Test") == 0)
return 1;
else
return 0;
}
}
int main(int argc, char* argv[])
{
char cUser[8];
char cPass[8];
printf("User:");
scanf("%s", cUser);
printf("\nPass:");
scanf("%s", cPass);
if(verify(cUser, cPass))
{
printf("\nWelcome %s\n", cUser);
}
else
{
printf("\nTry again!\n");
}
return 0;
}
Lets build it using gcc.
nightrover:test administrator$ gcc -o test test.c
Run the test program...Lets see how does it behave when different passwords are supplied.
nightrover:test administrator$ ./test
User:Username
Pass:password <-- wrong password, should give "Try again!" message
Try again! <-- as expected!
nightrover:test administrator$
Now lets try correct password (since we have source code, we know the correct password is "Test"). As mentioned in the beginning, great deal of these binaries do not come with source codes.
nightrover:test administrator$ ./test
User:Username
Pass:Test <-- wrong password, should give "Try again" message
Welcome Username <-- as expected! correct user/pass authorized you!
nightrover:test administrator$
But now what if we don't have source of this binary and we don't have correct password to login? As mentioned in the beginning, a great deal of these binaries come without source code.
We need to scan the binary (test.o) through the microscope - in our case a disassembler program can do this for us. We are going to use the "gdb". We need to learn a bit about the executable "gcc" has made beforehand. "otool" can be used to find address and offset for our text segment from the binary.
nightrover:test administrator$ otool -l test
Load command 0
cmd LC_SEGMENT
[...]
Section
sectname __text
segname __TEXT
addr 0x00001e60
size 0x00000172
offset 3680
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
To find out the in memory address of the instruction where the condition is checked, we will have to run our binary inside "gdb". We choose to break at "verify" function - because it is the "verify" function which we are going to disassemble.
nightrover:test administrator$ gdb --arch i386 test
GNU gdb 6.3.50-20050815 (Apple version gdb-768) (Tue Oct 2 04:07:49 UTC 2007)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...Reading symbols for shared libraries ... done
(gdb) break verify
Breakpoint 1 at 0x1ec8
(gdb) break main
Breakpoint 2 at 0x1f3d
Note: setting a breakpoint in the verify() function will halt the program's execution and let us step through the code one line at a time. A breakpoint can be set either by specifying the name of the function or a line number.
(gdb) run
Note: now if we run the program, it will stop at the begining of verify() and wait for further instructions.
Starting program: /Users/administrator/test/test
Reading symbols for shared libraries ++. done
Breakpoint 2, 0x00001f3d in main ()
(gdb) bt
#0 0x00001f3d in main ()
(gdb) next
Single stepping until exit from function main,
which has no line number information.
User:Pallav
Pass:Text
Breakpoint 1, 0x00001ec8 in verify ()
(gdb) bt
#0 0x00001ec8 in verify ()
#1 0x00001f9e in main ()
(gdb) f 0
#0 0x00001ec8 in verify ()
(gdb) disass
Dump of assembler code for function verify:
0x00001ec2
0x00001ec3
0x00001ec5
0x00001ec6
0x00001ec7
0x00001ec8
0x00001ecb
0x00001ed0
0x00001ed1
0x00001ed5
0x00001ed7
0x00001edb
0x00001edd
0x00001ee0
0x00001ee6
0x00001ee9
0x00001eec
0x00001ef3
0x00001ef4
0x00001ef7
0x00001efa
0x00001efd
0x00001eff
0x00001f04
0x00001f06
0x00001f0a
0x00001f0e
0x00001f10
0x00001f12
0x00001f14
0x00001f1b
0x00001f1e
0x00001f21
0x00001f23
0x00001f2a
0x00001f2c
0x00001f2e
0x00001f31
0x00001f34
0x00001f35
0x00001f36
0x00001f37
0x00001f38
End of assembler dump.
(gdb)
We need to figure out the offset for our jne conditional expression in the binary.
offset_in_file = address_in_memory - address_in_text_section + offset_in_text_section
offset_in_file = 0x00001F12 - 0x00001E60 + 3680
offset_in_file = 0x00000F12
We will use the "xxd" to get a hex dump of our binary.
nightrover:test administrator$ xxd ./test > test.hex
[...]
0000f00: 0000 0000 740a 0fb6 46ff 0fb6 4fff 29c8 ....t...F...O.).
0000f10: 85c0 750f c745 e401 0000 008b 45e4 8945 ..u..E......E..E
0000f20: e0eb 0bc7 45e4 0000 0000 ebef eb00 8b45 ....E..........E
[...]
If we look at the disassembled code, we know we have a very easy condition to bypass. There are so many different ways we could fix it: changing the return value of the verify function, forcing the zero flag to be clear at the conditional jump, overwriting the jump with NOPs, etc. The main challenge is to make it with the least amount of bytes changed. After considering different alternatives, I opted for a quite a simple method of flipping the conditional jump instruction. To do so, we need to change the instruction at offset 0x0000f12 from 75 (JNE) to 74 (JE). This single byte change to flip the conditional instruction lead us to...
[...]
0000f00: 0000 0000 740a 0fb6 46ff 0fb6 4fff 29c8 ....t...F...O.).
0000f10: 85c0 740f c745 e401 0000 008b 45e4 8945 ..u..E......E..E
0000f20: e0eb 0bc7 45e4 0000 0000 ebef eb00 8b45 ....E..........E
[...]
With the fix in hand, we need to recreate the binary from the altered xxd file. We will also have to set the executable bit set to let OS know that the file is executable.
nightrover:test administrator$ xxd -r test.hex > testpatched
nightrover:test administrator$ chmod +x testpatched
nightrover:test administrator$ ./testpatched
Now, lets run the patched binary...
nightrover:test administrator$ ./testpatched
User:nightrover
Pass:password <-- wrong password, should give "Try again" message
Welcome nightrover <-- even wrong password allowed you to login! Voila!
nightrover:test administrator$
It was very quick and painless solution. It may not be as good as having the real source of the application to be fixed, but in the real world it solved the problem. Our goal was to solve the problem, wasn't it?