粉碎堆栈example3.c混乱

文章可以在这里找到。

我正在阅读粉碎堆栈并发现自己被卡在example3.c上。

0x80004a3 : call 0x8000470  0x80004a8 : addl $0xc,%esp 0x80004ab : movl $0x1,0xfffffffc(%ebp) 0x80004b2 : movl 0xfffffffc(%ebp),%eax 

作者指出我们想要从0x80004a8跳到0x80004b2并且这个跳转是8个字节; 作者如何确定这是8个字节? 我重新创建了代码并通过objdump发送它,发现它不是8个字节(我在64位机器上,但我确保使用32位编译):

 8048452: e8 b5 ff ff ff call 804840c  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) 

作者还说“ 我们怎么知道在返回地址中添加8?我们首先使用测试值(例如1) ”他在哪里使用此测试值?

这不是我解释这篇文章的方式。 我理解它的方式他想修改返回地址,使x = 1; 跳过赋值,即他希望function返回到printf将被执行的位置。

正如你在反汇编中看到的那样,赋值是8个字节( c7 44 24 1c 01 00 00 00 ),因此将返回地址向前移动8个字节将使它超过该指令。 至于“我们首先使用测试值”评论……也许他只是意味着他在反汇编程序中查看代码来计算长度,或者他尝试了不同的偏移量(?)。

文章中的位移是错误的,它应该是10个字节。 调用函数(或执行跳转)时,返回地址设置为等于指令指针+当前指令大小:

 ret = IP + Curr_Inst_size 

因此,当对函数的调用返回时,指令指针应该等于0x80004a80x80004a3 +调用指令大小):

  0x80004a3 : call 0x8000470  --> 0x80004a8 : addl $0xc,%esp 0x80004ab : movl $0x1,0xfffffffc(%ebp) 0x80004b2 : movl 0xfffffffc(%ebp),%eax 

但是,你想要将指令指针设置为0x80004b2为了跳过赋值 ,你也不可避免地要跳过另一条指令(addl $0xc,%esp)到达那里,换句话说,你需要添加(0x80004b2-0x80004a8)字节或10个字节,指令指针跳过这两个指令:

  0x80004a3 : call 0x8000470  0x80004a8 : addl $0xc,%esp 0x80004ab : movl $0x1,0xfffffffc(%ebp) --> 0x80004b2 : movl 0xfffffffc(%ebp),%eax 

实际的指令大小取决于操作数,机器类型等。但是在这个例子中, addl是3个字节长,而movl是7个字节长。 您可以检查x86指令集参考以获得确切的指令大小,或者您可以编译和反汇编此代码,您将看到这两个指令长度为10个字节:

 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) 

这里和这里还讨论了关于示例3中的完全相同的问题。

经过很长时间和思考,我终于解决了这个问题。 帮助我解决这个问题的资源是Stack smashing代码无法在Linux内核2.6.38.7上运行…请帮忙

帮助我解决这个问题的最大变化是为gdb使用disassembly-flavor intel

反汇编代码(供参考):

 0804840c : 804840c: 55 push ebp 804840d: 89 e5 mov ebp,esp 804840f: 83 ec 10 sub esp,0x10 8048412: 8d 45 f7 lea eax,[ebp-0x9] 8048415: 83 c0 14 add eax,0x14 8048418: 89 45 fc mov DWORD PTR [ebp-0x4],eax 804841b: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 804841e: 8b 00 mov eax,DWORD PTR [eax] 8048420: 8d 50 05 lea edx,[eax+0x5] 8048423: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 8048426: 89 10 mov DWORD PTR [eax],edx 8048428: c9 leave 8048429: c3 ret 0804842a 
: 804842a: 55 push ebp 804842b: 89 e5 mov ebp,esp 804842d: 83 e4 f0 and esp,0xfffffff0 8048430: 83 ec 20 sub esp,0x20 8048433: c7 44 24 1c 00 00 00 mov DWORD PTR [esp+0x1c],0x0 804843a: 00 804843b: c7 44 24 08 03 00 00 mov DWORD PTR [esp+0x8],0x3 8048442: 00 8048443: c7 44 24 04 02 00 00 mov DWORD PTR [esp+0x4],0x2 804844a: 00 804844b: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 8048452: e8 b5 ff ff ff call 804840c 8048457: c7 44 24 1c 01 00 00 mov DWORD PTR [esp+0x1c],0x1 804845e: 00 804845f: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c] 8048463: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 8048467: c7 04 24 18 85 04 08 mov DWORD PTR [esp],0x8048518 804846e: e8 7d fe ff ff call 80482f0 8048473: c9 leave 8048474: c3 ret 8048475: 66 90 xchg ax,ax 8048477: 66 90 xchg ax,ax 8048479: 66 90 xchg ax,ax 804847b: 66 90 xchg ax,ax 804847d: 66 90 xchg ax,ax 804847f: 90 nop

我对此的理解存在两个问题:

A)我的第一个问题是在function内找到超出ret的字节function 。 再次; 我通过使用intel语法进行反汇编来做到这一点,并发现:

要将ret设置在内存中的正确空间,需要在调用函数时将其设置为EIP8048412处的地址空间8048412移动到堆栈0x9 。 因为这是32位代码; 为了得到ret我们然后为字大小添加额外的0x4字节。 要进入ret ,这意味着ret设置为0x9 + 0x4 ,十进制为13。

这解决了第一个进入ret问题。

B)第二个问题是跳过0x8048457内存位置。 这是通过向(*ret)添加7个字节来完成的,这使得程序跳过并在0x804845e执行,即00NUL )。 这只是效率低下; 所以我添加了额外的字节并在堆栈中执行了8个字节; 从而导致x = 0 ;

我发现确切的字节数为8( c7 44 24 1c 01 00 00是7字节), 00是一个字节。 这解决了我的最后一个问题

我修改过的C代码:

 void function(int a, int b, int c) { char buffer1[5]; int *ret; ret = buffer1 + 13; //tried 11, 14, 20, 40, 38, 43 (*ret) += 8; // tried 5, 8, 12; 8 is correct value! } void main() { int x; x = 0; function(1,2,3); x = 1; printf("%d\n", x); }