Executing the Hack
As mentioned earlier, this hack can be applied to:
- A function defined in the application itself like
- A function defined in a library C runtime library functions like
Let's look at both cases, one by one:
# Raman Deep: [email protected] # This script starts the victim application in GDB , apply the modify-function-return-value # hack on function 'get_remote_token' the first time it is called, and then detach the application from GDB. # So, there is no execution overhead once hack is applied. # The trick used is that most of the compilers, in default settings , # use EAX register to return the retun value to the caller file ./victim_client br get_remote_token r finish printf "\n value of eax = %d \n", $eax set $eax=0 printf "\n value of eax, after hack = %d ..... HACKED! \n", $eax detach q
I loaded the
victim_client with the GDB command
file. Then a breakpoint is set, with the GDB command
br, on target function
get_remote_token. Then the application is started with GDB command
r. Below is the result of running
victim_client directly and then in GDB under the influence
[[email protected] ARTICLE]$ ./victim_client Could not initialize... exiting [[email protected] ARTICLE]$ [[email protected] ARTICLE]$ gdb -quiet -x modify-function-return-value.gdb Breakpoint 1 at 0x804861a Breakpoint 1, 0x0804861a in get_remote_token () 0x080488a2 in main () value of eax = 1 value of eax, after hack = 0 ..... HACKED! Program initialized success. Executing rest of the program... [[email protected] ARTICLE]$
recvfrom in the C Runtime Library
Earlier, I noted that the remote server returns different information (
SUCCESS) depending on whether the client is invalid or valid. This information can be used to manipulate the application by hacking the
recvfrom function provided by the system.
recvfrom() is slightly different than merely modifying function return value. Why? Because we need to change the string returned via a second parameter,
byrecvfrom(). On computer B,
STOP; if this can be changed to
SUCCESS, then the application is compromised. This is a modify-function-argument-value hack, a variant of the modify-function-return-value hack. Here is a small GDB script (
modify-function-return-value_2.gdb) to do that.
# Raman Deep: [email protected] # This script starts the victim application in GDB , apply the modify-function-return-value # (well, actually, more than that because it changes the argument on return) hack on function 'recvfrom' # the first time this is called, and then detaches the application from GDB. So, there is no # execution overhead once hack is applied. file ./victim_client br recvfrom r # This is the second argument to the recvfrom. # Lets save it's address, for later use! set variable $_buffer_address = *(int *) ($esp +8) finish printf "\n Value returned by recvfrom(the buffer) = \"%s\" \n", $_buffer_address printf "\n Changing it...\n" call strcpy($_buffer_address,"SUCCESS") printf "\n Value returned by recvfrom changed to \"%s\"...and HACKED!\n",$_buffer_address detach q
The output below is the result of running
victim_client directly and then in GDB under the influence
[[email protected]]$ ./victim_client Could not initialize... exiting [[email protected] ]$ [[email protected]]$ gdb -quiet -x modify-function-return-value_2.gdb Breakpoint 1 at 0x8048480 Breakpoint 1, 0x49ef1ff0 in recvfrom () from /lib/libc.so.6 0x08048801 in get_remote_token () Value returned by recvfrom(the buffer) = "STOP" Changing it... $1 = -1073744896 Value returned by recvfrom changed to "SUCCESS"...and HACKED! Program initialized success. Executing rest of the program... [[email protected]]$
Source for Example Programs
Source code for the examples in this article can be downloaded. The steps to compilation are as follows:
gcc server.c –o server
gcc victim_client.c –o victim_client
server is run on a machine with IP address 192.168.109.138. Then set environment variable
REMOTE_HOST on client computers as:
Note that the client application and GDB scripts have been tested on an x86 platform running Fedora15 (GCC v4.6.0, GDB v 7.2.90, GLIBC v2.13.90) and Fedora 16 (GCC v4.6.2, GDB v7.3.50, GLIBC v2.14.90). In both cases, GCC was configured to produce 32-bit code.
Defense Against the Hack
There are many ways to make it more difficult for crackers to pull off this hack. The idea is to make it hard enough that they will be inclined to abandon their efforts or move on to some other hacking activity elsewhere. A truly determined hacker with all the time in the world, however, will get around any and all of these steps.
1. Source code obfuscation: This is a mechanism for obfuscating the names of the functions and variables in the source code so that it's difficult to guess the functionality by looking at the name of the identifier.
2. Binary code obfuscation: This is the mechanism for obfuscating the binary code generated. It's a lot more difficult, but is a very effective countermeasure. For example, developers can insert bogus code around sensitive information to make it harder for the hacker to stumble upon it but this is a very basic example.
- Use a challenge-response mechanism: The problem with
victim_clientis that it makes a big decision based solely on the return value of a single function. This problem can be eliminated by adding more variables; for example, passing a
challengevalue the server will use this
challengeand send back a
response. Then the client application will verify the response. This increases the number of variable attributes that a hacker needs to manipulate.
- Strip the binary: We were able to find the target function easily because the binary (ELF format) contained enough information about the symbols in it. If we strip the binary, then the symbol table of the ELF binary will be unavailable (hence, make things little harder for the hacker).
- Write your own version of C runtime functions: Writing your own C runtime functions, like
strcmp, combined with stripping and obfuscation can be very helpful. However, writing some of the functions such as
recvfrom(or any function that needs entry into the kernel) is not straightforward. In any case, writing your own versions of C-runtime-equivalent function creates maintenance and portability issues.
- Encryption: Developers should encrypt sensitive data whenever possible. Because encryption can be application-specific, it provides a very subtle countermeasure even if some of the functions are hacked, because information obtained (like the data captured through
recvfrom) is not of much use until and unless the encryption is broken.
- Use tricky C strings: Perhaps the most valuable information a hacker can obtain is through ASCII strings, which are also the means of communication with the user. All the C-style strings, if stored in some intelligent manner, can trick tools like
ltrace, suppressing lots of information from being available to a hacker. For example, you can always start your string with a
NULLcharacter tools like
ltracewill be fooled and will not display the actual contents of the string. However, you need to devise a mechanism in your application to handle this. Obviously, this technique can't be used while passing strings to functions like
- Compile your sensitive functions through assembly and then make changes to their prologue and epilogue. For example, the
eaxprocessor register, by default, is used to communicate the return value to the caller; we can change this calling semantic for desired functions. This is very low-level, but worth the pain if used cautiously.
The modify-function-return-value hack is simple, but it can be dangerous. The problems it can create for an application increase dramatically because it can be applied to function calls inside the C runtime library as well. The fact that it does not have a big runtime overhead is also something to worry about. We also examined a variant of this hack, wherein even arguments to a function can be changed. Developers must think of these problems well in advance to make sure that safeguarding techniques are implemented in the application code.
Raman Deep is a senior software engineer with SafeNet's Software Rights Management division. He develops UNIX releases for products designed to help software vendors control how their applications are deployed and used. This article represents his views only.