缓冲区溢出解释
Buffer Overflow explanation
我制作了这个简单的密码验证程序,我试图溢出缓冲区数组以将 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;
}
在 windows(resp. Linux)上,像这样创建一个 bat(resp shell)文件:
a 0123456789ABCDEFG
(a 是您的可执行文件的名称)
然后,用十六进制编辑器编辑,把最后的G
改成01的十六进制值,保存。
如果(我说如果)你可以确保你的整数值的地址在字符缓冲区之后(我不能使用我的 gcc,因为编译器使用基于实现的顺序定位它的变量) , 运行 这个脚本,你会看到在第一个参数的末尾传递了 \001 字符。
注意:根本无法传递 0(空)字符,因为参数以空字符结尾,因此如果您想注入一些数据或代码,则必须不使用零字符。
以下信息来自我的 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 作为空终止符将在第一个空处停止字符串输入。
对于问题 2,您无能为力,只需简单输入即可;然而,正如其他人所建议的那样,围绕问题 1 的解决方案是创建一个驱动程序,以您想要的方式构建输入缓冲区,然后将其传递给程序。
我制作了这个简单的密码验证程序,我试图溢出缓冲区数组以将 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;
}
在 windows(resp. Linux)上,像这样创建一个 bat(resp shell)文件:
a 0123456789ABCDEFG
(a 是您的可执行文件的名称)
然后,用十六进制编辑器编辑,把最后的G
改成01的十六进制值,保存。
如果(我说如果)你可以确保你的整数值的地址在字符缓冲区之后(我不能使用我的 gcc,因为编译器使用基于实现的顺序定位它的变量) , 运行 这个脚本,你会看到在第一个参数的末尾传递了 \001 字符。
注意:根本无法传递 0(空)字符,因为参数以空字符结尾,因此如果您想注入一些数据或代码,则必须不使用零字符。
以下信息来自我的 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 作为空终止符将在第一个空处停止字符串输入。
对于问题 2,您无能为力,只需简单输入即可;然而,正如其他人所建议的那样,围绕问题 1 的解决方案是创建一个驱动程序,以您想要的方式构建输入缓冲区,然后将其传递给程序。