Hacking With GDB

Compiling the Code

Now, first we'd like to describe some details about a simple but instructive C program to you. For simplicity, we name it overflow.

overflow.c:

1    #include<stdio.h>
2    #include<string.h>
3    #include<stdlib.h>
4    void granted();
5    int checkPasswd();
6    int checkPasswd()
7    {
8        char passwd[16];
9        printf("Enter your passwd: ");
10        gets(passwd);
11        if(strcmp(passwd, "passwd1"))
12        {   
13            printf("\nYou fail!\n");
14        }   
15        else
16        {   
17            granted();
18        }   
19    }
20    void granted(){
21        printf("\nAccess granted\n");
22        printf("You have gotten the privileges, and can do anything you like!! ");
23        // Privileged stuff happens here.
24        return ;
25    }
26    int main(){
27        checkPasswd();
28        return 0;
29    }

In overflow.c, the main() function invokes the checkPasswd() function. In checkPasswd() function, if the password we enter is correct, it will invoke granted() function and we will get the privileges to do anything. Of course, we have not set any privileged stuff here, it just prints out a statement to denote that you have been granted access. Then compile the source code:

$ gcc -fno-stack-protector overflow.c -o overflow

To prevent the program from stack buffer overflow attack, GCC 5.4.0 enable option fstack-protector by default. So, for demonstrating how to hack with stack buffer overflow, we specify -fno-stack-protector to disable stack-protector.

Then, we will see this warning information:

warning

Here is a quote from man gets.

The gets() function cannot be used securely. Because of its lack of bounds checking, and the inability for the calling program to reliably determine the length of the next incoming line.

Yes, just as you guess, we will make use of the dangerous gets() function to hack the overflow program.

When we enter the correct password(note that we hardcode the correct password in overflow.c):

correct password

When we enter a wrong password:

wrong password

Procedure and Stack

Before we start to hack, it will be better for us to review the knowledge about Linux stack. Linux stack is similar with which Eric has introduced during lecture, but we will describe some details with assembly instructions.

A procedure call involves passing both data (in the form of procedure parameters and return values) and control from one part of a program to another. In addition, it must allocate space for the local variables of the procedure on entry and deallocate them on exit. Most machines, including IA32, provide only simple instructions for transferring control to and from procedures. The passing of data and the allocation and deallocation of local variables is handled by manipulating the program stack.

Stack Frame Structure

IA32 programs make use of the program stack to support procedure calls. The machine uses the stack to pass procedure arguments, to store return information, to save registers for later restoration, and for local storage. The portion of the stack allocated for a single procedure call is called a stack frame. We should pay attention to that in Linux the stack is growing from high memory address to low memory address just as follows:

Stack Frame

The topmost stack frame is delimited by two pointers, with register %ebp serving as the frame pointer, and register %esp serving as the stack pointer. The stack pointer can move while the procedure is executing, and hence most information is accessed relative to the frame pointer. Suppose procedure P (the caller) calls procedure Q (the callee). The arguments to Q are contained within the stack frame for P. In addition, when P calls Q, the return address within P where the program should resume execution when it returns from Q is pushed onto the stack, forming the end of P’s stack frame. The stack frame for Q starts with the saved value of the P's frame pointer, followed by copies of any other saved register values. Procedure Q also uses the stack for any local variables that cannot be stored in registers. As described earlier, the stack grows toward lower addresses and the stack pointer %esp points to the top element of the stack. Data can be stored on and retrieved from the stack using the push and pop instructions. Space for data with no specified initial value can be allocated on the stack by simply decrementing the stack pointer %esp by an appropriate amount. Here we shall help you review these two stack operation instructions with two examples:

  • push: pushing a double-word value onto the stack involves first decrementing the stack pointer(%esp) by 4 and then writing the value at the new top of stack address.
  • pop: popping a double-word value involves reading from the top of stack location and then incrementing the stack pointer by 4.

Transferring Control

Here, we mainly review two very important instructions.

  • call
    • Call instruction has a target indicating the address of the instruction where the called procedure starts.
    • The effect of a call instruction is to push a return address on the stack and jump to the start of the called procedure
  • ret: the ret instruction pop the return address from caller's stack frame to %eip, at which execution will resume when the called procedure returns.

Stack Frame for overflow

Through above description about stack frame, consider the C program overflow, where main() function includes a call to checkPasswd() function. The figure below shows the stack frame structure just before checkPasswd() function calls C standard library gets() function.

stackFrameBeforegets

Recall that the gets() function has no bound checking, and it will read what we input in a line to char passwd[16] buffer in checkPasswd()'s stack frame no matter how long the input is. So, assume our input is longer than the size of buffer so that the return address of main() procedure's stack frame is overwritten as the address of granted() function:

stackFrameAfterGets

Then when the checkPasswd() function returns, the ret instruction will pop the address of granted() function to %eip, and the granted() function will be invoked despite the password is correct or not. Now we have known the method to hack the program overflow. In order to really do that, we actually need to know:

(a): The return address of main() after checkPasswd() returns.

(b): The buffer address on the stack.

(c): The address of the granted() function.

These information above can be obtained by using GDB on the overflow executable.

Hacking with GDB

Let's start with getting (a) the return address of main() after checkPasswd returns, which the call instruction will push to the stack when calling checkpasswd() function.

1) gdb ./overflow

2) Read the assembly code of overflow's main() function by disassemble main

disassemble main

In the output, there are two parts delimited by a colon, the left part is the address of the assembly instruction, the number in the angle brackets is the offset from the beginning of the function; the right part is the corresponding assembly instruction. We see from the figure that after the checkPasswd() function returns, the execution will continue the mov instruction. So 0x08048521 would be the return address that we would like to overwritten.

Now, we read the assembly code of checkPasswd() function in order to get (c). disassemble checkPasswd

Here, we get the address of granted() function call 0x80484f2 <granted> is 0x080484ea.

Finally, in order to get (b), the buffer address on the stack, we set a breakpoint at address 0x080484b8, where calling C standard library gets() function call 0x8048360 <gets@plt>. It is a relocation placeholder that will be replaced by the program loader since gets() is a function in shared library. In order to set a breakpoint at a specific memory address, we can use break * followed by a memory address.

break*

After setting breakpoint, we use run command to start overflow program and it will pause at the breakpoint we set before. Here, we use x command to print the content of the stack: x/20x $esp means we list 20 double-word data from the top of the stack. x/20x

In the output, there are 5 lines, each line can be divided into two parts with a colon, the right part is 4 two-double values in hex format, the left is the data's address within the same line. For example, we can easily know that the address of the first double-word in the second line is 0xbfffefe0. And then we find the return address(0x08048521) is pushed inside the stack with address 0xbfffeffc.

Then we use nexti command to step one instruction but not step into gets() function. Then overflow program asks us to enter the password. After entering a sequence of characters "BBBBCCCC"(four 'B's + four 'C's, '0x42' and '0x43' denote 'B' and 'C' respectively in hex system), we x/20x $esp again. The characters overwritten the memory space which are in two green rectangles.

x/20x

Till now, we can know (b) buffer address on the stack, which is $0xbfffefe0, and it is obvious that the buffer grows from low memory address to high memory address. The figure below shows the stack structure after entering 'BBBBCCCC'.

BBBBCCC

So, if we want to overwritten (a) 0x08048521 with (c) 0x080484ea, we need to construct a sequence of 32(0xbffff000 - oxbfffefe0) characters(bytes), in which the last 4 characters combined should equal (c) 0x080484ea to overwritten (a) 0x08048521, and we choose 28 'C' for the rest characters. Then we can see the stack frame structure as follows.

overflow

For convenience, we can use following command in terminal to generate 32 characters into a file named attack.txt.

python -c 'print "C"*28 + "\xea\x84\x04\x08"' > attack.txt

Note that we input the bytes of 0x080484ea inversely, because our platform is based on little endian intel processor.

With everything ready, we now can enter ./overflow < attack.txt and gain privilege without knowing the real password.

hacking done

results matching ""

    No results matching ""