粉碎堆栈 example3 ala Aleph One
Smashing the stack example3 ala Aleph One
我在 Linux x86_64 上复制了 Smashing the Stack for Fun and Profit 中的示例 3。但是我无法理解应该增加到 return 地址以跳过指令的正确字节数是多少:
0x0000000000400595 <+35>: movl [=10=]x1,-0x4(%rbp)
这是我认为 x = 1
指令所在的位置。我写了以下内容:
#include <stdio.h>
void fn(int a, int b, int c) {
char buf1[5];
char buf2[10];
int *ret;
ret = buf1 + 24;
(*ret) += 7;
}
int main() {
int x;
x = 0;
fn(1, 2, 3);
x = 1;
printf("%d\n", x);
}
并在 gdb 中对其进行反汇编。我已禁用地址随机化并使用 -fno-stack-protector
选项编译程序。
问题 1
我可以从下面的反汇编程序输出中看到我想跳过地址 0x0000000000400595
处的指令:来自 callq <fn>
的 return 地址和 movl
指令。因此,如果return地址是0x0000000000400595
,下一条指令是0x000000000040059c
,我应该在return地址上加7个字节?
0x0000000000400572 <+0>: push %rbp
0x0000000000400573 <+1>: mov %rsp,%rbp
0x0000000000400576 <+4>: sub [=12=]x10,%rsp
0x000000000040057a <+8>: movl [=12=]x0,-0x4(%rbp)
0x0000000000400581 <+15>: mov [=12=]x3,%edx
0x0000000000400586 <+20>: mov [=12=]x2,%esi
0x000000000040058b <+25>: mov [=12=]x1,%edi
0x0000000000400590 <+30>: callq 0x40052d <fn>
0x0000000000400595 <+35>: movl [=12=]x1,-0x4(%rbp)
0x000000000040059c <+42>: mov -0x4(%rbp),%eax
0x000000000040059f <+45>: mov %eax,%esi
0x00000000004005a1 <+47>: mov [=12=]x40064a,%edi
0x00000000004005a6 <+52>: mov [=12=]x0,%eax
0x00000000004005ab <+57>: callq 0x400410 <printf@plt>
0x00000000004005b0 <+62>: leaveq
0x00000000004005b1 <+63>: retq
问题 2
我注意到我可以将 5 个字节添加到 return 地址而不是 7 个字节并获得相同的结果。当我这样做的时候,我是不是跳到指令的中间0x0000000000400595 <+35>: movl [=20=]x1,-0x4(%rbp)
?在这种情况下,为什么这不会使程序崩溃,例如当我向 return 地址添加 6 个字节而不是 5 个字节或 7 个字节时。
问题 3
Just before buffer1[] on the stack is SFP, and before it, the return address.
That is 4 bytes pass the end of buffer1[]. But remember that buffer1[] is
really 2 word so its 8 bytes long. So the return address is 12 bytes from
the start of buffer1[].
在 Aleph 1 的示例中,he/she 将 return 地址的偏移量计算为距 buffer1[] 开头的 12 个字节。由于我在 x86_64 而不是 x86_32,因此我需要重新计算 return 地址的偏移量。在x86_64的时候,buffer1[]是不是还是2个字,也就是16个字节; SFP 和 return 地址各为 8 个字节(因为我们是 64 位的),因此 return 地址位于:buf1 + (8 * 2) + 8
,相当于 buf1 + 24
?
首先要注意的也是非常重要的一点:所有数字和偏移量都非常依赖于编译器。不同的编译器,甚至具有不同设置的同一个编译器,都可能产生截然不同的程序集。例如,许多编译器可以(并且将会)删除 buf2
因为它没有被使用。他们还可以删除 x = 0
,因为它的效果未被使用并在以后被覆盖。他们还可以删除 x = 1
并将所有出现的 x
替换为常量 1
,等等
也就是说,您绝对需要为您在特定编译器及其设置上获得的特定程序集编号。
问题 1
由于您为 main()
提供了程序集,我可以确认您需要向 return 地址添加 7 个字节,通常为 0x0000000000400595
,以跳过 x=1
和转到 0x000000000040059c
,它将 x
加载到寄存器中供以后使用。 0x000000000040059c - 0x0000000000400595 = 7
.
问题 2
仅添加 5 个字节而不是 7 个字节确实会跳转到指令的中间。然而,这个 2 字节的指令尾恰好(纯属偶然)是另一个有效的指令代码。这就是它不会崩溃的原因。
问题 3
这又非常依赖于编译器和设置。几乎所有事情都可能在那里发生。由于你没有提供反汇编,我只能猜测。猜测如下:buf
和 buf2
向上舍入到下一个堆栈单元边界(x64 上为 8 个字节)。 buf
变成8个字节,buf2
变成16个字节。帧指针不会保存到 x64 上的堆栈,因此没有 "SFP"。总共 24 个字节。
我在 Linux x86_64 上复制了 Smashing the Stack for Fun and Profit 中的示例 3。但是我无法理解应该增加到 return 地址以跳过指令的正确字节数是多少:
0x0000000000400595 <+35>: movl [=10=]x1,-0x4(%rbp)
这是我认为 x = 1
指令所在的位置。我写了以下内容:
#include <stdio.h>
void fn(int a, int b, int c) {
char buf1[5];
char buf2[10];
int *ret;
ret = buf1 + 24;
(*ret) += 7;
}
int main() {
int x;
x = 0;
fn(1, 2, 3);
x = 1;
printf("%d\n", x);
}
并在 gdb 中对其进行反汇编。我已禁用地址随机化并使用 -fno-stack-protector
选项编译程序。
问题 1
我可以从下面的反汇编程序输出中看到我想跳过地址 0x0000000000400595
处的指令:来自 callq <fn>
的 return 地址和 movl
指令。因此,如果return地址是0x0000000000400595
,下一条指令是0x000000000040059c
,我应该在return地址上加7个字节?
0x0000000000400572 <+0>: push %rbp
0x0000000000400573 <+1>: mov %rsp,%rbp
0x0000000000400576 <+4>: sub [=12=]x10,%rsp
0x000000000040057a <+8>: movl [=12=]x0,-0x4(%rbp)
0x0000000000400581 <+15>: mov [=12=]x3,%edx
0x0000000000400586 <+20>: mov [=12=]x2,%esi
0x000000000040058b <+25>: mov [=12=]x1,%edi
0x0000000000400590 <+30>: callq 0x40052d <fn>
0x0000000000400595 <+35>: movl [=12=]x1,-0x4(%rbp)
0x000000000040059c <+42>: mov -0x4(%rbp),%eax
0x000000000040059f <+45>: mov %eax,%esi
0x00000000004005a1 <+47>: mov [=12=]x40064a,%edi
0x00000000004005a6 <+52>: mov [=12=]x0,%eax
0x00000000004005ab <+57>: callq 0x400410 <printf@plt>
0x00000000004005b0 <+62>: leaveq
0x00000000004005b1 <+63>: retq
问题 2
我注意到我可以将 5 个字节添加到 return 地址而不是 7 个字节并获得相同的结果。当我这样做的时候,我是不是跳到指令的中间0x0000000000400595 <+35>: movl [=20=]x1,-0x4(%rbp)
?在这种情况下,为什么这不会使程序崩溃,例如当我向 return 地址添加 6 个字节而不是 5 个字节或 7 个字节时。
问题 3
Just before buffer1[] on the stack is SFP, and before it, the return address. That is 4 bytes pass the end of buffer1[]. But remember that buffer1[] is really 2 word so its 8 bytes long. So the return address is 12 bytes from the start of buffer1[].
在 Aleph 1 的示例中,he/she 将 return 地址的偏移量计算为距 buffer1[] 开头的 12 个字节。由于我在 x86_64 而不是 x86_32,因此我需要重新计算 return 地址的偏移量。在x86_64的时候,buffer1[]是不是还是2个字,也就是16个字节; SFP 和 return 地址各为 8 个字节(因为我们是 64 位的),因此 return 地址位于:buf1 + (8 * 2) + 8
,相当于 buf1 + 24
?
首先要注意的也是非常重要的一点:所有数字和偏移量都非常依赖于编译器。不同的编译器,甚至具有不同设置的同一个编译器,都可能产生截然不同的程序集。例如,许多编译器可以(并且将会)删除 buf2
因为它没有被使用。他们还可以删除 x = 0
,因为它的效果未被使用并在以后被覆盖。他们还可以删除 x = 1
并将所有出现的 x
替换为常量 1
,等等
也就是说,您绝对需要为您在特定编译器及其设置上获得的特定程序集编号。
问题 1
由于您为 main()
提供了程序集,我可以确认您需要向 return 地址添加 7 个字节,通常为 0x0000000000400595
,以跳过 x=1
和转到 0x000000000040059c
,它将 x
加载到寄存器中供以后使用。 0x000000000040059c - 0x0000000000400595 = 7
.
问题 2 仅添加 5 个字节而不是 7 个字节确实会跳转到指令的中间。然而,这个 2 字节的指令尾恰好(纯属偶然)是另一个有效的指令代码。这就是它不会崩溃的原因。
问题 3
这又非常依赖于编译器和设置。几乎所有事情都可能在那里发生。由于你没有提供反汇编,我只能猜测。猜测如下:buf
和 buf2
向上舍入到下一个堆栈单元边界(x64 上为 8 个字节)。 buf
变成8个字节,buf2
变成16个字节。帧指针不会保存到 x64 上的堆栈,因此没有 "SFP"。总共 24 个字节。