zoukankan      html  css  js  c++  java
  • 整数溢出

    介绍

    在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(dword) 0 ~ 2147483647(0 ~ 0x7fffffff) -2147483648 ~ -1(0x80000000 ~ 0xffffffff)
    unsigned int 4byte(dword) 0 ~ 4294967295(0 ~ 0xffffffff)
    long int 8byte(qword) 正: 0 ~ 0x7fffffffffffffff 负: 0x8000000000000000 ~ 0xffffffffffffffff
    unsigned long int 8byte(qword) 0 ~ 0xffffffffffffffff

    注意这里的数据都是用补码表示的,当程序中的数据超过了其数据类型的范围,就会造成整数溢出。

    扩展原理

    从小的数据类型扩展到大的数据类型,首先扩展是根据小的数据类型来说的,小的数据类型是无符号就做无符号扩展,是有符号类型就做有符号类型扩展,扩展到于大的数据类型同样的位数。然后根据大的数据类型是什么来决定这个数的具体输出。

    #include <stdio.h>
     
    int main(void)
    {
        char a = -1;
        unsigned char b = -1;
        printf("%u
    ", a);
        printf("%u
    ", b);
    }
    


    无论是不是unsigned,内存中存储的char类型的值都是一样的,都是0xFF。
    printf("%u")输出时,对char类型的变量会类型提升为int型。
    因为char是带符号数字类型,所以会进行符号拓展。a从0xFF拓展为0xFFFFFFFF(4294967295)。
    unsigned char则会进行无符号拓展,b从0xFF拓展为0x000000FF(255)。

    上界溢出

    # 伪代码
    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
    

    上界溢出有两种情况,一种是0xffff + 1,第二种是0xffff + 1。
    因为计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在(编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)。
    所以add 0x7fff, 1 == 0x8000,这种上界溢出对无符号型就没有影响,但是在有符号短整型中,0x7fff表示的是32767,但是0x8000表示的是-32768,用数学表达式来表示就是在有符号短整型中32767+1-32768。
    第二种情况是add 0xffff, 1,这种情况需要考虑的是第一个操作数。比如上面的有符号型加法的汇编代码是add eax, 1,因为eax=0xffff,所以add eax,1
    0x10000,但是只把 ax=0x0000 的值储存到了内存中。无符号的汇编代码是对内存进行加法运算add word ptr [rbp - 0x1a], 1 == 0x0000,从底层结果上有符号和无符号结果是一样的。
    再从数字层面看,在有符号短整型中,0xffff==-1,-1 + 1 == 0,没问题。可是在无符号型中,0xffff == 65535, 65535 + 1 == 0,明显结果不对。

    下界溢出

    # 伪代码
    short int a;
    
    a = a - 1;
    # 对应的汇编
    movzx  eax, word ptr [rbp - 0x1c]
    sub    eax, 1
    mov    word ptr [rbp - 0x1c], ax
    
    unsigned short int b;
    
    b = b - 1;
    # assembly code
    sub    word ptr [rbp - 0x1a], 1
    

    一样是两种情况,一种是sub 0x0000, 1 == 0xffff,对于有符号来说 0 - 1 = -1没问题,但是对于无符号来说就成了0 - 1 == 65535。第二种是sub 0x8000,1 == 0x7fff,对于有符号来说是-32768 - 1 = 32767有问题,而无符号32768 - 1 == 32767是正确的。

    例子

    未限制范围

    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
    }
    

    这里read的data_len是sizet型,是无符号的,-1相当于很大的数。只申请 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;
    }
    $ gcc test2.c
    $ ./a.out
    4294967296
    vuln
    

    上述代码就是一个范围大的变量 (长整型 a),传入 check 函数后变为范围小的变量 (整型变量 n),造成整数溢出的例子。
    已经长整型的占有 8 byte 的内存空间,而整型只有 4 byte 的内存空间,所以当 long -> int,将会造成截断,只把长整型的低 4byte 的值传给整型变量。
    在上述例子中,就是把 long: 0x100000000 -> int: 0x00000000。
    但是当范围更小的变量就能完全的把值传递给范围更大的变量,而不会造成数据丢失。

    只做了单边限制

    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,属于无符号长整型

    内容来源

    printf按照%u打印时的符号扩展问题
    CTF Wiki Introduction to The Principle of Integer Overflow

  • 相关阅读:
    Java中四种XML解析技术
    Android ui utils简单实用的Android界面工具
    Android SAX解析xml文件
    Android SAX解析XML
    《Unix & Linux 大学教程》 第一、二章 学习笔记
    Eclipse设置背景色
    Java中getResourceAsStream的用法
    Android中asset文件夹和raw文件夹区别
    《Unix & Linux 大学教程》 第三、四章 学习笔记
    Android SAX解析实例教程
  • 原文地址:https://www.cnblogs.com/luoleqi/p/13437001.html
Copyright © 2011-2022 走看看