1. 内容概述
整数溢出漏洞发生在当整数存储超出其数据类型所能表示的范围时。主要原因在于计算机中的整数类型分为有符号整数和无符号整数。有符号整数使用最高位来表示正负号:0 表示正数,1 表示负数,而无符号整数没有这个规则。常见的整数类型有 8 位、16 位和 32 位等。整数溢出通常发生在存储的值超出了该类型可表示的范围时,从而导致数据异常和潜在的安全漏洞。
2. 基础知识
数据类型 | 占用字节数 | 范围 |
字符类型 char | 1 | -128~127 |
无符号字符类型 unsigned char | 1 | 0~255 |
布尔类型 bool | 1 | 0~1 |
短整型 short | 2 | -32768~32767 |
无符号整型 unsigned short | 2 | 0~65535 |
整型 int | 4 | -2147483648 ~2147483647 |
无符号整型 unsigned int | 4 | 0~4294967295 |
长整型 long | 4 | -2147483648 ~2147483647 |
无符号长整型 unsigned long | 4 | 0~4294967295 |
64位整型 long long | 8 | -2^63~2^63 -1 |
3. 漏洞原理
3.1 上溢问题
#include<stdio.h>
int main() {
unsigned short a;
a = 65535; // 无符号短整型最大值
a = a + 5; // 超出范围,发生整数溢出
printf("a = %d\n", a); // 输出溢出后的值
// 运行结果: a = 4
short b;
b = 32767; // 有符号短整型最大值
b = b + 5; // 超出范围,发生整数溢出
printf("b = %d\n", b); // 输出溢出后的值
// 运行结果: b = -32764
return 0;
}
整数上溢是指当一个整数通过计算(如赋值)超过了其能够表示的上限时,结果会变成一个较小的数。例如,一个 `unsigned short` 类型的变量 `a = 65535`,当它再加 5 时会变成 4。这是因为在计算机中,65535 加上 5 的二进制表示相当于将 `(1111111111111111)₂` 加上 `(101)₂`,得到 `(10000000000000100)₂`。由于 `unsigned short` 类型的数据空间是 16 位,最高位的 1 会被舍弃,剩下低 16 位,即 `(0000000000000100)₂`,表示的数为 4。
当一个 `short` 类型的变量 `a = 32767` 时,若再加上 5,结果会变成 -32764。这是因为在计算机中,32767 的二进制表示是 `(0111111111111111)₂`,加上 5 后得到 `(1000000000000100)₂`。由于 `short` 类型的最高位是符号位,0 表示正数,1 表示负数,因此结果超出了 `short` 类型数据的表示范围,导致发生上溢,最终结果为 `(1000000000000100)₂`,对应的十进制值为 -32764。
计算机中的整数以补码形式存储,正数的补码与原码一致,负数的补码则是对原码按位取反再加 1。例如,`(1000000000000100)₂` 的补码经过反码处理后,加 1 的结果为 `(-32764)`,这是溢出后的最终值。
3.2 下溢问题
#include<stdio.h>
int main() {
unsigned short a;
short b;
b = -4; // 赋值一个负数给有符号短整型变量
a = b; // 将有符号短整型赋值给无符号短整型
printf("a = %d\n", a); // 输出无符号短整型变量的值
return 0;
}
/* 输出结果:
a = 65532
*/
整数下溢是指当一个整数通过运算(赋值)低于它能表示的下限时,结果会变成一个很大的数。由于无符号整数不能识别负数,当一个 `short` 类型的变量 `b = -4` 赋值给 `unsigned short` 类型的变量 `a` 时,`a` 的值会变成 65532。这是因为 -4 是负数,在计算机中以补码形式存储,其二进制补码为 `(1111111111111100)₂`。由于 `unsigned short` 类型的变量没有符号位,所有位都用于表示数值,因此这个二进制补码被解释为正数 `(65532)₁₀`。
3.3 截断问题
#include<stdio.h>
int main() {
short b;
int a = 65537; // 赋值一个超过 short 类型范围的数值
b = a; // 将 int 类型赋值给 short 类型
printf("b = %d\n", b); // 输出 short 类型的值
return 0;
}
/* 输出结果:
b = 1
*/
截断是指将数据放入比它本身小的存储空间中,从而导致溢出。例如,当一个 `int` 类型的变量 `a = 65537` 被赋值给 `short` 类型的变量时,`short` 类型只能接受低 16 位,导致溢出。65537 的二进制表示为 `(*10000000000000001)₂`,只保留低 16 位,结果是 `b = 1`。这种情况下,数据的高位部分被舍弃,产生截断效应。
3.4 符号问题
#include <stdio.h>
int main() {
short a = 32767; // short 类型的最大值
short b = -5;
// 比较 a 和 b 的大小
if (a > b) {
printf("a = %d is greater than b = %d\n", a, b);
// 正确的输出应该是 a > b
} else {
printf("a = %d is less than or equal to b = %d\n", a, b);
}
// 通过作差展示溢出问题
short diff = a - b; // 计算 a - b,发生溢出
printf("Difference (a - b) = %d\n", diff); // 输出溢出后的值
return 0;
}
/* 输出结果:
a = 32767 is less than or equal to b = -5
Difference (a - b) = -32764
*/
有符号整数之间的比较:在有符号整数的比较中,通常通过作差来判断两个数的大小。如果结果为正,则说明第一个数大于第二个数;如果结果为负,则第一个数小于第二个数。然而,当两个有符号整数作差时出现上溢或下溢,比较结果可能与实际情况相反。例如,short
类型的 32767 与 -5 比较时,由于 32767 - (-5) 的计算导致溢出,结果为 -32764,从而得出错误的结论:32767 小于 -5。这种情况会导致比较结果不准确。
#include <stdio.h>
int main() {
// 演示上溢
short a = 32767; // short 类型的最大值
short b = 1;
short result_overflow = a + b; // 发生上溢
printf("Overflow: %d + %d = %d\n", a, b, result_overflow);
// 输出结果应为:Overflow: 32767 + 1 = -32768
// 演示下溢
short c = -32768; // short 类型的最小值
short d = -1;
short result_underflow = c + d; // 发生下溢
printf("Underflow: %d + %d = %d\n", c, d, result_underflow);
// 输出结果应为:Underflow: -32768 + (-1) = 32767
return 0;
}
有符号整数的运算中,当两个有符号整数进行运算时,可能会发生上溢或下溢的情况。例如,如果运算结果超出有符号整数的表示范围,就会导致溢出,从而使结果不符合预期。
#include <stdio.h>
int main() {
short a = -5; // 有符号短整型,值为 -5
unsigned short b = 13; // 无符号短整型,值为 13
// 比较有符号和无符号整数
if (a > b) {
printf("a = %d is greater than b = %u\n", a, b);
} else {
printf("a = %d is less than or equal to b = %u\n", a, b);
}
return 0;
}
/* 输出结果:
a = -5 is greater than b = 13
*/
当无符号整数和有符号整数进行比较或运算时,有符号整数会被转换为无符号整数,从而导致上溢或下溢,并得出与事实相反的结论。例如,当 `short` 类型的 -5 和 `unsigned short` 类型的 13 进行比较时,-5 会被转换为无符号整数 65531,因此比较结果为 65531 > 13,这与实际相反。这种转换可能会导致错误的逻辑判断。
4. 实例分析
#include <stdio.h>
#define N 105
int main() {
int Number;
unsigned short number;
while (1) {
printf("ID Number: ");
scanf("%d", &Number);
number = Number;
if (number <= N && Number > N) {
puts("Welcome !");
break;
} else {
printf("%d\n", number);
printf("It is illegal, please check your id number.\n");
}
}
return 0;
}
利用整数溢出的思路是通过输入一个超出数据类型范围的数值,使变量发生溢出,从而达到程序逻辑要求的条件。以 unsigned short
为例,当输入的值超过其最大值 65535 时,会发生溢出,重新从 0 开始计数。通过输入如 65541
的值,unsigned short
类型的变量 number
将溢出为较小的值(例如 65541 - 65536 = 5
),这样满足了 number <= 105
且 Number > 105
的条件,从而输出 "Welcome!"。这种方法利用了无符号整数溢出后循环计数的特性,来满足程序中看似冲突的逻辑条件。