前言
参考:
https://ctf-wiki.org/pwn/linux/user-mode/integeroverflow/introduction/
C语言整数数据类型
C语言中整数的基本数据类型(数据类型的大小范围是编译器决定的,这里以 64位 gcc5.4 为准)
类型 | 字节 | 范围 |
---|---|---|
short int | 2byte(word) | 0 |
unsigned short int | 2byte(word) | 0 |
int | 4byte(dword) | 0 |
unsigned int | 4byte(dword) | 0 |
long int | 8byte(qword) | 正: 0 |
unsigned long int | 8byte(qword) | 0~0xffffffffffffffff |
当程序中的数据超过其数据类型的范围,则会造成溢出
原理
上界溢出
# 伪代码
short int a;
a = a + 1;
# 对应的汇编
movzx eax, word ptr [rbp - 0x1c]
add eax, 1
mov word ptr [rbp - 0x1c], ax
unsigned short int b;
b = b + 1;
# assembly code
add word ptr [rbp - 0x1a], 1
上界溢出有两种情况,一种是 0x7fff + 1
, 另一种是 0xffff + 1
前者是数据类型上的溢出,后者是汇编意义上的溢出
计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在 (编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)
对于
add 0x7fff, 1 == 0x8000
,0x7fff 为 32767,在无符号整型中 32767+1 没有影响,因为范围最大到 65535(0xffff);而在有符号短整型中, 0x8000 就为 -32768用数学表达式来表示就是在有符号短整型中
32767+1 == -32768
对于
add 0xffff, 1
,这种情况需要考虑的是第一个操作数以
add eax, 1
为例,因为eax=0xffff
,所以add eax, 1 == 0x10000
,但是无符号的汇编代码是对内存进行加法运算add word ptr [rbp - 0x1a], 1 == 0x0000
(该寄存器最多存储4位)在有符号的加法中,虽然
eax
的结果为 0x10000,但是只把末四位ax=0x0000
的值储存到了内存中,从结果看和无符号是一样的从数字层面上看,有符号短整型
0xffff == -1,-1 + 1 == 0
,无符号短整型0xffff == 65535, 65535 + 1 == 0
下界溢出
和上界溢出类似,只是 add 变成了 sub
一样是两种情况:
sub 0x0000, 1 == 0xffff
short int 0 - 1 == -1
unsign short int 0 - 1 == 65535
sub 0x8000, 1 == 0x7fff
short int -32768 - 1 = 32767
unsign short int 32768 - 1 == 32767
例子
未限制范围
#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;
}
只申请 0x20
大小的堆,但是却能输入 0xffffffff
长度的数据,从整型溢出到堆溢出
错误的类型转换
范围大的变量赋值给范围小的变量:
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;
}
$ ./a.out
4294967296
vuln
长整型 a 传入 check 函数变为整型变量 n
单边限制:
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");
}
$ ./a.out
-1
aaaaaaaaaaaa
aaaaaaaaaaaa
*** stack smashing detected ***: terminated
Aborted (core dumped)
表面上看,我们对变量 len 进行了限制,但是仔细思考可以发现,len 是有符号整型,所以 len 的长度可以为负数,但是在 read 函数中,第三个参数的类型是 size_t
,该类型相当于 unsigned long int
,属于无符号长整型