缓冲区溢出解释

3

我制作了一个简单的密码验证程序,我试图溢出缓冲区数组以将auth变量更改为1,我已经成功做到了,但我只能将auth变量更改为字符1而不是十进制1,该怎么办?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){

char buffer[16];
int auth=0;
strcpy(buffer, argv[1]);

if(strcmp(buffer,"password")==0)
    auth=1;
else
    auth=0;

if(auth)
    printf("Granted");



 return 0;

}

2
使用我的gcc编译器,我尝试了printf("%x %x\n",&buffer,&auth);,但是输出的结果是28feb0 28feac,看来是因为填充优化所导致的。在堆栈中变量的顺序不能保证与声明的顺序相同。 - Jean-François Fabre
@pedrosantos:我也一样,可以看看我的评论。 - Jean-François Fabre
我的想法是通过分析汇编和地址来触发未定义行为,如果可能的话,注入任何二进制数据。但是,使用我的gcc 4.9编译器,我无法在那个特定的代码段中实现它。黑客必须很幸运才能成功。 - Jean-François Fabre
@Jean-FrançoisFabre 不要使用"%x"打印指针,而应该使用"%p"。此外,指针必须强制转换为void *类型。不使用"%p"来打印(void *)指针是未定义行为 - Some programmer dude
1
我知道问题出在哪里了,我使用溢出更改了auth变量,但是随着执行的继续,strcmp(buffer, "password")部分会再次更改auth为1或0,这就是为什么它不起作用的原因,我想。 - pedro santos
显示剩余4条评论
2个回答

5

以下信息来自于我在Ubuntu-14.04系统上使用gcc版本4.8.4作为编译器和gdb版本7.7.1作为调试器运行的结果。

首先,缓冲区溢出是由strcpy函数引起的,如果你溢出了buf,使其覆盖了auth的内存位置,但接下来的if-else块将覆盖你的更改。

其次,您可以通过在调试器中查看堆栈来了解发生了什么。我对您的代码进行了轻微修改,通过将auth初始化为0xbbbbbbbb(只是为了找到auth在堆栈上的位置)。

在main函数上设置断点并进入函数,我们可以检查各个寄存器的值:

   (gdb) info reg
   rax            0x0   0
   rbx            0x0   0
   rcx            0x0   0
   rdx            0x7fffffffdf30    140737488346928
   rsi            0x7fffffffdf18    140737488346904
   rdi            0x2   2
   rbp            0x7fffffffde30    0x7fffffffde30
   rsp            0x7fffffffddf0    0x7fffffffddf0
         [... some lines removed ...]
   rip            0x400652  0x400652 <main+37>
   eflags         0x246 [ PF ZF IF ]
   cs             0x33  51
   ss             0x2b  43
   ds             0x0   0
   es             0x0   0
   fs             0x0   0
   gs             0x0   0

从这里我们可以看到栈从0x7fffffffddf0延伸到0x7fffffffde30。现在,在调用strcpy之前停止,我们可以查看堆栈:

(gdb) x/76xb $rsp
0x7fffffffddf0: 0x18    0xdf    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffddf8: 0x1d    0x07    0x40    0x00    0x02    0x00    0x00    0x00
0x7fffffffde00: 0x30    0xde    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffde08: 0x00    0x00    0x00    0x00    0xbb    0xbb    0xbb    0xbb
0x7fffffffde10: 0xd0    0x06    0x40    0x00    0x00    0x00    0x00    0x00
0x7fffffffde18: 0x40    0x05    0x40    0x00    0x00    0x00    0x00    0x00
0x7fffffffde20: 0x10    0xdf    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffde28: 0x00    0x2b    0x25    0x07    0xdd    0x7a    0xc0    0x6d
0x7fffffffde30: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffde38: 0x45    0x6f    0xa3    0xf7

从这里可以看出,auth位于内存地址0x7fffffffde0c

我将passwordAAAAAAAA111设置为命令行参数,现在我们可以单步执行strcpy调用并再次查看内存:

(gdb) x/76xb $rsp
0x7fffffffddf0: 0x18    0xdf    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffddf8: 0x1d    0x07    0x40    0x00    0x02    0x00    0x00    0x00
0x7fffffffde00: 0x30    0xde    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffde08: 0x00    0x00    0x00    0x00    0xbb    0xbb    0xbb    0xbb
0x7fffffffde10: 0x70    0x61    0x73    0x73    0x77    0x6f    0x72    0x64
0x7fffffffde18: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7fffffffde20: 0x31    0x31    0x31    0x31    0x00    0x7f    0x00    0x00
0x7fffffffde28: 0x00    0x2b    0x25    0x07    0xdd    0x7a    0xc0    0x6d
0x7fffffffde30: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffde38: 0x45    0x6f    0xa3    0xf7

(gdb)

从这里,我们可以看出auth的值没有被改变(注意从0x7fffffffde0c开始仍存在四个0xbb)。同时,我们现在可以看到密码存储在内存中的位置,它从0x7fffffffde10开始。我使用的四个'A'代表了四个0x41,而我使用的四个'1'则代表了四个0x31。

所以,在我的系统上,我看不到有任何可能会溢出到auth变量的方法。

最后,你提出的问题,请记住命令行参数被视为字符数组,因此在命令行传递类似于AAAA1的东西将导致数组[0x41 0x41 0x41 0x41 0x31]被传递给你的程序。你想让你的程序接收的实际上是[0x41 0x41 0x41 0x41 0x01 0x00 0x00 0x00](假设32位,小端架构)。你将面临两个问题: 1. 0x01是一个不可打印字符 2. 0x00作为空终止符将在第一个null处停止字符串输入。

对于问题2,只使用简单输入并没有太多可做的; 然而,正如其他人建议的那样,解决问题1的方法是创建一个驱动程序来构建输入缓冲区,然后将其传递给程序。


我差点忘了,由于参数都是以null结尾的,所以根本无法注入0(null)字符。因此,如果你想注入一些数据或代码,就必须不使用零字符。这是一个好而详细的解释。 - Jean-François Fabre
谢谢您的解释,但在我的程序中,auth变量是在缓冲区之后分配的,因此我可以溢出它。这是与不可打印字符有关的问题。 - pedro santos

1
在Windows(或Linux)中,创建一个bat(或shell)文件如下:
a 0123456789ABCDEFG

(a是您的可执行文件的名称)

enter image description here

然后,使用十六进制编辑器编辑它并将最后一个 G 更改为 01 的十六进制值,保存。
如果(我说的是如果)您可以确保整数值的地址位于 char 缓冲区之后(使用我的 gcc 无法实现此目标,因为编译器会按照基于实现的顺序定位其变量),运行此脚本,您将看到 \001 字符传递给了第一个参数的末尾。
注意:由于参数以 null 结尾,因此根本没有办法传递 0(null)字符,因此,如果要注入某些数据或代码,则必须不使用零字符。

我正在运行Debian。 - pedro santos

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