可以找到文章here。
我正在阅读粉碎堆栈并发现自己被卡在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
作者表示我们想要从0x80004a8
跳到0x80004b2
并且这个跳转是8个字节;作者如何确定这是8个字节?我重新创建了代码并通过objdump
发送它,发现它不是8个字节(我在64位机器上,但我确保使用32位编译):
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)
作者还说“我们怎么知道在返回地址中添加8?我们用了一个 首先测试值(例如1)“他在哪里使用此测试值?
答案 0 :(得分:3)
这不是我对文章的解释。我理解它的方式是他想要修改返回地址,以便跳过x = 1;
赋值,即他希望function
返回到printf
将被执行的位置。
正如你在反汇编中看到的那样,赋值是8个字节(c7 44 24 1c 01 00 00 00
),因此将返回地址向前移动8个字节将使它超过该指令。至于“我们首先使用了测试值”评论......也许他只是意味着他在反汇编程序中查看代码来计算长度,或者他尝试了不同的偏移量(?)。
答案 1 :(得分:1)
文章中的位移是错误的,它应该是10
个字节。调用函数(或执行跳转)时,返回地址设置为等于指令指针+当前指令大小:
ret = IP + Curr_Inst_size
因此,当对函数的调用返回时,指令指针应该等于0x80004a8
(0x80004a3
+调用指令大小):
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
但是,您希望将指令指针设置为0x80004b2
,以跳过作业,您也不可避免地必须跳过另一条指令(addl $0xc,%esp)
才能到达那里或者换句话说,您需要将(0x80004b2-0x80004a8)
个字节或10个字节添加到指令指针以跳过这两个指令:
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
实际指令大小取决于操作数,机器类型等。但在本例中,addl
长3个字节,movl
长7个字节。您可以检查x86 Instruction Set Reference的确切指令大小,或者您可以编译和反汇编此代码,您将看到这两个指令长度为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)
答案 2 :(得分:-1)
帮助我解决此问题的最大变化是将disassembly-flavor intel
用于gdb
。
反汇编代码(供参考):
0804840c <function>:
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 <main>:
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 <function>
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 <printf@plt>
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)我的第一个问题是在ret
内找到超出function
的字节数。再次;我通过使用intel语法进行反汇编来实现这一点,并发现:
要将ret
设置在内存ret
中的正确空间,需要在调用函数时将其设置为EIP
。 8048412
处的地址空间向下移动到堆栈0x9
。因为这是32位代码;要到达ret
,我们会为字大小添加额外的0x4
个字节。要转到ret
,这意味着ret
设置为0x9 + 0x4
,十进制为13。
这解决了到达ret
的第一个问题。
B)第二个问题是跳过0x8048457
内存位置。这是通过向(*ret)
添加7个字节来完成的,这使得程序跳过并在0x804845e
执行00
(NUL
)。这只是效率低下;所以我添加了额外的字节并在堆栈中执行了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);
}