整数溢出
C语言中的整型分类
C语言中的整型按数据类型主要分三类:短整形(short)、整形(int)、长整形(long)
按符号分类:有符号、无符号
每种数据类型都有自己的大小范围:
类型 | 字节 | 范围 |
---|---|---|
short int | 2byte(word) | 0~32767(0~0x7fff) -32768~-1(0x8000~0xffff) |
unsigned short int | 2byte(word) | 0~65535(0~0xffff) |
int | 4byte(word) | 0~2147483647(0~0x7fffffff) -2147483648~-1(0x80000000~0xffffffff) |
unsigned int | 4byte(word) | 0~4294967295(0~0xffffffff) |
long | 8byte(word) | 正: 0~0x7fffffffffffffff 负: 0x8000000000000000~0xffffffffffffffff |
unsigned long | 8byte(word) | 0~0xffffffffffffffff |
如果说栈溢出是超过变量的栈空间范围,那么当程序中的数据超过其数据类型的范围,则会造成溢出,整数类型的溢出被称为整数溢出
上界溢出
上界溢出可以这么理解:在数据类型范围临界点继续增加
数值,导致溢出
# 伪代码
short int a;
a = a + 1;
# 对应的汇编
movzx eax, word ptr [rbp - 0x1c] /*将rbp - 0x1c位置的值(变量a)赋给eax寄存器*/
add eax, 1
mov word ptr [rbp - 0x1c], ax /*将ax(eax低2byte)中的值赋给rbp - 0x1c(变量a)*/
unsigned short int b;
b = b + 1;
# assembly code
add word ptr [rbp - 0x1a], 1 /*将rbp - 0x1a处的值(变量b)加1*/
我们看上面的例子,变量a是有符号短整形(short)也就是说它的范围为正0~0x7fff,负0x8000~0xffff。有符号整型的计算主要是在寄存器中执行的,因为short占2个字节add eax,1
计算之后,在将ax(eax低2byte)中的值传给变量a
再看变量b,它是无符号短整形,范围为0~0xffff。无符号整型的计算主要是在栈中执行,所以add word ptr [rbp - 0x1a], 1
直接在栈中给变量b加上了1
上界溢出主要有两种情况:
- 第一种情况:0x7fff + 1
因为计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在 (编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)。所以 add 0x7fff, 1 == 0x8000
,这种上界溢出对无符号整型就没有影响,但是在有符号短整型中,0x7fff 表示的是 32767,但是 0x8000 表示的是 -32768,用数学表达式来表示就是在有符号短整型中 32767+1 == -32768
- 第二种情况:0xffff + 1
这种情况需要考虑的是第一个操作数,比如上面的有符号型加法的汇编代码是 add eax, 1
,因为 eax=0xffff
,所以 add eax, 1 == 0x10000
,但是无符号的汇编代码是对内存进行加法运算 add word ptr [rbp - 0x1a], 1 == 0x0000
在有符号的加法中,虽然 eax
的结果为 0x10000,但是只把 ax=0x0000
的值储存到了内存中,从结果看和无符号是一样的
再从数字层面看看这种溢出的结果,在有符号短整型中,0xffff==-1,-1 + 1 == 0
,从有符号看这种计算没问题。但是在无符号短整型中,0xffff == 65535, 65535 + 1 == 0
下界溢出
和上界溢出一样,只不过在汇编中把add
替换成sub
。你可以这样理解:在数据类型范围临界点继续减少
数值,导致溢出
一样有两种情况:
第一种是 sub 0x0000, 1 == 0xffff
,对于有符号来说 0 - 1 == -1
没问题,但是对于无符号来说就成了 0 - 1 == 65535
。
第二种是 sub 0x8000, 1 == 0x7fff
,对于无符号来说是 32768 - 1 == 32767
是正确的,但是对于有符号来说就变成了 -32768 - 1 = 32767
出现整数溢出的情况
未限制范围
就如同前面的栈溢出,将不限制长度的字符串放在定长的变量中。整数溢出就是将不限制长度的数据放在了定长的变量中,就好比你吃的非常的撑,然后妈妈让你再喝一碗汤😂
一个示例:
$ cat test.c
#include<stddef.h>
int main(void)
{
int len;
int data_len;
int header_len;
char *buf;
header_len = 0x10;
scanf("%uld", &data_len);
len = data_len+header_len
buf = malloc(len);
read(0, buf, data_len);
return 0;
}
$ gcc test.c
$ ./a.out
-1
asdfasfasdfasdfafasfasfasdfasdf
# gdb a.out
► 0x40066d <main+71> call malloc@plt <0x400500>
size: 0xf
我们只申请了0x20大小的堆,但是却输入0xffffffff长度的数据,从整形溢出到堆溢出
错误的类型转换
即使正确的对变量进行约束,也仍然有可能出现整数溢出漏洞,可以概括为错误的类型转换,如果继续细分下去,可以分为:
范围大的变量赋值给范围小的变量
可以这样理解:你明明只能吃一小碗饭,妈妈问你吃多少,你说一碗就行,然后妈妈给你盛了一海碗
,亲妈没错了
$ cat test2.c
void check(int n)
{
if (!n)
printf("vuln");
else
printf("OK");
}
int main(void)
{
long int a;
scanf("%ld", &a);
if (a == 0)
printf("Bad");
else
check(a);
return 0;
}
$ gcc test2.c
$ ./a.out
4294967296
vuln
代码中讲一个范围大的变量a(长整形),传入check函数后变为范围小的变量n(整型),这样就造成了溢出
长整形占有8字节的内存空间,但是整型只有4字节,所以将long放进int之后会造成截断,只能将long的低4字节的值传给整型变量long: 0x100000000 -> int: 0x00000000
,但是反过来将int放在long中时没有事的
只做了单边限制
这种情况只针对有符号类型
$ cat test3.c
int main(void)
{
int len, l;
char buf[11];
scanf("%d", &len);
if (len < 10) {
l = read(0, buf, len);
*(buf+l) = 0;
puts(buf);
} else
printf("Please len < 10");
}
$ gcc test3.c
$ ./a.out
-1
aaaaaaaaaaaa
aaaaaaaaaaaa
我们虽然对变量len进行了限制,但是len是有符号整型,所以len的长度可以为负数,但是在read函数中,第三个参数的类型是size_t,该类型相当于unsigned long int,属于无符号长整形
例题
以后做到补上,wiki上没给程序