使用缓冲区溢出漏洞修改C函数的返回地址

3
我正在尝试修改以下C程序,使得主函数跳过printf("x is 1")语句,只打印"x is 0"。
 void func(char *str) {
         char buffer[24];
         int *ret;

         ret = buffer + 28; // Supposed to set ret to the return address of func
         (*ret) += 32; // Add the offset needed so that func will skip over printf("x is 1")
         strcpy(buffer, str);
 }

 int main(int argc, char **argv) {
         int x;
         x = 0;
         func(argv[1]);
         x = 1;
         printf("x is 1");
         printf("x is 0");
         getchar();
 }

正如评论所示,ret指针需要首先设置为函数的返回地址。然后我需要添加一个偏移量,将其推移到我想要跳过的行。我正在使用2 x Intel(R) Xeon(TM) CPU 3.20GHz的Linux系统运行此代码。我使用gcc版本4.7.2 (Debian 4.7.2-5)进行编译。我还尝试使用此链接(http://insecure.org/stf/smashstack.html)中的example3.c作为参考。以下是使用gdb对主函数进行反汇编的结果:

Dump of assembler code for function main:
    0x0000000000400641 <+0>:     push   %rbp
    0x0000000000400642 <+1>:     mov    %rsp,%rbp
    0x0000000000400645 <+4>:     sub    $0x20,%rsp
    0x0000000000400649 <+8>:     mov    %edi,-0x14(%rbp)
    0x000000000040064c <+11>:    mov    %rsi,-0x20(%rbp)
    0x0000000000400650 <+15>:    movl   $0x0,-0x4(%rbp)
    0x0000000000400657 <+22>:    mov    -0x20(%rbp),%rax
    0x000000000040065b <+26>:    add    $0x8,%rax
    0x000000000040065f <+30>:    mov    (%rax),%rax
    0x0000000000400662 <+33>:    mov    %rax,%rdi
    0x0000000000400665 <+36>:    callq  0x4005ac <func>
    0x000000000040066a <+41>:    movl   $0x1,-0x4(%rbp)
    0x0000000000400671 <+48>:    mov    $0x40075b,%edi
    0x0000000000400676 <+53>:    mov    $0x0,%eax
    0x000000000040067b <+58>:    callq  0x400470 <printf@plt>
    0x0000000000400680 <+63>:    mov    $0x400762,%edi
    0x0000000000400685 <+68>:    mov    $0x0,%eax
    0x000000000040068a <+73>:    callq  0x400470 <printf@plt>
    0x000000000040068f <+78>:    callq  0x400490 <getchar@plt>
    0x0000000000400694 <+83>:    leaveq
    0x0000000000400695 <+84>:    retq
 End of assembler dump.

根据我从示例中读到的内容,我的缓冲区长度为24字节,我应该再添加4个字节以获取SFP大小。这意味着我要添加28个字节才能到达<+41>的返回地址。然后看起来我想跳转到最后一个printf调用处<+73>,这应该是32的偏移量。然而,当我执行代码时,“x is 1”仍然被打印出来。我似乎找不出原因。我的数学或假设有问题吗?


修改返回地址是一个非常糟糕的做法。建议返回一个值,指示代码应该转到不同的代码块。 - user3629249
2
@user3629249 兄弟,他正在学习黑客工具如何利用堆栈。这不是他正在学习的主流编码技术。你有读过引用的 insecure.org 链接的内容吗?你的评论甚至没有任何适用性。 - cybermike
你有考虑到你正在使用64位处理器(64位字长)和汇编输出显示64位寻址,但是你引用的example3.c明确说明它们假设了32位处理器吗?(“我们必须记住,内存只能以字长的倍数寻址。在我们的情况下,一个字是4个字节,或32位。”)根据你之前的问题,使用调试器来检查func通过其过程代码如何获得其堆栈分配,并查看结果堆栈内存本身。 - cybermike
1个回答

3
你应该也要将函数func()拆开,以便更好地了解事情的发展。此外,我并不理解你调用strcpy()的作用,对于我来说只是导致分段错误的原因,所以我将其注释掉以使你的代码正常工作。
不要忘记,在代码中可以看到的大小是十六进制打印的,而你在代码中输入缓冲区移位是十进制的。所以,当你读取某些内容时:
mov    %rdi,-0x28(%rbp)

你必须考虑40个字节(0x28十六进制= 40十进制),而不是28个字节。

上面的代码实际上是从func()函数反汇编中提取的。正如@cybermike所提到的,不要忘记,虽然Alpeh1的文本仍然是该主题的参考资料,但它现在已经相当陈旧了:直到现在,架构和保护系统都已经广泛发展。

此处所述,在x64架构中,编译器现在将尝试使用16字节边界对齐堆栈地址,因此为您的24个字符数组分配大小时,它实际上会保留32个字节,即最近的边界。

再加上为“rest”指针分配的8个字节,那么您就知道您的返回地址距离确切为40个字节。

然后,查看main()的反汇编,正常的返回地址是:

0x00000000004005fe <+41>:    movl   $0x1,-0x4(%rbp)

我们希望它成为:

0x0000000000400614 <+63>:    mov    $0x4006bb,%edi

因此,我们需要将回报率增加 63 - 41 = 22。

总之,我已经使用以下 func() 函数使您的练习正常工作:

void func(char *str) {
    char buffer[24];
    int *ret;

    ret = buffer + 40; // Supposed to set ret to the return address of func
    (*ret) += 22; // Add the offset needed so that func will skip over printf("x is 1")
    //strcpy(buffer, str);

执行结果:

$ ./se 
x is 0

谢谢!你的解释帮了我很大忙。在我的机器上,好像可以保留strcopy,所以我猜那可能是系统相关的。看起来只是我的偏移量搞错了。 - user2276280
很高兴我能提供一些帮助。在我的端上,我像这样启动了编译好的程序("./se")。如果我在命令行上添加一些补充信息,那么argv就会被初始化("./se abc"),然后strcpy()就不会再引发分段错误了。无论如何,在任何一种情况下,这个调用都与当前的练习没有联系,只会带来麻烦,请注意;)。 - WhiteWinterWolf
@MattMcNabb:谢谢,回答已编辑,希望这个错误没有进一步影响我的解释。 - WhiteWinterWolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接