Reputation: 460
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
Upvotes: 2
Views: 3354
Reputation: 2528
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:
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
.
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
.
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.
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.
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 argv1 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 argv1. The next two instructions effectively move the address of argv1 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? (e.g. 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.
N.B. 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
Upvotes: 6
Reputation: 58782
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
.
Upvotes: 3