• 浮点数的存储方式和ctf实战


      1、(1)数字分两种:整数和小数。之前介绍了整数溢出,本文介绍小数(浮点数)的存储和表示方法;整数的表示方法很简单:按照一定的计算方式转成二进制即可,比如10进制的9转成二进制1001,内存中最小存储单元是字节,也就是8bit;如果用1byte存储9,那么转成二进制就是00001001,这个应该不难理解;那么小数或浮点数该怎么表示和存储在内存了?

           (2)举个例子:6.625这个浮点数,整数部分是6,小数部分是0.625,要想存储在内存,必须先转成二进制的形式;整数部分和小数部分转换的方式刚好相反,如下:

         (2.1)整数部分6:这个很常见,每次除以2,用商继续除以2,直到商为0为止。6的二进制表示就是110;

                               

                    (2.2)小数部分0.625:这个该怎么表示成二进制了? 整数部分是除以2,小数部分刚好反着,就是乘以2,计算过程如下:小数部分就是101

               

          (2.3)6.625用二进制小数“表示”就是:110.101;怎么验证110.101转成10进制后就是6.625了?整数部分很好验证:2^2+2^1+0*2^0=6,小数部分怎么验证了?形式和整数是一样的!

               1*2^(-1)+0*2^(-2)+1*2^(-3)=0.5+0+1/8 = 0.625

                       通过上述互相转换验证了小数部分转成二进制方式是正确的!

           (3)内存从硬件上看都是无数个小电容构成的。电容充电就是1,放电就是0;所以内存中所有的数都是0101这种二进制串,是没法直接表示小数点的,这种情况只能继续通过人为抽象存储格式来存浮点数了。目前国际通用的浮点数存储规则是IEEE754,浮点数存储格式如下:

             

         按照上面的标准,110.101 = 1.10101*2^2( 二进制小数点左移两位,相当于翻2^2倍,和位运算中的移位是一样的!) ,此时进一步抽象出了几个关键的“描述符”:s=0,M=1.10101,E=2;到了这里上述问题还是存在:内存硬件上只能存01二进制串,怎么存(表)储(示)小数点了?

       (4)第(3)步抽象出s、M、E这些“描述符”后,利用这些信息是不是已经完全能够还原110.101这个浮点数了?答案是肯定的!套用上述的公式,能得到1.10101*2^2,根据这个进一步还原得到110.101;

    所以在内存中存储浮点数,等价于在内存中存储s、M、E这些关键信息;

       (5)s、M、E这些关键信息又是按照什么标准或方式在内存中存放的了?格式如下,以4字节的float为例: 最高位31位放s,23~30这8bit存放E,剩下23bit存放M;

            

         几点需要注意:

    • E是指数,所以有可能是负数,这8bit是怎么表示负指数的了?这里就和有符号数表示有本质区别了。有符号数最高位1表示负数,0表示整数,但这里不这么干,而是指数+127;比如这里的指数是2,那么E=127+2=129=1000 0001(所以这里指数最大不能超过2^127-1,表示浮点数足够了!);如果指数是负数,比如-5,那么E=127-5=122=111 1010;
    • M:所有M中小数点左边都是1,所以这1位可以省略,直接存剩下的位数;
    • M:只有23位,如果10进制的小数部分乘以2始终不为1,那么只取23位,其余的舍弃,所以这是部分浮点数有误差、无法精确表示的根本原因!最低为舍弃时如果是1就不能直接舍弃,还要+1;

        所以6.625这个浮点数经过一系列转换后,最终在内存中的存储形式如下:

             

         cpu从内存中读取这4字节数据后,按照上面的规则反过来计算出10进制的浮点数!

      2、这里来看一道2018护网杯CTF题目:getting_start;题目的代码是酱紫的:

         

         这里需要执行system("bin/sh")这行代码,也就是说上面if条件不能成立;这里v7已经相等了,只能看v8了;v8已经有了初始值,怎么才能让v8=0.1了、让if条件不成立了?

         这里能接受用户输入的地方有read,数据保存在buf中,所以这里只能通过控制buf的值来改变栈上的数据,当然也包括v8了!老规矩,先画个栈图,能直观理解:

       

       buf距离v8有32字节,而read会读取0x28=40字节,所以如果buf全部读取40字节,连下面的v9都能覆盖!不过这里v9和本题无关,暂时放过它!buf、v5、v6随便写,那么前24字节随便写,这里用“a”替代;由于v7要保持原值,所以第25~28字节是0x7FFFFFFFFFFFFFFF;接下来就是最重点的部分了:29~32字节、也就是v8怎么填写二进制数据,才能让这里的值等于0.1?这里先偷个懒:下面参考这里链接有个工具,可以直接把double类型的小数转成二进制形式,如下:

      

       那么最后4字节就是0x3FB999999999999A,所以最终的payload=b"a"*24+ p64(0x7FFFFFFFFFFFFFFF) + p64(0x3FB999999999999A);

      3、站在学习的角度,这里从头开始手搓一下0.1在内存中的存放形式;

      (1)0.1是10进制,先转成小数二进制的形式;按照之前的方法,每个步骤如下:

         

       这里出现了一个有趣的现象:从第6步开始出现了循环,所以没必要再挨个计算了,这里直接写出二进制的形式:0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011.....

      (2)二进制的形式有了,现在要提取s、M、E三个要素,这里要继续做格式转换。小数点向右移4位才遇到第一个1,所以这时表达式变成了:

           1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011*2^(-4)

         这里三个要素就明确了:s=0,M=1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 001 ,  E=(2^10-1)-4=1023-4=1019=011 1111 1011;

         注意:这里的M只保留52位时,最后一位是1,这位不能直接去掉,要在末尾+1(小数存放的误差就是这么来的),所以真正的M = 1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010

         (3)再按照标准把s、M、E首尾拼接(我这里为了区分三要素,人为用||隔开了,实际上是没有||的):0 || 011 1111 1011  || 1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010 ,也就是0011 1111 1011 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 = 0x3FB999999999999A;

    参考:

    1、https://www.bilibili.com/video/BV1j7411H7Fc?p=8  浮点数进制转换(讲的很详细,强烈建议看完)

    2、http://www.binaryconvert.com/result_double.html?decimal=048046049   非常好用的浮点数(float或double)10进制和2进制之间转换的工具

  • 相关阅读:
    linux 中的./configuration --prefix=安装路径 的用法(指定源码安装方式的安装路基)
    深入了解Activiti工作流流程定义
    ResultCode 自定义错误状态码
    maven和gradle对比
    js中的prototype原型解析
    json字符串的标准格式
    迷茫于Hibernate/JPA的人提一些建议。
    ModelDriven 和 Preparable 拦截器
    Spring中bean的scope
    spring配置文件详解以及beans:beans标签
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14544262.html
Copyright © 2020-2023  润新知