简体   繁体   中英

How to disable possible stack smashing protection (EIP is not being overwritten, EBP is)

I'm trying to figure out how stash smashing is carried out step by step. I have already used Google to no avail, I still don't know why my EIP is not being overwritten. I have this example program:

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int main(int argc, char *argv[])
 5 {
 6     char buf[10];
 7 
 8     strcpy(buf, argv[1]);
 9     printf("Done.\n");
10     return 0;
11     
12 }

It's compiled with

gcc -g -o prog main.c

When I put a lot of AAAAAA's I get SEGV and the register EBP (and also argc and argv addresses are overwritten:

Program received signal SIGSEGV, Segmentation fault.
0x08048472 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>, argv=<error reading variable: Cannot access memory at address 0x41414145>)
    at main.c:12
12  }
(gdb) info reg
eax            0x0  0
ecx            0x41414141   1094795585
edx            0xb7fbb878   -1208240008
ebx            0xb7fba000   -1208246272
esp            0x4141413d   0x4141413d
ebp            0x41414141   0x41414141
esi            0x0  0
edi            0x0  0
eip            0x8048472    0x8048472 <main+71>
eflags         0x10282  [ SF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

I thought that EIP is just below EBP, but it still has the address from main function. Here's the disassembly of main:

(gdb) disass main
Dump of assembler code for function main:
   0x0804842b <+0>: lea    0x4(%esp),%ecx
   0x0804842f <+4>: and    $0xfffffff0,%esp
   0x08048432 <+7>: pushl  -0x4(%ecx)
   0x08048435 <+10>:    push   %ebp
   0x08048436 <+11>:    mov    %esp,%ebp
   0x08048438 <+13>:    push   %ecx
   0x08048439 <+14>:    sub    $0x14,%esp
   0x0804843c <+17>:    mov    %ecx,%eax
   0x0804843e <+19>:    mov    0x4(%eax),%eax
   0x08048441 <+22>:    add    $0x4,%eax
   0x08048444 <+25>:    mov    (%eax),%eax
   0x08048446 <+27>:    sub    $0x8,%esp
   0x08048449 <+30>:    push   %eax
   0x0804844a <+31>:    lea    -0x12(%ebp),%eax
   0x0804844d <+34>:    push   %eax
   0x0804844e <+35>:    call   0x80482f0 <strcpy@plt>
   0x08048453 <+40>:    add    $0x10,%esp
   0x08048456 <+43>:    sub    $0xc,%esp
   0x08048459 <+46>:    push   $0x8048510
   0x0804845e <+51>:    call   0x8048300 <puts@plt>
   0x08048463 <+56>:    add    $0x10,%esp
   0x08048466 <+59>:    mov    $0x0,%eax
   0x0804846b <+64>:    mov    -0x4(%ebp),%ecx
   0x0804846e <+67>:    leave  
   0x0804846f <+68>:    lea    -0x4(%ecx),%esp
=> 0x08048472 <+71>:    ret    
End of assembler dump.

Now I'm in the process of figuring out the assembler instructions one by one, but I don't see the moment where EIP is loaded with a return address from the stack just after strcpy finishes. I tried the -fno-stack-protector but it didn't change a thing. What could be the reason for this?

EDIT:

OK, I'll try to go over it step by step, please correct me where I'm wrong

   # Just below the sp are argc and argv and the sp points to the address
   # where RET will be stored
   # This one moves the address of argc (which is on the stack) to $ecx 
   0x0804842b <+0>: lea    0x4(%esp),%ecx
   # Move stack pointer down for alignment
   0x0804842f <+4>: and    $0xfffffff0,%esp
   # Push the value to which $sp pointed to before alignment 
   # It is never used - correct me if I'm wrong  
   0x08048432 <+7>: pushl  -0x4(%ecx)
   # Push last used base pointer value (and start creating another frame)
   0x08048435 <+10>:    push   %ebp
   # Set current position sp as bp - I think here the main body starts
   0x08048436 <+11>:    mov    %esp,%ebp
   # Push the address of argc - it's later used for calculating 
   # the address of argv[1]. 
   0x08048438 <+13>:    push   %ecx
   # Make some space on the stack (20 bytes - 5 words - first two I'm 
   # sure for what (alignment and not used here return value?) 
   # another 3 for buffer[10]
   0x08048439 <+14>:    sub    $0x14,%esp
   # Move argc address to $eax
   0x0804843c <+17>:    mov    %ecx,%eax
   # Move argv address to $eax
   0x0804843e <+19>:    mov    0x4(%eax),%eax
   # Move past argv - $eax should now point to pointer to first 
   # argument string
   0x08048441 <+22>:    add    $0x4,%eax
   # Move the address of the parameter string to $eax
   0x08048444 <+25>:    mov    (%eax),%eax
   # Make space for 2 words 
   # (probably alignment and return value from strcpy)
   0x08048446 <+27>:    sub    $0x8,%esp
   # Push the parameter address
   0x08048449 <+30>:    push   %eax
   # Get the address of the local buffer
   0x0804844a <+31>:    lea    -0x12(%ebp),%eax
   # Push it
   0x0804844d <+34>:    push   %eax
   # Call strcpy
   0x0804844e <+35>:    call   0x80482f0 <strcpy@plt>
   # Remove 4 words - 2 for arguments and 2 for return + alignment
   0x08048453 <+40>:    add    $0x10,%esp
   # Make space for 3 words - alignment + return value
   0x08048456 <+43>:    sub    $0xc,%esp
   # Push the printf argument address (the string address)
   0x08048459 <+46>:    push   $0x8048510
   # Call printf
   0x0804845e <+51>:    call   0x8048300 <puts@plt>
   # Remove 4 words - 1 for parameter and previous 3
   0x08048463 <+56>:    add    $0x10,%esp
   # Reset 0x0 just because
   0x08048466 <+59>:    mov    $0x0,%eax
   # Load previously saved address of argc
   0x0804846b <+64>:    mov    -0x4(%ebp),%ecx
   # not sure about that leave...
   0x0804846e <+67>:    leave  
   # Reload $esp starting value
   0x0804846f <+68>:    lea    -0x4(%ecx),%esp
   # Pop the RET address - this one should be changed to 
   # pointer to malicious code
=> 0x08048472 <+71>:    ret    
  1. Is the value in line +7 unnecessary? I don't see any use for it, so why is it stored?
  2. In some places sp moves more than it has to - is it due to alignment? (eg line +14)
  3. Is my conclusion over line +71 correct?

Disclaimer: I am using gcc-4.8.3 on a Windows 7 system with gnuwin32 installed. Windows doesn't appear to have ASLR enabled by default, so I get reproducible memory addresses when I run this program which makes life a bit easier. Also, if you follow this the memory address you get will, in all probability, be different.

Now consider this program:

#include <string.h>

void copyinput(char* input)
{
    char buf[10];
    strcpy(buf, input);
}

int main(int argc, char** argv)
{
    int a = 5;
    copyinput(argv[1]);
    a = 7;

    return 0;
}

which we can compile with this command line:

gcc -g -ansi -pedantic -Wall overflow2.c -o overflow

and then run the program under gdb.

We place a break point at `main' and set the command line argument to "AAAAAAAAAABBBBBBBBBBCCCCCCCCCC" and note the following:

  1. first note the disassembly of main:

      0x0040157a <+0>: push %ebp 0x0040157b <+1>: mov %esp,%ebp => 0x0040157d <+3>: and $0xfffffff0,%esp 0x00401580 <+6>: sub $0x20,%esp 0x00401583 <+9>: call 0x401fd0 <__main> 0x00401588 <+14>: movl $0x5,0x1c(%esp) 0x00401590 <+22>: mov 0xc(%ebp),%eax 0x00401593 <+25>: add $0x4,%eax 0x00401596 <+28>: mov (%eax),%eax 0x00401598 <+30>: mov %eax,(%esp) 0x0040159b <+33>: call 0x401560 <copyinput> 0x004015a0 <+38>: movl $0x7,0x1c(%esp) 0x004015a8 <+46>: mov $0x0,%eax 0x004015ad <+51>: leave 0x004015ae <+52>: ret 0x004015af <+53>: nop 

    what we are interested in here is the address of the next instruction after we call copyinput . This will be the value of eip that gets pushed on the stack when control flow is passed to copyinput .

  2. lets look at the registers:

     (gdb) info reg eax 0x1 1 ecx 0x752c1162 1965822306 edx 0xa02080 10494080 ebx 0x2 2 esp 0x28fea0 0x28fea0 ebp 0x28fec8 0x28fec8 esi 0xa01858 10491992 edi 0x1f 31 eip 0x401590 0x401590 <main+22> eflags 0x202 [ IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x53 83 gs 0x2b 43 

    we are interested in esp and ebp from the above. Remember that ebp should also get pushed onto the stack during the function call to copyinput .

  3. Single-step to the invocation of copyinput and then step into that function. At this point, look at the registers (before the call to strcpy ) again:

     (gdb) info reg eax 0x9218b0 9574576 ecx 0x752c1162 1965822306 edx 0x922080 9576576 ebx 0x2 2 esp 0x28fe70 0x28fe70 ebp 0x28fe98 0x28fe98 esi 0x921858 9574488 edi 0x1f 31 eip 0x401566 0x401566 <copyinput+6> eflags 0x202 [ IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x53 83 gs 0x2b 43 

    What we can see here is that the stack frame for copyinput is from 0x28fe70 to 0x28fe98, and referring back to point (2) we can see that the stack frame for main is based at 0x28fec8.

  4. We can examine the stack from 0x28fe70 to 0x28fec8 (a total of 88 bytes) like this:

      (gdb) x/88xb 0x28fe70 0x28fe70: 0x50 0x15 0x40 0x00 0xdc 0x00 0x00 0x00 0x28fe78: 0xff 0xff 0xff 0xff 0x30 0x60 0x44 0x00 0x28fe80: 0x03 0x00 0x00 0x00 0x8c 0xfe 0x28 0x00 0x28fe88: 0x00 0x00 0x00 0x00 0x8f 0x17 0x40 0x00 0x28fe90: 0x50 0x1f 0x40 0x00 0x1c 0x50 0x40 0x00 0x28fe98: 0xc8 0xfe 0x28 0x00 0xa0 0x15 0x40 0x00 0x28fea0: 0xb0 0x18 0x92 0x00 0x00 0x50 0x40 0x00 0x28fea8: 0x88 0xff 0x28 0x00 0xae 0x1f 0x40 0x00 0x28feb0: 0x50 0x1f 0x40 0x00 0x60 0x00 0x00 0x40 0x28feb8: 0x1f 0x00 0x00 0x00 0x05 0x00 0x00 0x00 0x28fec0: 0x58 0x17 0x92 0x00 0x1f 0x00 0x00 0x00 

    The raw memory dump is not very easy to read, so lets collapse the bytes into words, and convert the byte order into big-endian, and we can see where certain values are located at:

     0x28fe70: 0x00401550 <- esp for `copyinput` 0x000000dc 0x28fe78: 0xffffffff 0x00446030 0x28fe80: 0x00000003 0x0028fe8c 0x28fe88: 0x00000000 0x0040178f 0x28fe90: 0x00401f50 0x0040501c 0x28fe98: 0x0028fec8 <- stored *ebp* for `main``s stack frame 0x004015a0 <- stored *eip*, 0x28fea0: 0x009218b0 <- esp for `main``s stack frame 0x00405000 

    So from this we can see that the stored eip is located on the stack at address 0x28fe9C. From this you can see that eip gets pushed onto the stack first then ebp get pushed on the stack.

  5. Now single stepping till after the call to string copy and examining memory again shows:

     (gdb) x/88xb 0x28fe70 0x28fe70: 0x86 0xfe 0x28 0x00 0xb0 0x18 0x92 0x00 0x28fe78: 0xff 0xff 0xff 0xff 0x30 0x60 0x44 0x00 0x28fe80: 0x03 0x00 0x00 0x00 0x8c 0xfe 0x41 0x41 0x28fe88: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x28fe90: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x28fe98: 0x42 0x42 0x43 0x43 0x43 0x43 0x43 0x43 0x28fea0: 0x43 0x43 0x43 0x43 0x00 0x50 0x40 0x00 0x28fea8: 0x88 0xff 0x28 0x00 0xae 0x1f 0x40 0x00 0x28feb0: 0x50 0x1f 0x40 0x00 0x60 0x00 0x00 0x40 0x28feb8: 0x1f 0x00 0x00 0x00 0x05 0x00 0x00 0x00 0x28fec0: 0x58 0x17 0x92 0x00 0x1f 0x00 0x00 0x00 

    and we can see that both the stored values of ebp and eip have been clobbered on the stack. Now when we return from copyinput which will pop the value for eip (which is now 0x43434343) and ebp (which is now 0x43434242) off the stack and attempt to execute the instruction at 0x43434343; which will obviously generate an exception.

The main thrust of a stack attack like this would be to arrange it so that we overwrite eip with a valid value of our choosing. For example, consider the following program:

#include <stdio.h>
#include <string.h>

void copyinput(char* input)
{
   char buf[10];
   strcpy(buf, input);
}

void testinput()
{
    printf("we should never see this\n");
}

int main(int argc, char** argv)
{
    int a = 5;
    copyinput(argv[1]);
    a = 7;

   return 0;
}

The function testinput is never called. However if we can overwrite the return address in copyinput with the value of 0x0040157a (which is the location of testinput on my machine) we would be able to cause that function to execute.

================================================================================= answers for questions made in the comments:

Not sure what OS/compiler you are using. I took your sample program compiled it using gcc-4.8.3 on a Windows 7 box. My disassembly for main looks like this:

(gdb) disass main
Dump of assembler code for function main:
   0x00401560 <+0>: push   %ebp
   0x00401561 <+1>: mov    %esp,%ebp
   0x00401563 <+3>: and    $0xfffffff0,%esp
   0x00401566 <+6>: sub    $0x20,%esp
   0x00401569 <+9>: call   0x401fc0 <__main>

This is the preamble for main in which we are setting up the stack frame for main. We push the base-pointer of the previous stack frame (from some function provided by the run-time library), then move the the base pointer to where the stack point is. Next with we adjust esp to make it evenly divisible by 16 and then we subtract 32 bytes (0x20) from esp (remember that the stack grows down, so we now have some space that main is going to use.

The common pattern of push %ebp , mov %esp, %ebp and then sub xxx, %esp is a common preamble for a function.

Lets try to find where things are located in memory, shall we. In gdb we can do the following:

(gdb) x/16xb &argv[0]
0xa31830:   0x58    0x18    0xa3    0x00    0x98    0x18    0xa3    0x00
0xa31838:   0x00    0x00    0x00    0x00    0xab    0xab    0xab    0xab

Which is what we expect, two 32-bit pointers followed by a null terminator. So argv[0] is located at 0x00a31858 and argv 1 is located at 0x00a31898; which can be seen by examining the memor at these two locations:

(gdb) x/20cb 0x00a31858
0xa31858:   100 'd' 58 ':'  92 '\\' 117 'u' 115 's' 101 'e' 114 'r' 115 's'
0xa31860:   92 '\\' 103 'g' 104 'h' 117 'u' 98 'b'  101 'e' 114 'r' 92 '\\'
0xa31868:   71 'G'  78 'N'  85 'U'  72 'H'
(gdb) x/20xb 0x00a31898
0xa31898:   0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xa318a0:   0x41    0x41    0x00    0xab    0xab    0xab    0xab    0xab
0xa318a8:   0xab    0xab    0xab    0xfe

We can find out where our buffer is located but doing the following in GDB:

(gdb) print $esp
$4 = (void *) 0x28fea0
(gdb) print $ebp
$5 = (void *) 0x28fec8
(gdb) x/40xb $esp
0x28fea0:   0xb6    0xfe    0x28    0x00    0x98    0x18    0xa3    0x00
0x28fea8:   0x88    0xff    0x28    0x00    0x9e    0x1f    0x40    0x00
0x28feb0:   0x40    0x1f    0x40    0x00    0x60    0x00    0x41    0x41
0x28feb8:   0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x28fec0:   0x00    0x17    0xa3    0x00    0x0b    0x00    0x00    0x00

So we can see that our buffer starts at 0x28feb6

Now that we have that out of the way, lets looks at the next section of our code, which should be setting up for the call to strcpy :

   0x0040156e <+14>:    mov    0xc(%ebp),%eax
   0x00401571 <+17>:    add    $0x4,%eax
   0x00401574 <+20>:    mov    (%eax),%eax
   0x00401576 <+22>:    mov    %eax,0x4(%esp)
   0x0040157a <+26>:    lea    0x16(%esp),%eax
   0x0040157e <+30>:    mov    %eax,(%esp)
   0x00401581 <+33>:    call   0x402748 <strcpy>

As a reminder, in AT&T assembly syntax the address operand looks like this:

displacement(base register, offset register, scalar multiplier)

which is equivalent to the intel syntax:

[base register + displacement + offset register * scalar multiplier]

So with that,

   0x0040156e <+14>:    mov    0xc(%ebp),%eax
   0x00401571 <+17>:    add    $0x4,%eax
   0x00401574 <+20>:    mov    (%eax),%eax
   0x00401576 <+22>:    mov    %eax,0x4(%esp)

We add 0x0C to our current base pointer which give a value of 0x28FED4, and we then copy what that contained at that memory address to eax . By using GDB, we can find out that the four bytes located at 0x08FEC4 is 0x00a31830 which is the address of argv[0]. Adding four to eax causes eax to now point to argv 1 . The next two instructions effectively move the address of argv 1 to four bytes above esp .

   0x0040157a <+26>:    lea    0x16(%esp),%eax
   0x0040157e <+30>:    mov    %eax,(%esp)

Continuing along, we increment esp by 0x16 (which gives us 0x28FEB6, which we have previously determined to be where buf[10] is located. We then move this value to where esp is at. At this time, our stack now looks like:

           ~            ~
           |            |
           +------------+
0x28fea4   | 0x00a31898 |    remember that this is the address of argv[1][0]
           +------------+
0x28fea0   | 0x0028feb6 |    remember that this is the address of buf[0]
           +------------+

Which makes sense given that the function prototype for strcpy which is:

    char*  strcpy(char* dst, const char* src);

And that typically, arguments are pushed onto the stack from right to left, so we would expect that the src gets pushed on first and then dst would get pushed on second. So instead of just pushing the arguments onto the stack, the compiler set aside enough space so that it could load the required values at the correct place. So everything is in place and we can now call strcpy .

The next few instructions just set up the call to printf (well actually puts ), we need to move the address of the string "Done.\\n" onto the stack and then call puts :

   0x00401586 <+38>:    movl   $0x404024,(%esp)
   0x0040158d <+45>:    call   0x402750 <puts>

Finally, we move the return value into eax (which is the register that normally contains the return value from a function) and then we exit main .

   0x00401592 <+50>:    mov    $0x0,%eax
   0x00401597 <+55>:    leave  
   0x00401598 <+56>:    ret 

Not sure if I answered all of your questions, but I think I did. Also I hope I didn't screw up the analysis too much, I don't normally do that in depth analysis of assembly or use AT&T syntax.

=============== edit2 ===================================

Three remaining questions:

Is the value in line +7 unnecessary? I don't see any use for it, so why is it stored?

Your analysis that we are pushing the original, un-aligned value of esp appears correct. My hunch is that in the first few lines of your disassembly we are looking at special start-up code for main. Remember that there was a stack frame on the stack prior to the creation of the stack frame for main. You may want to take a look at this link to see what the normal start-up order of a program under Linux is.

My hunch is that we need to preserve the unmodified value of esp so that we can restore an earlier stack frame to its correct location.

In some places sp moves more than it has to - is it due to alignment? (eg line +14)

I would make the analysis that these lines are where we are actually setting up the stack frame for main. In main+14 we are subtracting 20 bytes from esp so we are allocating 20 bytes for use by our main function. We can argue that 12 of those bytes are used by our buffer (remember that there probably will be two bytes of padding at the end of our buffer so that the next value stored on the stack will be at a 32-bit word boundary).

   0x08048435 <+10>:    push   %ebp
   0x08048436 <+11>:    mov    %esp,%ebp
   0x08048438 <+13>:    push   %ecx
   0x08048439 <+14>:    sub    $0x14,%esp

So, I would claim that main+10 through main+14 are the normal function prolog

Is my conclusion over line +71 correct?

Yes. At this point we need to have overwritten the stored eip on the stack and this will cause the RET instruction to read our value. The following description of the RET instruction is taken from from here (actually this page has a good deal of information on assembly it is well worth reading. The only down side is that this page uses the Intel syntax and you have been presenting AT&T syntax.)

call, ret — Subroutine call and return

These instructions implement a subroutine call and return. The call instruction first pushes the current code location onto the hardware supported stack in memory (see the push instruction for details), and then performs an unconditional jump to the code location indicated by the label operand. Unlike the simple jump instructions, the call instruction saves the location to return to when the subroutine completes.

The ret instruction implements a subroutine return mechanism. This instruction > first pops a code location off the hardware supported in-memory stack (see the pop instruction for details). It then performs an unconditional jump to the retrieved code location.

 Syntax call <label> ret 

Additional information on the LEAVE instruction (used at main+67 ) is (taken from here ):

Releases the stack frame set up by an earlier ENTER instruction. The LEAVE instruction copies the frame pointer (in the EBP register) into the stack pointer register (ESP), which releases the stack space allocated to the stack frame. The old frame pointer (the frame pointer for the calling procedure that was saved by the ENTER instruction) is then popped from the stack into the EBP register, restoring the calling procedure's stack frame.

A RET instruction is commonly executed following a LEAVE instruction to return program control to the calling procedure.

See "Procedure Calls for Block-Structured Languages" in Chapter 6 of the IA-32 Intel Architecture Software Developer's Manual, Volume 1, for detailed information on the use of the ENTER and LEAVE instructions.

NB it is possible to change the flavor of the disassembly emitted by GDB by using the following commands:

set disassembly-flavor att
set disassembly-flavor intel
show disassembly-flavor

The third command shows what the current flavor is.

PS I second Jesters comment in his answer below. Moving the actual vulnerable code off to a function rather then in main will make the analysis easier as you don't have to deal with the weirdness of the alignment of main and the unique prolog and epilog for main. Once you've gotten a handle on this type of stack exploitation you can then go back and work through an example where the vulnerability is in main.

PPS working on a Linux system you may also run into ASLR issues, in that every time you run a program things are a different memory locations, so the offsets between stack frames and stack frame locations will change. You can use the following short program (taken from The Shellcoder's Handbook: Discovering and Exploiting Security Holes by Chris Anley, et.al ) to see if ASLR is an issue

    #include <stdio.h>
    unsigned long find_start(void)
    {
        __asm__("movl %esp, %eax");
    }

    int main()
    {
        printf("0x%x\n", find_start());
        return (0);
    }

Run the program several times, if the output differs you have some version of ASLR running. It will make your life more difficult, but not insurmountable

You are first overwriting locals on the stack, which includes a saved copy of ecx that the compiler used to remember the stack pointer. So by the time the code gets to 0x0804846b the value on the stack is clobbered so ecx is loaded with a wrong value. You can see that in your register dump, it's 0x41414141 . Next, esp is loaded based on ecx , so esp gets a wrong value too. Finally, the ret tries to pop the return address from the stack, which of course uses esp but that has bad value as we have seen above. Thus, crash.

Note that this code is normally only generated for main for alignment purposes, so you might have better luck if you stick your code into a separate function that you just call from main .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM