目录

  1. 1. 前言
  2. 2. C语言整数数据类型
  3. 3. 原理
    1. 3.1. 上界溢出
    2. 3.2. 下界溢出
  4. 4. 例子
    1. 4.1. 未限制范围
    2. 4.2. 错误的类型转换

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

整数溢出

2025/3/18 Pwn
  |     |   总文章阅读量:

前言

参考:

https://ctf-wiki.org/pwn/linux/user-mode/integeroverflow/introduction/


C语言整数数据类型

C语言中整数的基本数据类型(数据类型的大小范围是编译器决定的,这里以 64位 gcc5.4 为准)

类型 字节 范围
short int 2byte(word) 032767(00x7fff) -32768-1(0x80000xffff)
unsigned short int 2byte(word) 065535(00xffff)
int 4byte(dword) 02147483647(00x7fffffff) -2147483648-1(0x800000000xffffffff)
unsigned int 4byte(dword) 04294967295(00xffffffff)
long int 8byte(qword) 正: 00x7fffffffffffffff 负: 0x80000000000000000xffffffffffffffff
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,属于无符号长整型