跳转至

Buffer Overflow

32位Buffer Overflow

概述

  • Error/bug:可能使程序无法实现预设目的

  • vulnerability:会遭受攻击

  • Attack:利用脆弱性实现攻击者的额外功能,例如:权限提升、任意代码执行

类型安全(Type safety)

Java拥有严格的类型定义系统;会在编译运行时检查程序是否安全,如检查输入是否越界;会自动释放不使用的资源。

C使用较差的类型定义,其数组可以越界,需要手动进行内存管理。

如下面这段C代码,其输出为3,说明C中的数组可以对数组外的内容赋值和调用,其本质为数组基地址(数组名) + 偏移量(数组内序号),如a[11]表示a[0]后面偏移11个整数长度的位置的内容。

1
2
3
4
5
6
int main() {
    int a[10];
    a[11] = 3;
    printf("%d",a[11]);
    return 0;
}

常见的字符串漏洞

gets

gets()函数在接收输入时不会检测长度,并将接收到的字符串全部输入给char数组,导致数组越界。

如这段代码,如果我输入一个大于16的字符串,a仍会输出这个越界的字符串直到遇到'\0'

1
2
3
4
5
6
int main(int argc,char*argv[]){
    char a[16];
    gets(a);
    printf("%s\n",a);
    return 0;
}

可以使用fgets()代替gets(),其可以指定取用的长度

strcpy and strcat

与gets类似,strcpy和strcat不会检测字符串长度是否引起越界。

可以使用

  • strncpy()

  • strncat()

等进行替代。

Null-Termination Errors

即使使用了新的函数,程序仍然可能不安全,下面是一个例子:

1
2
3
4
5
6
7
int main(int argc,char*argv[]){
    char a[16];
    strncpy(a,"0123456789abcdef",sizeof(a));
    printf("%s\n",a);
    printf("%d\n",a[17]);
    printf("%d\n",a[18]);
}
输出

1
2
3
0123456789abcdef??
19
4294967240

即字符串a的末尾没有'\0',导致输出了数组后面的内容。

函数调用时的内存分布

程序的内存分布如下

内存分布

对于下面这个函数,分析其变量的存储位置

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

//已定义的静态内容在Data segment
int x = 100;

int main() {
    // 数据存储在栈上
    int a = 2;
    float b = 2.5;
    static int y; // 未定义的静态内容在BSS segment

    // 在堆上分配内存
    int *ptr = (int*)malloc(2 * sizeof(int));
    // 值 5 和 6 存储在堆上,指针ptr在栈上
    ptr[0] = 5;
    ptr[1] = 6;

    // 释放堆上的内存
    free(ptr);
    return 1;
}

对于函数

1
2
3
4
5
6
void func(int a, int b){
    int x,y;

    x = a + b;
    y = a - b;
}

其存储如下

函数调用

  • 函数调用时传入的变量从右到左依次入栈

  • Return Address 为函数结束后返回的地址

  • Previous Frame Pointer 为函数调用前栈指针的位置,32位中使用ebp表示

  • 新的栈指针指向局部变量开始的位置

这些内容在内存中如下排列

低地址 ---> ---> ---> ---> 高地址
y x old ebp ret addr a b

由于将地址高的位置作为栈底,处于较低位置的局部变量可以将内容溢出到ret addr等位置,从而返回到错误的地址,此时在这个位置传入shellcode(能够打开shell的一段代码),就可以成功攻击。

RO