Modify-function-return-value Hack! — Part 1

Hacker alert!

Sometimes it’s helpful to think from a hacker’s perspective. The information obtained can be used to write better code and a secure application. An application shipped in release mode (without any debug information) may be vulnerable to security holes, which this article focuses on. Let us go on to understand how someone can hack a key function in an application — like authentication.

The basic threat makes some functions pass (return success) even if they fail — like even if the password that is provided is wrong. In this article, we will discuss how a poorly written application involving function calls could get hacked, how gdb can be invoked in batch mode, and how to write gdb scripts and use them to automate the hack. We also look at counter measures for developers to safeguard their applications.

Note: There is no intention to provide cracking techniques; this article is purely for educational purposes. This enables application developers to focus on security-related aspects. It is assumed that readers are acquainted with GCC, C and Linux. We will focus on the Intel platform only, but the idea can be extended to other hardware platforms with a few modifications.

Basic concepts

Usually, a program is divided into a set of functions, each doing a specific task. Once a function is invoked, it has (in general) two characteristics:

  • It returns a value — the status of the function call; whether it passed or failed. The caller can take action based on this return value.
  • Side-effects — the effects of the function on the global state of the program (like updating a database or a global variable, etc).

Not all functions have side-effects, but almost all functions return a value. The return value normally decides the flow of the program after that call. What if a hacker manages to change the return value of a targeted function? This causes application logic (more precisely, the flow) to be changed to the hacker’s advantage — we don’t want this!

Consider an application that has a function named authenticate. This function takes an input string (password), and returns 0 if the password is good — or returns 1 otherwise. Hackers can gain access to the application by changing the return value of authenticate.

Victim application: An example

For this article, we have a test application (hackMe.c) shown below, where the authenticate function is called by main to check if the application should continue execution or not. The application exits if the input string passed to it is not equal to PASS. The program command-line argument is passed to main, and subsequently to the authenticate function.

#include<stdio.h>
/*
* Returns 0 on success, and
* Returns 1 on failure
*/
int authenticate (char *test)
{
  if(strcmp(test,"PASS") == 0 )
  return 0; /* success*/
  else
  return 1; /* fail */
}
/* USAGE: . /hackMe FAIL */
int main(int argc, char *argv[])
{
  int retVal = -1;
  if (argc< 2)
  {
    printf ("\n USAGE: %s <PASS|FAIL>", argv[0]);
    exit (-2);
  }
  /* skipping any checks on argv to keep it simple*/
  retVal = authenticate(argv[1]);
  /* WE WILL USE GDB TO ALTER RETURN VALUE OF THIS CALL JUST BEFORE
  COPYING THE RETURN VALUE TO VARIABLE retVal */
  if( retVal == 0)
  printf("\n Authenticated ...program continuing...\n");
  else
  {
    printf("\n Wrong Input, exiting...\n");
    exit (retVal);
  }
/*
.
.
Rest of the program
.
.
*/
}

Compile and link the program (without any debug information!) with the following command:

gcc hackMe.c -o hackMe

Analysing the victim binary

Here are the results of invoking this program with different command-line arguments:

[raman@Linux32 article]$ ./hackMe PASS     # [R1]
Authenticated ...program continuing...
[raman@Linux32 article]$ ./hackMe FAIL     # [R2]
Wrong Input, exiting...
[raman@Linux32 article]$ ./hackMe 123      # [R3]
Wrong Input, exiting...
[raman@Linux32 article]$

The program continues execution only if the input string supplied as a command-line argument matches some criteria (in this case, a fixed string, PASS). But what exactly causes it to fail in scenario R2 and scenario R3? Let’s make use of nm — this command lists all the symbols in a binary.

[raman@Linux32 article]$ nm hackMe | grep " T "
08048390 T authenticate
080484d4 T _fini
08048278 T _init
0804847c T __libc_csu_fini
0804844c T __libc_csu_init
080483c2 T main
080482e0 T _start
[raman@Linux32 article]$

There are two functions of interest: main and authenticate. All the rest are provided by GCC. The developer didn’t take enough care while naming — authenticate seems to be the weak function! It’s a guess, but let’s dig deeper.

Running the victim application in gdb: Mounting the attack manually

Let’s apply the mechanism discussed in the basic concepts, and see if this application can be compromised! The session below shows the steps performed with gdb (I used v6.8):

[raman@Linux32 article]$ gdb ./hackMe                        (#1)
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 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-redhat-linux-gnu"...
(gdb) br authenticate                                         (#2)
Breakpoint 1 at 0x8048396
(gdb) r FAIL      ---------------------- > Program should fail(#3)
Starting program: /home/rdeep/article/hackMe FAIL

Breakpoint 1, 0x08048396 in authenticate ()                   (#4)
(gdb) stepi
0x08048399 in authenticate ()
(gdb) step
Single stepping until exit from function authenticate,
which has no line number information.
0x0804840e in main ()
(gdb) p $eax                                                  (#5)
$1 = 1
(gdb) set $eax=0  -------------------------- > But we hacked! (#6)
(gdb) p $eax
$2 = 0
(gdb) c                                                       (#7)
Continuing.

Authenticated ...program continuing... ---- > And, reflected here!

Before explaining what is happening in this session, let’s recall some gdb commands:

  • stepi — Step one assembly instruction at a time.
  • step — Step program until it reaches a different source line; when the program is compiled without any debug information, this command stops just before coming out of the current function.
  • set — Set the value of a variable or register, etc. $eax, in this command, refers to the processor register EAX.
  • To pass small (like int) return types, GCC uses the EAX processor register on the Intel platform.

Armed with this information, let’s find out what is happening in the above session. I loaded the (release mode) program in gdb (#1), and inserted a breakpoint in the authenticate function (#2) — the one we need to manipulate. Then the program is started, and the command r (#3) provides a command-line argument, FAIL. Since this is a wrong password, the program should end with a failure message. However, we intervene (with the help of the great gdb!) and alter the program flow.

As soon as the program is run, it hits the breakpoint in the authenticate function (#4). We want control back with us as soon as gdb has finished executing authenticate and the program flow is just back into main. This is achieved by two GDB commands: stepi and step. By the time step completes, we are back in main and the return value of authenticate is available in the EAX processor register.

As you can see, the return value was 1 (#5); we changed this to 0 (#6), and then we continued the program with the command c (#7) — and all this happened before copying the return value into the variable retVal. So, here is the result; we changed the normal execution flow of the program. The program that was supposed to fail has passed!

This approach is simple because:

  • No coding is involved.
  • Performance hit is minimal.
  • It can be used against any function in the application, provided the function side-effects do not alter the overall program flow.
  • No debug information is required in binary; it works on release-mode binaries, as long as they are un-stripped.

Automating the attack: GDB scripts at work

GDB can be invoked in batch mode, wherein it reads inputs from a file (called a GDB script) instead of reading interactively. We will be looking into two gdb options, -batch and -x. The –x switch takes the name of a file from where gdb reads commands. Please refer to man gdb for more details.

Let us write a script with GDB commands in it, to automate the steps we performed in the previous section. We can name our script hackScript.gdb, and here are its contents:

file ./hackMe
br authenticate
r FAIL
printf "n ----> Breakpoint HIT...n"
stepi
step
printf "nn ----> I AM OUT OF THE TARGET FUNCTION, BACK IN MAIN: RETURN VALUE HERE(BEFORE HACK) IS %d", $eax
printf "nn ----> SETTING THIS VALUE TO 0n"
set $eax=0
printf "nn ----> EAX AFTER HACK IS %d", $eax
c

This script loads the binary, and then inserts a breakpoint with the command br. Let us use the GDB command printf (similar to LIBC printf) to print logging information. Given below is the output to run this script in gdb:

[raman@Linux32 article]$ gdb -x hackScript.gdb  -batch
Breakpoint 1 at 0x8048396

Breakpoint 1, 0x08048396 in authenticate ()

----> Breakpoint HIT...
0x08048399 in authenticate ()
Single stepping until exit from function authenticate,
which has no line number information.
0x0804840e in main ()

----> I AM OUT OF THE TARGET FUNCTION, BACK IN MAIN: RETURN VALUE HERE(BEFORE HACK) IS 1

----> SETTING THIS VALUE TO 0

----> EAX AFTER HACK IS 0
Authenticated ...program continuing...

Counter measures

  1. Obfuscation — this technique involves changing the function names to some hard-to-guess names. This can be achieved by just using the #define construct in C.
  2. Use function side-effects — don’t just make decisions on the return value, use an additional criterion.
  3. Use a challenge-response mechanism.
  4. Strip the binary so that it’s difficult to determine symbols (functions, variables, etc.) and their address in the binary.
  5. Include fake code before exiting the application, so that it’s hard to detect the location where the program exits.

We learned how GDB can be used to hack an application if the hacker has sufficient information about the key functions. Although key functions can be obfuscated, some level of R&D with the application can undermine the security of the application.

The application used in this article was built in release mode, though un-stripped. The vulnerability can be reduced by stripping (with the strip command) before shipping. However, even stripping the binary may not be very effective, as some smart minds have an UNSTRIP program. Stripping a binary may also result in debugging challenges in the field.

What next?

I hope you find this article useful for educational purposes. The idea discussed can be extended (in a positive manner) to build a logging mechanism for a particular application, in order to debug the behaviour of the application — and, in some cases, alter it!

In the next article, we will try to make better use of this technique by digging a little deeper into the glibc interface layer, and explore more threats that our application will have to face in the real world!

Disclaimer: The information provided in this article is only for educational purposes and should not be used to attack/hack any application that is not owned by you.
References

1. GDB man page
2. UNSTRIP

  • xfreax

    Hi Raman Deep,
    I’m a beginner at gdb. Tried your hack-steps mentioned above in “Running the victim application in gdb: Mounting the attack manually”. But when I gave the “c” command to continue, it didn’t pass i.e the code exited with a return value of “01″. Also I saw that the EAX register on my machine had some random 5 digit value each time I tried debugging, instead of a 1 as yours.
     
    Can you tell me what should be the problem? Whether its with the EAX register or any other way that you can suggest to have this issue sorted?
     
    Thanks!

  • Chris Drane

    In practice, the target would likely be running as a SUID binary with higher permissions, preventing the above from working.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.