Wednesday, September 24, 2008

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 : push   %ebp

0x00001ec3 : mov    %esp,%ebp

0x00001ec5 : push   %edi

0x00001ec6 : push   %esi

0x00001ec7 : push   %ebx

0x00001ec8 : sub    $0x2c,%esp

0x00001ecb : call   0x1ed0

0x00001ed0 : pop    %ebx

0x00001ed1 : cmpl   $0x0,0x8(%ebp)

0x00001ed5 : je     0x1f2c

0x00001ed7 : cmpl   $0x0,0xc(%ebp)

0x00001edb : je     0x1f2c

0x00001edd : mov    0xc(%ebp),%eax

0x00001ee0 : lea    0x102(%ebx),%edx

0x00001ee6 : mov    %eax,-0x24(%ebp)

0x00001ee9 : mov    %edx,-0x28(%ebp)

0x00001eec : movl   $0x5,-0x2c(%ebp)

0x00001ef3 : cld    

0x00001ef4 : mov    -0x24(%ebp),%esi

0x00001ef7 : mov    -0x28(%ebp),%edi

0x00001efa : mov    -0x2c(%ebp),%ecx

0x00001efd : repz cmpsb %es:(%edi),%ds:(%esi)

0x00001eff : mov    $0x0,%eax

0x00001f04 : je     0x1f10

0x00001f06 : movzbl -0x1(%esi),%eax

0x00001f0a : movzbl -0x1(%edi),%ecx

0x00001f0e : sub    %ecx,%eax

0x00001f10 : test   %eax,%eax

0x00001f12 : jne    0x1f23

0x00001f14 : movl   $0x1,-0x1c(%ebp)

0x00001f1b : mov    -0x1c(%ebp),%eax

0x00001f1e : mov    %eax,-0x20(%ebp)

0x00001f21 : jmp    0x1f2e

0x00001f23 : movl   $0x0,-0x1c(%ebp)

0x00001f2a :jmp    0x1f1b

0x00001f2c :jmp    0x1f2e

0x00001f2e :mov    -0x20(%ebp),%eax

0x00001f31 :add    $0x2c,%esp

0x00001f34 :pop    %ebx

0x00001f35 :pop    %esi

0x00001f36 :pop    %edi

0x00001f37 :leave  

0x00001f38 :ret    

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?

Install OpenVPN+Tunnelblick on OS X

Obtain OpenVPN from http://openvpn.net/ and unpack the tarball. OpenVPN has 2 prerequisites, OpenSSL (encryption) and LZO (real-time compression). With Mac OS X 10.2 or higher, OpenSSL headers are included, so it should not be problem. LZO, is for a real-time compression. You can disable LZO with --disable-lzo Otherwise, install LZO. Once the prerequisites have worked out, build OpenVPN. An example:

./configure
make
make install (if not root, use sudo make install)


OpenVPN should be installed in /usr/local/sbin and ready to use. 

Next up is installation of Tunnelblick. Download it from http://www.tunnelblick.net, mount the disk image and double click on "Tunnelblick-Complete.mpkg", which will install all the necessary drivers and software packages. You can start the program from the /Applications folder. It offers the sample configuration with necessary directives and samples. Make appropriate changes in the ~/Library/openvpn/openvpn.conf according to your needs. 

Next step is to put all the certificate files in place. The certificates are received from the VPN server. Copy all certificates files to ~/Library/openvpn folder (this is where our openvpn.conf resides).

If you are accessing a remote network, and not just a single host, you need an "up" shell script to set the appropriate static routes. Download the OpenVPN TAP up-down script from:

Save it to ~/Library/openvpn, the folder where your Tunnelblick OpenVPN configuration lives.

You need to make the script executable.

sudo chmod +x ~/Library/openvpn/tap-up-down.sh

You need to edit your openvpn.conf to include these lines:

up ./tap-up-down.sh
down ./tap-up-down.sh

This about it as far as setup is concerned. It is now time to test our setup. Run Tunnelblick. If it successfully connects to the VPN server, the color of the icon will change to white from dark-grey. Upon failure, you can view the connection log by clicking on Tunnelblick icon on your menu bar (top-right) and click "Details...".

Hope this will help you to setup OpenVPN + Tunnelblick on your Mac OS X.