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?
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(?).
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.