好好说话之整数溢出

整数溢出

C语言中的整型分类

C语言中的整型按数据类型主要分三类:短整形(short)、整形(int)、长整形(long)

按符号分类:有符号、无符号

每种数据类型都有自己的大小范围:

类型字节范围
short int2byte(word)0~32767(0~0x7fff)
-32768~-1(0x8000~0xffff)
unsigned short int2byte(word)0~65535(0~0xffff)
int4byte(word)0~2147483647(0~0x7fffffff)
-2147483648~-1(0x80000000~0xffffffff)
unsigned int4byte(word)0~4294967295(0~0xffffffff)
long8byte(word)正: 0~0x7fffffffffffffff
负: 0x8000000000000000~0xffffffffffffffff
unsigned long8byte(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.out0x40066d <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上没给程序

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值