整数溢出漏洞和格式化字符串漏洞

漏洞成因:

在计算机中,整数类型分为无符号整数和有符号整数 两种。

有符号整数会在最高位用0表示正数,1表示负 数,而无符号整数则没有这种规则。

常见的整数类型有8位(单字节字符类型、布尔类型)、 16位(短整型)、32位(长整型)等。

当一个整数存入了比它本身小的存储空间中,超出了 数据类型所能表示的范围时,就会发生整数溢出。

1. 基础知识

整数数据类型 数据类型 :

 

2.漏洞原理

2.1上溢

先看如下程序:

输出如下:

上溢:一个整数通过运算(赋值),超过了它能 表示的上限,则会变成一个很小的数。

例1:一个unsigned short类型的变量a = 65535, 当a累加5时会变成4

这是因为在计算机中,(65535)10 + (5)10 = (1111111111111111)2 + (101)2 = (10000000000000100)2,而由于unsigned short 数据类型的空间是16位,最高位的1被丢弃,因此只保 留了低16位,即(0000000000000100)2 = (4)10

例2:一个short类型的变量a = 32767,当a累加5时会 变成-32764

这是因为在计算机中,(32767)10 + (5)10 = (0111111111111111)2 + (0000000000000101)2 = (1000000000000100)2,而由于short数据类型的 最高位为符号位,0表示正数,1表示负数,因此上式运 算后超出了short类型的数据范围,造成上溢,即 (1000000000000100)2 = (-32764)10

[注]在计算机中,整数以补码形式存储,正数的补码与原 码一致,负数的补码等于原码按位取反+1 (1000000000000100)2的补码,等于反码后 (111111111111011)2,再加1,等于(-11111111111100)2 ,即(-32764)10

下溢:

下溢:一个整数通过运算(赋值),低于它能表 示的下限,则会变成一个很大的数。

例3,由于无符号整数不能识别负数(无符号整数类型 接收一个负数会变成一个大正数),因此一个short类 型的变量a = -4赋给unsigned short类型的变量b时 ,b = 65532

这是因为-4是负数,在计算机中以补码形式存储,-4 的补码为(-4)10 = (1111111111111100)2,而由 于unsigned short数据类型的首位不表示符号位,所 有位全部用来表示数据,因此b =  (1111111111111100)2 = (65532)10

截断:

截断:将数据放入了比它本身小的存储空间中, 从而出现了溢出(短整型接收整型时只能接收低 16位)。

例4,一个int类型的变量a = 65537赋给short类型的 变量b时,b = 1

(65537)10 = (*10000000000000001)2

符号问题:

符号问题:指符号问题引发的整数溢出漏 洞,大致有三点需要注意

a.有符号整数之间的比较

 从汇编中可以看到,当两个有符号整数比较大小时 ,是将两数作差,若结果为正则显示被减数大,若 结果为负则显示减数大

 当两个有符号整数作差时出现上溢或下溢时,比较 结果会与事实相反。 

 例如,short类型的32767与-5比较大小时,由例 1可知,在计算机中,short类型的32767-(-5) =-32764<0,从而得出32767<-5的错误结论。

b.有符号整数的运算

当两个有符号整数运算时,有可能发生上溢或者下溢。

c.无符号整数和有符号整数的比较(运算)

  当无符号整数和有符号整数进行比较或运算时,会将有符号整 数转化成无符号整数之后进行比较或运算,从而导致上溢下溢 以及与事实相反的结论。

  例如,short类型的-5与unsigned short类型的13比较大小 时,-5转化成无符号整数后等于65531>13,与事实相反。

2.3.实例分析

输入一个合适的数值,使得程序显示“Welcome!”

number和Number的数据类型不同,可以表 示的数据范围不同,因此,可以通过整数溢出 的方式

输入一个合适的较大的值给Number,使得在 Number给number赋值时发生截断之后, number的值较小,从而满足if条件。

 

3.数组越界漏洞

3.1基础知识:

数组的原理 :

数组是内存中一段连续的存储空间,一个数组 中包含多个类型相同的数组元素

数组通过数组名在内存中找到对应的数组空间

数组元素通过数组名和索引获取

程序定义了含有10个元素的数组a,依次打印 出数组元素的地址:

运行结果显示,各个元素之间的地址相差一个 int类型数值的大小,a[4]的地址与a的地址相 差0x10个字节,即4*sizeof(int)大小

通过IDA的反汇编进一步明确元素的寻址方式 ,IDA能够获取各个变量用ebp表示出的地址 ,数组a的地址为ebp-0x34:

 var_34的值是-34,var_38的值是-38,变 量i的地址是ebp-0x38。这段代码将i的值赋给 eax和edx;然后将edx的值赋给数组元素,每 个数组元素的地址为ebp-0x34+eax*4, ebp-0x34是数组a的地址,eax*4是索引 i*sizeof(int)。

由此确定数组元素寻址的方式为:数组地址+ 索引*数组元素大小

数组越界漏洞实例 :

Adobe Flash CVE-2014-0497

  Flash在对AS3代码进行解析时出现的一个数组越 界漏洞,Ii32函数的一个参数是数组的索引值,这 个函数会将数组对应位置的值取到变量中,但由于 边界检查存在问题,导致攻击者可以构造特殊的数 值绕过边界检查。

  攻击者利用这个漏洞,可以对内存的其它区域的数 据进行读取。

CVE-2016-7193

    一个存在于Microsoft Office Word中的一个数组 越界漏洞。该漏洞在Word解析RTF文件格式时会被 触发,成功利用后可以远程执行代码,进而完全控 制受害者主机。在函数中,某数组实际分配的大小 为0x20,而在程序逻辑中,对该数组的边界限制则 是0x40,导致该数组后面的0x20数据能被写入而 改变。

    在攻击者的精心构造下,该漏洞能够使得用户在打 开构造的RTF文件后执行payload。

数组越界的防范 :

Address Sanitizer是一个快速内存错误检测工具,它 由编译器检测模块和运行时库组成。

Address Sanitizer能够检测包括堆区越界、UAF在内 的内存错误。它是LLVM/Clang编译器的一部分,在编 译程序时使用-fsanitize=address参数能够开启 Address Sanitizer。

C和C++(特别是C)的安全性大多需要依靠开发人员 ,所以对于数组越界的防范需要开发人员在编程时, 控制好对数组的边界访问。

3.2漏洞原理

C和C++数组的边界检查 C和C++不对数组做边界检查,除了语言对编 程人员信任和程序性能的顾虑外,C和C++的 数组边界检查本身也是一件困难的事情。

   数组越界的判定不仅依赖于下标的值,也依赖于指 针的类型。

   对于指向数组的指针来说,在程序中若没有显式的 指明数组长度,还需要证明其地址计算结果位于该 数组内。

   程序运行时,数组可能重新进行了动态分配,长度 发生了变化。

 

格式化字符串漏洞

1.基础知识

printf :

向stdout按规定的格式输出信息

int printf (const char *format,[argument]…)

format是格式控制字符串,其他参数为输出项

sprint :

把格式化的数据写入某个字符串中

int sprintf(char *buffer,const char *format, [argument]…)

       buffer是要写入字符串的缓冲区

       这个函数就是把第三部分的数据,按照第二部分格式化字符的格 式,把第三部分的数据进行”格式化“,然后在把格式化后的数据类型,存储到字符串的缓存区间里去

Snprintf

在sprintf的基础上限制了可写入字符的最大值n

当格式化后的字符串长度<size,则将字符串全部 复制到str中,并在最后添加字符串结束符’\0’;当 格式化后的字符串长度>=size,则将其中的size-1 个字符复制到str中,并在最后添加字符串结束符 ’\0’

int snprintf (char *str,size_t size,const char *format,[argument]…)

fprintf

用于格式化输出到一个流/文件中

int fprintf(FILE *stream,const char *format,[argument]…)

根据指定的格式控制字符串format向输出流 stream中写入数据

当stream为stdout时,fprintf与printf的功能相同

vprintf、vsprintf、vsnprintf、vfprintf

功能分别对应于printf,sprintf,snprintf和 fprintf

将变参列表换成了va_list类型的参数

 

格式化字符串是由普通字符串和格式化规定字 符构成的字符序列 :

    1.普通字符被原封不动地复制到输出流中

    2.格式化规定字符则是以’%’开始,用来确定输出内 容格式。

格式化规定字符的基本格式如下: %[parameter][flags][fieldwidth][.precision] [length]type

%[parameter][flags][fieldwidth][.precisi on][length]type

parameter :

可以忽略或者是n$。n表示是参数列表的第n个参 数,通过这种形式直接访问第n个参数

flags:

用于调整输出和打印的符号、空白、小数点、八进 制和十六进制前缀等

 

%[parameter][flags][fieldwidth][.precisi on][length]type

fieldwidth :限制显示数值的最小宽度,当输出字符个数不足限 制的宽度时,默认用空格填充,或者flags中的其他 填充方式;超过限制宽度不会截断,正常显示

precision :输出的最大长度

%[parameter][flags][fieldwidth][.precisi on][length]type

length : 指浮点型参数或者整形参数的长度

2. 漏洞原理

1. 泄露内存数据

printf按照format的要求,输出了3个数值,但这 些数值并不是输入的参数,而是保存在栈中的数值 ,打印出的这三个字符串正是位于format参数之后 的数据。

 

2. 覆写内存

格式化输出函数除了能读取内存数据之外,还能够 通过转换说明符%n向指定地址写入一个整数值。

向任意地址写入数据的能力,可以被用于覆写内存 中的关键数据。

fs_write.c演示了利用格式化字符串漏洞,修改 flag变量的值。

fs_write.c演示了利用格式化字符串漏洞修改flag 变量的值。flag的初值为0xbabe,利用格式化字符 串漏洞,修改为0xbeef。

为了方便演示,先暂时关闭系统的ASLR保护。

复写内存的核心功能在printf(format)中实现,具 体的思路是:

    首先在格式化字符串中放入flag变量的地址

    然后利用“$”来指定这个地址位于第几个参数,

    最终使用“%n”(即hhn)向这个参数所指向的地址处写 入数据。

首先strcpy()函数利用IDA事先分析后获取 的flag变量地址,拷贝给format。

使用IDA事先查看flag的地址为0x804a028,由于 要向flag中写入0xbeef,也就是说要向 0x804a028和0x804a029两个地址中写入内容, 因此将这两个地址放入到format中。

strcpy(format,"\x28\xa0\x04\x08");

strcat(format,"\x29\xa0\x04\x08");

然后确定写在format数组里的两个地址属于printf 的第几个参数,通过gdb进行调试,在 printf(format)处设置断点进行查看。

下图所示,format的地址为0xffffce4c,是printf 的第11个参数,即(0xffffce4c-0xffffce20) /4=11,由于flag的两个地址写在了format最开始 的位置,因此它们分别是printf的第11和第12个参 数。

最后利用“%n”(即hhn) 向flag的两个地址中进行写入

由于%n是将之前打印的所有字符数写入到某一地址中, 所以要计算好打印的字符数。这里我们构造格式化字符串 的格式如下: % width c % num $ hhn % width c % num $ hhn 通过for语句的两轮循环,写入两轮% width c % num $ hhn。

hhn表示每次写入一个字节到地址中。

% width c % num $ hhn:

     width用来计算正确的值以写入到%hhn中。 

     计算width是通过已写入的字节数和要写入的值进行计算

         例如,为了写入0xef到指定地址,由于在format数组的 起始部分已经写入了8字节的flag地址,所以应该再填充 0xef-8个字节(ef转成10进制为239再减8为231,即算 写入的字符个数。);同理,写入0xbe时要减去前面写 过的0xef长度,得到需要填充的长度,为了防止这个数负 值,加上0x100再取模。

     num指定写入到第几个参数,在上一步已经确认了是第11和12个

     hhn表示每次写入一个字节到地址中。

最终format中的内容为: \x28\xa0\x04\x08\x29\xa0\x04\x08%231c %11$hhn%207c%12$hhn

3.漏洞示例分析

程序编译时会采用两种表进行辅助,一个为 PLT表,一个为GOT表 

    PLT表可以称为内部函数表

    GOT表为全局函数表(也可以说是动态函数表)

    这两个表是相对应的,PLT表中的数据就是GOT表 中的一个地址,可以理解为一定是一 一对应的

    PLT表中的每一项的数据内容都是对应的GOT表中 一项的地址(这个是固定不变的

    看到带有@plt标志的函数时,这个函数其实就是个 过渡作用,可以通过PLT表跳转到GOT表来得到函 数真正的地址

 

可通过3轮格式化字符串漏洞,实现任意读写 。 另外为了多次利用漏洞,使main函数循环执 行。

num的确定

先查看buf的地址是printf的第几个参数,在调用 printf处设置断点,buf的地址为0xffffcfac,是 printf中格式化字符串的第7个参数,即( 0xffffcfac-0xffffcf90)/ 4 = 7。

因为要把exit的GOT地址覆写为main函数地址, 即0x8048648,所以应写入四个字节,即重复四 次% width c % num $ hhn。

粗略估计width最多占用3个字节,num最多占用2 个字节,则每个格式“% width c % num $ hhn”占用12个字节,四次重复共48个字节,占用 48/4=12个参数。

由于buf是从第7个参数开始,写入的地址从第 7+12=19个参数开始。num依次为19、20、21 、22

漏洞利用

编写generate_format(addr, value)函数构造格 式化字符串,addr为要覆写的地址,value为覆写 的值。

调用generate_format(exit_got,main),生成的 payload作为输入

循环执行main函数

执行完printf,格式化字符串漏洞就会将exit@got 覆写为main函数的地址

获取system函数地址

最终的目的是获取到shell,即实现 system(“/bin/sh”)。由于格式化字符串漏洞能够 泄露内存关键数据,可以考虑利用这个漏洞泄露出 system的地址

利用格式化字符串漏洞,泄露出GOT表中puts的地址,再利用libc中system函数与puts函数的偏移, 计算出system地址。

构造的格式化字符串格式为: %num$s+puts@got,即把puts@got的地址写 入buf,再通过%s读出。

其中%num$s占4个字节,是第7个参数; puts@got占4个字节,是第8个参数,num就可以 写为8,即将puts@got的地址写入到第8个参数的 位置

获取了puts的实际地址后,通过libc中两个函数的 偏移即可得到system的地址

覆写GOT表中printf的地址

原理与覆写exit函数GOT表相同,调用 generate_format(printf@got,system_addr), 生成的payload作为输入。

执行system(‘/bin/sh’)

此时GOT表中printf地址已被覆写为system地址 ,在buf中输入’/bin/sh’,执行printf(buf)时, 相当于执行system(‘/bin/sh’)

  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值