-->

Smashing the stack example3.c confusion

2020-07-06 08:08发布

问题:

Article can be found here.

I'm reading up on smashing the stack and have found myself to be getting stuck on example3.c.

0x80004a3 <main+19>:    call   0x8000470 <function>
0x80004a8 <main+24>:    addl   $0xc,%esp
0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
0x80004b2 <main+34>:    movl   0xfffffffc(%ebp),%eax

The author indicates that we want to skip from 0x80004a8 to 0x80004b2 and that this jump is 8 bytes; how has the author determined this is 8 bytes? I have recreated the code and sent it through objdump and found that it's not 8 bytes (I am on a 64 bit machine but I've made sure to compile using 32 bit):

8048452:    e8 b5 ff ff ff          call   804840c <function>
8048457:    c7 44 24 1c 01 00 00    movl   $0x1,0x1c(%esp)
804845e:    00 
804845f:    8b 44 24 1c             mov    0x1c(%esp),%eax
8048463:    89 44 24 04             mov    %eax,0x4(%esp)
8048467:    c7 04 24 18 85 04 08    movl   $0x8048518,(%esp)

The author also said "How did we know to add 8 to the return address? We used a test value first (for example 1)" Where did he use this test value at?

回答1:

That's not how I interpret the article. The way I understand it he wants to modify the return address so that the x = 1; assignment is skipped, i.e. he wants function to return to where the printf would be executed.

As you can see in your disassembly, the assignment is 8 bytes (c7 44 24 1c 01 00 00 00), hence moving the return address 8 bytes forward would move it past this instruction. As for the "We used a test value first" comment.. maybe he just means that he looked at the code in a disassembler to figure out the length, or that he experimented with different offsets(?).



回答2:

The displacement in the article is wrong, it should be 10 bytes. When a function is called (or a jump is executed), the return address is set to equal the instruction pointer + the current instruction size:

ret = IP + Curr_Inst_size

So when the call to the function returns, the instruction pointer should equal 0x80004a8 (0x80004a3 + the call instruction size):

    0x80004a3 <main+19>:    call   0x8000470 <function>
--> 0x80004a8 <main+24>:    addl   $0xc,%esp            
    0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
    0x80004b2 <main+34>:    movl   0xfffffffc(%ebp),%eax

However, you want to set the instruction pointer to 0x80004b2 instead, to skip the assignment, you also, inevitably, have to skip another instruction (addl $0xc,%esp) to get there, or in other words, you need to add (0x80004b2-0x80004a8) bytes, or 10 bytes, to the instruction pointer to skip those two instructions:

    0x80004a3 <main+19>:    call   0x8000470 <function>
    0x80004a8 <main+24>:    addl   $0xc,%esp            
    0x80004ab <main+27>:    movl   $0x1,0xfffffffc(%ebp)
--> 0x80004b2 <main+34>:    movl   0xfffffffc(%ebp),%eax 

The actual instruction size depends on the operands, machine type etc.. But in this example, the addl is 3 bytes long, and the movl is 7 bytes long. You could check the x86 Instruction Set Reference for the exact instruction size, or you could compile and disassemble this code, you will see that those two instructions are 10 bytes long:

int main()
{
    asm("addl  $0xc,%esp\n\
         movl  $0x1,0xfffffffc(%ebp)");

}

gdb:

0x08048397 <+3>: 83 c4 0c               add    $0xc,%esp
0x0804839a <+6>: c7 45 fc 01 00 00 00   movl   $0x1,-0x4(%ebp)

There's also a discussion here and here about this exact same issue in example 3.