C语言整数溢出的攻击面,符号转换漏洞unsigned绕过安全检查的案例
扫描二维码
随时随地手机看文章
C语言因直接操作内存和高效性被广泛应用于系统级开发,但其缺乏边界检查的机制导致整数溢出成为安全漏洞的高发区。从符号转换漏洞到无符号整数(unsigned)绕过安全检查,攻击者通过精心构造的输入触发溢出,进而实现缓冲区溢出、权限提升甚至远程代码执行。本文结合典型漏洞案例,深入剖析整数溢出的攻击原理与防御策略。
一、符号转换漏洞:从类型不匹配到任意内存写入
符号转换漏洞通常发生在有符号整数(signed)与无符号整数(unsigned)混合使用时。C语言在类型转换时遵循“算术转换规则”,即当有符号数与无符号数参与运算时,有符号数会被隐式转换为无符号数。若未对输入范围进行严格校验,攻击者可利用此特性绕过安全检查。
典型案例:OpenSSH远程缓冲区溢出漏洞
在OpenSSH的auth2-chall.c文件中,input_userauth_info_response()函数存在整数溢出漏洞:
nresp = packet_get_int(); // 读取网络数据包中的整数值
if (nresp > 0) { // 检查nresp是否为正数
response = xmalloc(nresp * sizeof(char *)); // 分配内存
for (i = 0; i < nresp; i++) {
response[i] = packet_get_string(NULL); // 读取字符串
}
}
攻击原理:
符号转换绕过检查:packet_get_int()返回的nresp为有符号整数,但xmalloc的参数类型为size_t(无符号整数)。若攻击者发送负数(如-1),在比较nresp > 0时会被视为无符号数0xFFFFFFFF(32位系统),从而绕过检查。
整数溢出导致堆溢出:nresp * sizeof(char *)的计算中,-1的无符号转换值为0xFFFFFFFF,乘以4后发生溢出(结果为0x00000004),导致xmalloc仅分配4字节内存。后续循环写入大量数据时,覆盖堆内存中的函数指针或元数据,最终实现任意代码执行。
防御策略:
严格类型匹配:避免有符号与无符号数混合运算,统一使用size_t处理内存相关操作。
输入范围校验:在分配内存前,显式检查nresp是否超过合理阈值(如nresp < MAX_ALLOWED_SIZE)。
使用安全函数:改用带边界检查的内存分配函数(如calloc替代malloc)。
二、unsigned绕过安全检查:从回绕到缓冲区溢出
无符号整数溢出在C语言中是“定义明确的行为”(undefined behavior的例外),即发生溢出时会对类型位宽取模。攻击者利用此特性构造输入,使安全检查失效并触发溢出。
典型案例:字符串拼接缓冲区溢出
以下代码片段试图将两个用户输入的字符串拼接至固定大小的缓冲区:
void concat_strings(char *str1, char *str2) {
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char buf[256];
if (len1 + len2 < sizeof(buf)) { // 检查总长度是否超过缓冲区
memcpy(buf, str1, len1);
memcpy(buf + len1, str2, len2);
buf[len1 + len2] = '\0';
}
}
攻击原理:
无符号整数回绕绕过检查:若len1和len2均为较大值(如0x100和0xFFFFFF00),len1 + len2会发生溢出(结果为0x100 + 0xFFFFFF00 = 0x00000000),导致条件len1 + len2 < sizeof(buf)成立。
缓冲区溢出:实际拼接时,memcpy会写入超过buf大小的数据,覆盖栈上的返回地址或函数指针,实现控制流劫持。
防御策略:
显式溢出检查:在计算长度前,检查len1或len2是否接近SIZE_MAX - sizeof(buf)。
使用安全函数:改用snprintf或strlcpy等带长度限制的字符串操作函数。
静态分析工具:通过Coverity、Clang Static Analyzer等工具检测潜在溢出风险。
三、整数溢出攻击的衍生场景
1. 循环条件绕过
以下代码试图限制循环次数以避免缓冲区溢出:
void read_data(int fd, char *buf, size_t max_len) {
int len = 0;
while (len < max_len) { // 检查已读取长度
len += read(fd, buf + len, max_len - len); // 读取数据
}
}
攻击原理:
若max_len为INT_MAX,且read返回负数(如-1),len会因符号转换变为0xFFFFFFFF,导致循环条件len < max_len恒成立,最终触发无限循环或缓冲区溢出。
防御策略:
检查返回值:确保read的返回值非负,且len不会超过max_len。
使用无符号类型:将len声明为size_t以避免符号问题。
2. 数组越界访问
以下代码通过用户输入的索引访问数组:
int get_array_element(int *arr, int index, size_t size) {
if (index >= 0 && index < size) { // 检查索引范围
return arr[index];
}
return -1;
}
攻击原理:
若size为INT_MAX,攻击者传入index = -1,在比较index < size时,-1会被转换为无符号数0xFFFFFFFF,导致条件成立并访问非法内存。
防御策略:
统一使用无符号索引:将index声明为size_t,并确保size为合理值。
边界检查:在访问数组前,显式检查index是否小于size。
四、防御整数溢出的通用策略
编译器选项:启用-fsanitize=undefined等编译选项,在运行时检测溢出。
静态分析:通过SonarQube、Fortify等工具扫描代码中的潜在溢出风险。
安全编码规范:
避免混合使用有符号与无符号整数。
对用户输入进行严格校验。
使用安全的数学库(如SafeInt、Arbitrary Precision Arithmetic)。
总结
C语言整数溢出的攻击面广泛,涵盖符号转换漏洞、无符号整数回绕、循环条件绕过等多种场景。攻击者通过精心构造输入,利用C语言的隐式类型转换和未定义行为,绕过安全检查并触发缓冲区溢出或控制流劫持。开发者需通过严格的输入校验、类型安全编程和安全工具辅助,构建多层次的防御体系,从根本上降低整数溢出漏洞的风险。