如何在C语言中进行缓冲区溢出时跳过一行

12

我想在C语言中跳过一行,在主要部分使用缓冲区溢出技术跳过x=1;这一行;然而,尽管我从<main+35><main+42>计算了7个字节,但我不知道为什么我无法跳过从4002f4地址到下一个地址4002fb

我还在Debian和AMD环境下配置了随机化和execstack环境选项,但我仍然得到了x=1;。这个程序有什么问题吗?

我已经使用dba调试栈和内存地址:

0x00000000004002ef <main+30>:    callq  0x4002a4 **<function>**  
**0x00000000004002f4** <main+35>:    movl   $0x1,-0x4(%rbp)  
**0x00000000004002fb** <main+42>:    mov    -0x4(%rbp),%esi  
0x00000000004002fe <main+45>:    mov    $0x4629c4,%edi  

void function(int a, int b, int c)  
{
  char buffer[5];
  int *ret;

  ret = buffer + 12;
  (*ret) += 8; 
}

int main()
{
   int x = 0; 
   function(1, 2, 3);
   x = 1;
   printf("x = %i \n", x);  
   return 0;  
}

3
我不太确定我是否应该帮助您调试攻击策略,但您应该转储堆栈$esp中的x/i10并将其添加到您的说明中。此外,请展示从function开始执行指令时发生的情况。 - Alex Brown
1
我建议你忽略程序的输出,只需在调试器中逐个汇编指令地单步执行。这样你就可以看到每一步实际上正在做什么(寄存器值、堆栈),并且这将告诉你为什么它没有按照你的期望执行。 - Gabe
请解释一下为什么你想这样做。 - Jim Balter
7
在过去,恶意软件作者在吃早饭前就写好缓冲区溢出漏洞了。现在他们在Stackoverflow上发布问题。我想问问这个世界要变成什么样子? - David Heffernan
2
@jim:根据我的教授,这是一个老问题,大多数操作系统都有避免此类攻击的策略。我只是一名学生,试图理解指针如何通过寄存器移动。 - Percy
@tony 没有任何操作系统可以防止您更改要从中返回的函数的返回地址...这取决于机器体系结构是否具有只读调用堆栈。但无论如何,您正在朝错误的方向前进...返回地址位于缓冲区之前,而不是之后。而且您正在添加到一个字符,但返回地址不是字符,因此可能会或可能不会将8添加到地址。 - Jim Balter
4个回答

4

你一定在阅读 Smashing the Stack for Fun and Profit 这篇文章。我也在阅读同样的文章,并发现了同样的问题,它没有跳过那个指令。经过几个小时在IDA中的调试会话后,我已经修改了下面的代码,它打印出x=0和b=5。

#include <stdio.h>

void function(int a, int b) {
     int c=0;
     int* pointer;

     pointer =&c+2;
     (*pointer)+=8;
}

void main() {
  int x =0;
  function(1,2);
  x = 3;
  int b =5;
  printf("x=%d\n, b=%d\n",x,b);
  getch();
}

4
为了跳过main()中的x = 1,需要在function()中更改返回地址。为此,您需要两个信息。

1. 栈帧中返回地址的位置。

我使用gdb来确定该值。我在function()处设置断点(break function),执行代码到断点处(run),检索当前栈帧的内存位置(p $rbpinfo reg),然后检索buffer的内存位置(p &buffer)。使用检索到的值,可以确定返回地址的位置。
(使用GCC -g标志编译以包含调试符号,并在64位环境中执行)
(gdb) break function
...
(gdb) run
...
(gdb) p $rbp
$1 = (void *) 0x7fffffffe270
(gdb) p &buffer
$2 = (char (*)[5]) 0x7fffffffe260
(gdb) quit

(帧指针地址 + 字长大小) - 缓冲区地址 = 从本地缓冲变量到返回地址的字节数
(0x7fffffffe270 + 8) - 0x7fffffffe260 = 24

如果你对调用栈的工作原理感到困难,阅读维基百科上的调用栈函数序言文章可能会有所帮助。这说明了在 C 中制作“缓冲区溢出”示例的困难。从 buffer 开始的偏移量为 24,这假设了一定的填充样式和编译选项。现在 GCC 会愉快地插入堆栈金丝雀,除非你告诉它不要。

2. 添加到返回地址中跳过x = 1的字节数。

在你的情况下,保存的指令指针将指向 0x00000000004002f4<main+35>),即函数返回后的第一条指令。要跳过此赋值语句,需要使保存的指令指针指向0x00000000004002fb<main+42>)。

你计算的这个数字为 7 字节是正确的 (0x4002fb - 0x4002fb = 7)。

我使用 gdb 对应用程序进行反汇编(disas main),并验证了我的情况下的计算。通过检查反汇编结果手动解决此问题是最好的方法。


请注意,我在 Ubuntu 10.10 64 位环境中测试了以下代码。

#include <stdio.h>

void function(int a, int b, int c)  
{
    char buffer[5];
    int *ret;

    ret = (int *)(buffer + 24);
    (*ret) += 7; 
}

int main()
{
     int x = 0; 
     function(1, 2, 3);
     x = 1;
     printf("x = %i \n", x);  
     return 0;  
}

输出

x = 0


这实际上只是改变了 function() 的返回地址,而不是一个真正的缓冲区溢出。在一个真正的缓冲区溢出中,你将会通过溢出 buffer[5] 来覆盖返回地址。然而,大多数现代实现都使用技术,例如堆栈金丝雀来保护。


1
更正:ret = (int *)(buffer + 24); 应该是 ret = (int *)(buffer + 6); C语言将24乘以4以获得兼容指针,这给我们带来了96。但偏移量是“24”,因此我们需要6而不是24(6 * 4 = 24)。此外,您可以使用传递的参数位置(在本例中为a、b、c)来到达堆栈上所需的位置,而无需声明缓冲区。@jschmier - DevX
请告诉我要翻译的英文内容。 - DevX

2
你在这里做的事情似乎与传统的缓冲区溢出攻击没有太多关系。缓冲区溢出攻击的整个思想是修改“函数”的返回地址。反汇编你的程序将向你展示ret指令(假设是x86)从哪里获取它的地址。这就是你需要修改的内容,使其指向main+42
我假设你想在这里明确地引发缓冲区溢出,通常情况下,你需要通过操纵“函数”的输入来引发它。
只声明一个buffer[5]会把栈指针移动到错误的方向(通过查看生成的汇编代码来验证),返回地址在堆栈内部更深处(它是由调用指令放置在那里的)。在x86中,堆栈向下增长,即向较低的地址方向增长。
我会通过声明一个int*,并将它向上移动直到到达已推送返回地址的指定地址,然后修改该值以指向main+42,并让函数ret

我不确定指针从一个寄存器移动多少字节,因此我不知道这些行- ret = buffer + 12; (*ret) += 8; - 和数字12和8是否正确跳过地址0x00000000004002fb处的x=1;。 - Percy
1
@tony "指针从一个寄存器移动到另一个寄存器需要多少字节" 你非常非常困惑。 - Jim Balter

1

你不能这样做。 这是一个经典的缓冲区溢出代码示例。看看当你从键盘输入5个字符然后再输入6个字符时会发生什么。如果你输入更多字符(16个字符应该足够),你将覆盖基指针,然后函数返回地址,最终导致分段错误。你想要做的是找出哪4个字符覆盖了返回地址,并使程序执行你的代码。在Linux堆栈和内存结构方面进行一些谷歌搜索。

 void ff(){
     int a=0; char b[5];
     scanf("%s",b);
     printf("b:%x a:%x\n" ,b ,&a);
     printf("b:'%s' a:%d\n" ,b ,a);
 }

 int main() {
     ff();
     return 0;
 }

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