其他
原创 | 缓冲区溢出漏洞那些事:C -gets函数
关注我们
存在缺陷的程序设计 尤其是C语言,不像其他一些高级语言会自动进行数组或者指针的堆栈区块边界检查,增加溢出风险。 C语言中的C标准库还具有一些非常危险的操作函数,使用不当也为溢出创造条件。
什么是缓冲区溢出攻击
如果攻击者知道程序的内存布局,他们可以故意提供缓冲区无法存储的输入,并覆盖保存可执行代码的区域,用他们自己的代码替换它。例如,攻击者可以覆盖指针(指向内存中另一个区域的对象)并将其指向漏洞利用负载,从而获得对程序的控制权。
缓冲区溢出攻击的类型
哪些编程语言更容易受到攻击?
如何防止缓冲区溢出
地址空间随机化 (ASLR) — 随机移动数据区域的地址空间位置。通常,缓冲区溢出攻击需要知道可执行代码的位置,而随机化地址空间使得这几乎不可能。 数据执行预防 ——将某些内存区域标记为不可执行或可执行,从而阻止攻击在不可执行区域中运行代码。 结构化异常处理程序覆盖保护 (SEHOP) — 帮助阻止恶意代码攻击结构化异常处理 (SEH),这是一种用于管理硬件和软件异常的内置系统。因此,它可以防止攻击者利用 SEH 覆盖利用技术。在功能级别上,使用基于堆栈的缓冲区溢出来覆盖存储在线程堆栈中的异常注册记录来实现 SEH 覆盖。
代码和操作系统保护中的安全措施是不够的。当组织发现缓冲区溢出漏洞时,它必须迅速做出反应以修补受影响的软件,并确保软件用户可以访问补丁。
示例代码展示
#include <stdlib.h>
#include <stdio.h>
#include "Windows.h"
int main(int argc, char **argv) {
MessageBoxA((HWND)-0, (LPCSTR) "缓冲区溢出测试\n", (LPCSTR)"功能", (UINT)0);
int cookie;
char buf[2];
int *a = &cookie;
char * b = buf;
printf("buf: %08x cookie: %08x\n", b, a);
u_int64 p =(u_int64)a-(u_int64)b;
printf("两变量内存地址之差=%d\n",p);
gets(buf);
if (cookie == 0x41424344)
printf("缓冲区溢出成功!\n");
}
运行效果展示
使用MessageBoxA函数检测程序是否正常运行,点击确定开始测试
代码分析漏洞成因
提示:u_int64 p =(u_int64)a-(u_int64)b; 代码部分对程序涉及变量了内存地址进行了一个减分计算并对赋值给变量 p,(为使其运算成立还对其进行了类型转义),结果可告知两个变量内存地址距离,方便溢出利用
隐患:使用gets()函数获取输入数据,因gets()函数无限读取数据并不检查缓冲区的大小限制,会将超出缓冲区的数据继续写入堆栈,导致存在溢出隐患。
为方便理解此处代码演示下:
#include <stdlib.h>
#include <stdio.h>
#include "Windows.h"
int main() {
char test[] = "test1";
printf("test1初始值为%s\n清输入st值:",test);
char st[2];
gets(st);
printf("输出test:%s\n",test);
printf("输出st:%s\n",st);
}
反汇编分析其运行过程堆栈是如何变化的
有运行得知(外加源代码)程序初始关键词:test1初始值为test1
反汇编分析
扩展知识
即000000000061FDE0+2C=61FE0C
IDA调试分析使其跟清晰地去查看变化
ush rsi
.text:00000000004078D1 push rbx
.text:00000000004078D2 sub rsp, 38h
.text:00000000004078D6 call __main
.text:00000000004078DB lea rsi, [rsp+48h+var_1E] 初始rsi值
.text:00000000004078E0 xor r9d, r9d ; uType
.text:00000000004078E3 xor ecx, ecx ; hWnd
.text:00000000004078E5 lea rbx, [rsp+48h+var_1C]
.text:00000000004078EA lea r8, Caption ; lpCaption
.text:00000000004078F1 lea rdx, Text ; lpText
.text:00000000004078F8 call cs:__imp_MessageBoxA
.text:00000000004078FE mov r8, rbx
.text:0000000000407901 mov rdx, rsi
.text:0000000000407904 sub rbx, rsi
.text:0000000000407907 lea rcx, aBuf08xCookie08 ; "buf: %08x cookie: %08x\n"
.text:000000000040790E call _Z6printfPKcz ; printf(char const*,...)
.text:0000000000407913 lea rcx, byte_40902D ; char *
.text:000000000040791A mov rdx, rbx
.text:000000000040791D call _Z6printfPKcz ; printf(char const*,...)
.text:0000000000407922 mov rcx, rsi
.text:0000000000407925 call gets
.text:000000000040792A cmp [rsp+48h+var_1C], 41424344h
.text:0000000000407932 jz short loc_40793D
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止;
这里为3,故获取的数值为12,共输入6个数读取到第2个结束
往期推荐
原创 | GitHub Java CodeQL CTF原创 | CodeQL与AST之间联系