栈溢出是如何发生的
假设有一个函数包含一个固定大小的字符数组:
void vulnerable_function(char *input) {
char buffer[100];
strcpy(buffer, input); // 没有边界检查
}
如果攻击者提供一个超长的输入,strcpy
函数会将超出 buffer
大小的数据写入到栈上的其他区域,可能会覆盖返回地址,导致程序在函数返回时跳转到攻击者指定的代码位置。
利用方式总结
直接覆盖返回地址
直接由参数地址溢出到返回地址,在函数返回时就会返回到攻击者期望的地址
poc = b'a'*pad + adder
pad是溢出点距离返回地址的距离
adder是要返回的地址
溢出低地址
要理解为什么可以覆盖低地址来绕过pie,我们先来了解一下端序与pie保护
大端序与小端序
在计算机系统中,大端序(Big-Endian) 和 小端序(Little-Endian) 是两种不同的字节序(Byte Order)表示方式。这些表示方式定义了多字节数据(如整数和浮点数)在内存中的存储顺序。它们的区别如下:
大端序(Big-Endian)
-
定义:在大端序中,多字节数据的高字节(即最重要的字节)存储在内存的低地址处,而低字节(即最不重要的字节)存储在高地址处。
-
例子:对于 4 字节的整数
0x12345678
,在大端序系统中,内存中的存储顺序是:地址 数据 0x00 0x12 0x01 0x34 0x02 0x56 0x03 0x78
小端序(Little-Endian)
-
定义:在小端序中,多字节数据的低字节(即最不重要的字节)存储在内存的低地址处,而高字节(即最重要的字节)存储在高地址处。
-
例子:对于 4 字节的整数
0x12345678
,在小端序系统中,内存中的存储顺序是:地址 数据 0x00 0x78 0x01 0x56 0x02 0x34 0x03 0x12
常见系统的字节序
-
小端序:
- x86 和 x86-64 架构:大多数现代 PC 和服务器使用的处理器(如 Intel 和 AMD 处理器)采用小端序。
- ARM 架构:许多 ARM 处理器(特别是手机和嵌入式设备)默认使用小端序,虽然 ARM 处理器也可以配置为大端序。
-
大端序:
- 网络协议:许多网络协议(如 TCP/IP)使用大端序,通常被称为“网络字节序”。
- 某些旧系统和处理器:例如一些早期的 Motorola 处理器和一些嵌入式系统使用大端序。
区别
- 数据解释:不同的字节序会影响程序如何解释多字节数据。例如,相同的字节序列在大端序系统上会被解释为不同的值,而在小端序系统上则解释为另一个值。
- 跨平台兼容性:在不同字节序的系统之间交换数据时,必须考虑字节序问题,以确保数据能够被正确解释和处理。
PIE
PIE(Position-Independent Executable) 是一种保护机制,用于增强程序的安全性。PIE 通过使可执行文件在内存中的地址位置不固定,减少了攻击者利用内存地址预测的可能性。其主要原理包括:
原理
-
地址空间布局随机化(ASLR):
- PIE 文件与 ASLR 配合使用,使得每次程序加载时,其内存中的各个部分(如代码段、数据段等)的位置都是随机的。这使得攻击者无法通过预测内存地址来执行攻击,如代码注入或缓冲区溢出攻击。
-
位置无关代码(Position-Independent Code, PIC):
- PIE 文件包含位置无关代码,即代码在编译时并不依赖于固定的内存地址。这样,代码可以在任何内存地址处执行,而无需进行地址修正。编译器通过使用相对地址而不是绝对地址来实现这一点。
-
重定位表(Relocation Table):
- 编译器生成的 PIE 文件包含一个重定位表,这个表记录了代码中需要根据实际加载地址进行调整的位置。在程序加载到内存中时,链接器会根据实际的内存地址修正这些位置,确保代码和数据可以正确地被访问。
PIE 保护的工作流程
-
编译:
- 在编译时,使用
-fPIC
(Position-Independent Code)选项编译源代码,生成位置无关代码。可执行文件也会被编译为 PIE 文件,以便支持 ASLR。
- 在编译时,使用
-
链接:
- 链接时,生成的 PIE 文件包含了必要的重定位信息,以便在加载到内存时进行地址调整。
-
加载:
- 当程序加载到内存中时,操作系统的加载器根据 ASLR 随机分配内存地址。加载器使用重定位表调整代码中的地址,使得程序可以正确地在新的地址空间中运行。
溢出低地址
我们能发现,如果是小端序,修改低地址有时候可以在未泄露地址
的情况下将程序控制流控制到原地址附近
地址 数据
0x00 0x78 <----- 覆盖
0x01 0x56
0x02 0x34
0x03 0x12