简体   繁体   中英

Reserved stack space in assembly doesn' t seem to match the c code

I have the following code example:

int main(int argc, char **argv) {
    char *name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
    exit(0);
}

Disassembling the program leads to something similar to:

1.  0x08048250 <main+0>: push %ebp
2.  0x08048251 <main+1>: mov %esp,%ebp
3.  0x08048253 <main+3>: and $0xfffffff0,%esp
4.  0x08048256 <main+6>: sub $0x20,%esp
5.  0x08048259 <main+9>: movl $0x80a6f68,0x18(%esp)
6.  0x08048261 <main+17>: movl $0x0,0x1c(%esp)
7.  0x08048269 <main+25>: mov 0x18(%esp),%eax
8.  0x0804826d <main+29>: movl $0x0,0x8(%esp)
9.  0x08048275 <main+37>: lea 0x18(%esp),%edx
10. 0x08048279 <main+41>: mov %edx,0x4(%esp)
11. 0x0804827d <main+45>: mov %eax,(%esp)
12. 0x08048280 <main+48>: call 0x804f5c0 <execve>
13. 0x08048285 <main+53>: movl $0x0,(%esp)
14. 0x0804828c <main+60>: call 0x8048af0 <exit>

I am trying to understand the assembly:

On line 4 the stackpointer is decreased to allocate space for the local variables, but I don't understand why it reserves 32 bytes (0x20=32 bytes)? As I understand it should only have to allocate:

  • 4 bytes for the pointer "name"
  • 8 bytes for the string "/bin/sh";
  • Does NULL also take up space? If so it is equivalent to 0x0 of 4 bytes right?
  • The argc and argv are not part of this stackframe right? they are pushed by mains caller and as such don't need to be reserved in this stackframe.

Also I see some data that is being stored at an offset from the stack pointer but it doesn't seem like all the space is being used.

Could someone maybe explain this piece of assembly? I am having troubles mapping the c code to the given assembly instructions. Especially since the lengths of the reserved space doesn' t seem to match with the c code.

I am particularly interested in:

  • Line 3: What is this for?
  • Line 9: what does this do?
  • Line 11 and 13: what does the (%esp) syntax mean?

Thanks!

Regarding "it should only have to allocate":

  • "4 bytes for the pointer name" - not quite. name is an array of two pointers, so 2*4 = 8 bytes (assuming that you're using 32-bit pointers, which is what it looks like).

  • "8 bytes for the string "/bin/sh" - this won't be on the stack. It'll be elsewhere in the binary (probably the .rodata segment, ie read-only data), so doesn't take up any stack space.

  • "Does NULL also take up space?" - NULL is (probably, unless you have a perverse C compiler) the value 0. A "value" can't in itself take up stack space, but if there's a variable on the stack with value NULL then you'll find zero on the stack.

  • "The argc and argv [etc]" - these are arguably part of the stack frame for this function call, but are initialised by the caller, so aren't reserved by decreasing %esp.

"doesn't seem like all the space is being used" - correct in general, due to alignment. Consider:

struct {
    char ch;
    int *ptr;
};

For this struct case we'll have a one byte char, then three bytes of padding in order to align ptr correctly, then four bytes of ptr. (Or 7 bytes of padding if pointers are 64 bits.) If we allocate one of these structures on the stack then we'll have three (or 7) "unused" stack bytes.

However, in this case the compiler is creating room on the stack for the arguments to the execve() call within the initial "sub ... %esp" instruction, which is a slight optimisation.

Line 3: alignment: align %esp on a 16-byte boundary. (Should be enough for anybody.) Line 4: create some stack space. Some of this is the local variables; some other bytes of it are used for scratch space by the compiler when putting together function calls.

Line 9: this (effectively) puts the string "/bin/sh" into %edx. The address of that string is at location 0x80a6f68 in the binary (which will be where .rodata is loaded). Line 5 puts the value 0x80a6f68 into (%esp + 0x18). Line 9 puts *(%esp + 0x18), ie *(char **)0x80a6f68, into %edx.

Line 11: (%esp) is effectively pointer deference: *esp = eax. It puts the value of the eax register into *esp, ie the four bytes immediately above the stack pointer. The value of eax was defined on line 7.

Line 13: this sets *esp to 0x00000000. Since we then call exit(), this sets exit's first argument (ie *esp) to 0.

name is an array of 2 pointers. This will take 8 or 16 bytes depending on whether your platform is 32 bit or 64 bit.

The string /bin/sh will not be on the heap at all but will be in the initialized data segment; for completeness yes the \\0 does need space.

argc and argv will be passed on the stack as they are parameters.

Line 3 is aligning the stack pointer to be a multiple of 16 bytes

Line 19 is effectively say 'let edx be 0x18 plus the value of esp '; lea is 'load effective address'; think of it like a mov but rather than do the memory load it returns the address it would have loaded the memory from.

On line 11 and 13, the (esp) means the contents of the location pointed to by esp .

I will admit my assembler is a little rusty.

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