2个字节能存多少个16进制_多个二进制分析工具各项性能进行的分析与比较

478d48b4b4f0b2f3d20d3f0a8f58c728.png看玄武的公众号的时候看到了这篇文章。 二进制工具分析的主要是程序的各种信息,比如格式、体系结构、编译器标识、指令、助记符等。

反汇编

概览

IDA是HexRays公司的商业产品,这个也是大家最熟悉的了。 不过我是真的用不起,羡慕那些有钱的实验室还有土豪们啊。 过去几年还有其他的工具出现,比如Binary Ninja(二进制忍者,一看就知道是日本人的东西,貌似他们CTF战队也叫这个名字), radare, Ghidra(NSA出品最近又爆出有RCE洞,不知道是不是故意留的后门)等。 Angr,Shellphish的东西,这个做漏洞自动化挖掘利用的应该都知道。 插个题外话,这些真正的大牛们,是真的在做研究搞技术,他们玩的CTF才是真的CTF吧。 二进制分析软件还是很多的,文章中没有讲Windows下的OD和WinDbg,做Windows安全的,肯定会用到这两个。

反汇编结果

下图是这些反汇编工具的特性比较:

988ae496a2bb33370f1c7017b6483b52.png

文章选取了三个ELF文件: 小于900KB的elf-Linux-x64-bash、17MB的delta_generator 34MB的llvm-opt,分别作为小、中、大程序。 这三个程序是随机选择的,没有什么特别的地方,也都是开源项目中的常规程序。 使用的机器是Dell XPS 15,处理器是 Intel® Core™ i7-6700HQ CPU @ 2.60GHz,内存16G,操作系统使用的是Debian 10 (Buster)。

62c174dda78f1063301c7b1ade57fee7.png

对这个表进行下说明: 反汇编,肯定不能把程序的“真实面貌”给展示出来,IDA和Ghidra的分析结果可以认为是“近似真实”。 Angr通过代码模拟执行来做控制流分析,进而对二进制文件进行正确的反汇编。 不过它只能适用于小程序,对于大型程序的分析还是很困难的。 当然前景还是非常好的。 Miasm需要获得程序的入口点来进行反汇编。 它递归地从一个入口点执行反汇编,直到找不到任何的指令为止。 它的效率不是很高,对小程序,反汇编花费了近4分钟,对大程序的反汇编用了2个小时。 从设计角度来讲,这个工具是面向指令的,所以在发现函数方面的表现可以说是非常差的。 Delta_Generator程序的x86_64版本用于DDISAM,因为工具不支持x86。 IDA和Ghidra都能检索到足够的指令和函数,这是其他的工具不能比的。 不过Ghidra的反汇编速度要差不少。  

导出器

概览

下面将反汇编程序导出到独立文件中,主要目的是在初始化反编译步骤后关闭反编译器,因为后面就不需要这个功能了。 下图是上节测试的工具可使用的导出器:

b01b072109844e5bb3f493ed60f5d804.png

从上表中可以看出: Ghidra提供了IDA插件来生成XML文件和原始数据文件,可以将IDA的反汇编结果导入到其中。 BinDiff使用BinExport从IDA导出反汇编结果。 BinExport的作者之一已经在Ghidra上开了一个导出的功能端口(POC可以在他的GitHub中找到,工作的还是不错的)。 ddisasm能够分析二进制文件,并通过Protobuf文件导出大量信息,用于实现二进制重写。 因此导出的特征集中在对task有用的专有信息上。 这部分信息只是所有有用信息的一个真子集。 还有不错的导出器: Diaphora: 把二进制文件导出到Sqlite数据库,并用Python编写。 不过Sqlite文件比i64大出4到6倍,实用性要差一些。 Yaco: 不导出任何低于基本颗粒度的信息(仅导出hash),这个工具也是生成FlatBuffers文件的唯一工具。 bnida: 是用于将projec从IDA移植到Binary Ninja的插件。 它导出到一个json文件并用python编写。 它不会导出任何关于函数内容的数据,只有它们的名称和地址。

导出特性

下表详细列出了不同导出器导出的各种信息:

9753a7a4070f2a6d07fbe052318ea3a1.png

不同导出器的目的不一样,从二进制文件中导出的信息类型也就不一样。 BinExport设计的是作为diffing引擎的一部分,ddisasm则是作为二进制重写工具链的一部分。 注意下Ghidra-XML一列,这些导出信息在IDA和GHIDRA的实现结果会有不同,比如函数的类型。 导出器有两个主要策略: 第一种策略是导出反汇编指令及其内容信息(助记符、操作数、操作数内的表达式等)。 使用此策略,导出过程是独立的,不需要其他工具来分析它; 第二种策略是只导出原始字节(指令)本身,并将剩余的反汇编工作留给另一个反汇编程序(例如capstone)。 该策略更加紧凑,但需要一个辅助工具来理解导出的内容。 策略的选择取决于工具的最终目标,Ghidra不导出反汇编指令是有道理的,因为它们有自己的反汇编程序; binexport导出所有指令也是有道理的,因为bindiff是自治的(并且尽可能快)工具。  

全基准

重点研究选择性能最强大的两个: IDA和Ghidra。 对他们的导出器的细节进行比较。 BinExport的接口处于实验阶段,性能还不够,不做深入探讨。

数据集

文章收集了来自不同来源的各种二进制文件的数据集,包括各种体系结构、文件格式、大小和比特数的二进制文件: 二进制样本: Jonathan Salwan的二进制分析工具的测试套件。 AOSP(Android开源项目): 一种用于移动设备的开源操作系统。 llvm: 编译器基础设施项目。

fd3e1c1eb9bf2e47ab26894734ea91ce.png

b4ccf8ffa3479a8c2db7aee7efa49ca0.png

对上图做几点说明: 上图显示的是数据集中每个程序的指令数。 由于图纵轴的跨越范围很大(从0到8百万),所以除了后面四个的点,前面的点几乎都是在一条直线上。 如果要对百万条以上指令的程序分析能力进行比较的话,还要增加一些大型程序的用例,上图中使用最大的程序是46.83 MB的clang-check。 按第一节的标准,只能算做一个中型程序。

反汇编时间

感兴趣的第一个指标是反汇编的时间,也就是自动分析的持续时间。 IDA比Ghidra快,那快多少呢?

bb73e205034747c21317033abebd2c87.png

从上图的结果来看,Ghidra比IDA慢得多,对测试的大型二进制程序,慢了有13倍。 不过有点得注意,这个结果是有偏差的,因为Ghidra会执行一个额外的反编译步骤。 当然从使用者角度来讲,肯定是IDA的体验更好了。

导出时间和大小

另一个重要的指标是反汇编程序+导出器组合的导出时间,主要研究三种组合: IDA + BinExport IDA + Ghidra XML Ghidra + XML

f5bb80415280dc1c78b783bfe95dffbb.png

5bce3d1ca404721d8f2db37b695b0bb4.png

对IDA和Ghidra,程序的导出大小都远远大于程序本身。 当BinExport生成一个Protobuf文件时,Ghidra会生成两个文件——一个是带有所有信息的XML文件,一个是原始字节文件。 这其中包含所有导出的二进制代码。 图上坐标的数字表示这两个文件的大小之和。

38a163182da9a64f8a2c05d09d89cdcf.png

可以看出,BinExport和XML的导出大小大致相同。 不过,BinExport比Ghidra导出了更多的二进制文件信息。 要注意的是,除了原始字节外,Ghidra不会导出指令本身的任何信息,也不会导出基本块的任何信息。 导出文件的格式采用了紧凑性设计(例如,重复删除表的广泛使用),导出文件使用二进制序列化协议Protobuf。 这将在下文中进一步讨论。 上表还包括IDA生成的数据库的大小,即i64文件。 它比本研究中考虑的任何导出文件都大得多。

全导出

为总结先前测试的结果,文章绘制了下面的图表,用于说明导出过程的三个阶段中所花费的时间: 反汇编阶段: 反汇编二进制文件; 导出阶段: 生成导出文件; 反序列化/加载阶段: 在Python中导入导出的文件。

9436d366160bf305ece8b67f5e422d73.png

可以看出,对大型二进制文件的Protobuf格式(这里是MdBook),反序列化时间是不可忽略的。  

二进制序列化格式的实验

由于持久存储、RPC通信、数据传输等功能的需求,所以会有很多不同的序列化格式。 我们自然希望以“人类可读”的方式存储数据(即文本,如JSON,XML)。 这种格式可以快速访问,存储也比较紧凑。 但对于程序序列化而言,需要在磁盘开销、反序列化时间和内存占用之间进行权衡,显然二进制序列化格式更合适。

二进制序列化格式

本节主要关注三种二进制序列化格式: Protobuf:由谷歌开发并广泛使用的一种格式,用于结构化数据的串行化。 FlatBuffers:谷歌开发的另一种格式来序列化数据。 主要用于特别重视性能的应用程序。 Cap’n Proto:由Kenton Varda(在谷歌工作期间是Protobuf的技术主管)为沙暴(SandStorm)开发的格式。 沙暴是一个个人云服务系统,使用者可以使用一个简单的APP 商店安装一些像邮箱、文件编辑、博客软件等。 在这里只能佩服下谷歌的技术实力。 所有这些格式都使用自定义的模式语言来解释数据如何格式化。 简单看下三种协议的基本代码: Protobuf:
message Meta {  optional string executable_name = 1;  optional string executable_id = 2;  optional string architecture_name = 3;  optional int64 timestamp = 4;}
FlatBuffers:
table Meta {  executable_name:string;  executable_id:string;  architecture_name:string;  timestamp:long;}
Cap’n Proto:
struct Meta {    executableName @0 :Text;    executableId @1 :Text;    architectureName @2 :Text;    timestamp @3 :UInt64;}
这些格式之间的主要区别在于它们如何在线路上存储数据。 Protobuf是最老的一个,它使用编码/打包的步骤来转换线路上的输入。 这使得Protobuf更加紧凑,因为编码步骤减少了存储对象所需的字节量(参见Protobuf文档中的编码)。 FlatBuffers和Cap’n Proto则都使用“零拷贝”策略,这意味着线路上数据的结构与存储器中数据的结构相同。 这种技术的主要优点是没有解码的步骤。 Flatbuffers/Cap’n proto和Protobuf之间的另一个巨大区别是随机访问读取的能力,即在不读取整个消息之前读取消息的特定部分的能力。 对于Protobuf来说,这是不可能的,因为消息需要在前面进行解析以及内存分配。 而Flatbuffers和Cap’n proto都使用指针实现了这个特性,允许快速访问消息的一部分。 对于FlatBuffers ,分配(即如何编写消息)必须从下到上进行。 因为消息必须在另一个消息开始之前完成。 Protobuf没有这个限制,因为所有消息都在结尾写入。 Cap’n Proto也没有这个限制,因为分配时知道对象的大小。 最后一个区别是如何将未设置的字段(即没有此特定消息值的字段)存储在线路上。 Protobuf和Flatbuffer都不分配它们,而Cap’n Proto仍然分配它们。 这就浪费了Cap’n Proto的空间。

基准

对于这些基准,我们将BinExport Protobuf翻译成FlatBuffers和Cap’n Proto模式。 对于Cap’n Proto,翻译是手动完成的,并使用了选项—flatc proto for Flatbuffer,并加上一些小的修改。 接着进行比较。 首先要比较导出的文件与二进制文件本身相比有多大。

c63bac9e6d955a408427682d6dfa6639.png

从图中可以看出导出文件的大小与二进制文件的大小成非线性增长。 下图显示了导出文件大小与二进制文件大小之间的比例。

1a358a2205e3754ddfaa67b1e9852fcd.png

可以看出,Protobuf比其他两种更紧凑,因为编码步骤是至关重要的这一部分。 其他两个协议的导出大小仍有改进的余地,主要是通过更好地理解不同值的范围。 使用Protobuf,可以将每个整数声明为64位的宽整数,序列化算法只写入数字的varint编码值。 但是,对于Cap’n Proto和Flatbuffers,这个值无论如何都需要64位长。 另一个研究点是用多少内存来加载Python中的序列化文件。 需要注意的是,要使用MexyyPrPosiror模块检索内存使用情况。

0300d94b3d98d7d84881b7ca2cec7fd1.png

不出所料,加载二进制文件的导出所需的内存对于Protobuf来说要重要得多。 例如,对于llvm-opt,Protobuf文件大约为150mb,但要占用1.8G的内存。 最后一个指标是,从三个文件格式加载python格式的导出文件需要多长时间。

b32dd63704014f3092102a29a8f38249.png

不出所料,Protobuf需要大量的时间来进行反序列化。 Cap’n Proto和Flatbuffers具有相似的性能,主要是因为它们基于相同的模式。 还有两点需要注意的地方: 可以通过应用它们的“打包”算法来缩小Cap’n Proto输出文件的大小。 但是,这会删除“零拷贝”协议的属性。 这是否比Protobuf更好,还需要更多的实验来了解。 使用熟知的算法压缩导出的文件,也是Cap’n Proto和Flatbuffers的可行策略,因为它也会减少导出文件的大小。 但这会增加一些时间,因为需要在使用之前解压缩文件。 这一点不适用于Protobuf,因为格式已经很紧凑了。  

结论

从二进制输出尽可能多数据的过程本身并没有意义,但却是其他工作的基础,如机器学习算法的特征提取、图形遍历算法、或基于用户定义标准地对函数/块/指令的快速访问。 这篇文章探讨了使用导出器从反汇编程序导出反汇编程序的不同选项。 总来的来看,功能最全的导出器是BinExport,它可导出大量的信息。 同时由于使用的序列化格式Protobuf,能够保持紧凑。 不过所有的二进制导出器仍有改进的空间,在可伸缩性方面还有很大不足。  

结尾

老外的文章学术性很强,有很多数据和实验支撑。 这篇文章我开始觉得没有啥“意义”,不像那些工具的介绍、攻击手法的讲解、漏洞的分析利用这类的文章。 但是后面一想,这样的文章是非常值得学习的,一种探索研究的过程,不单是技巧方面的研究,这些枯燥的内容恰恰是我们欠缺的吧。

6f87ac3515eb320cf9ac5b7a79c86734.gif

### 回答1: 是的,8字节能表示的最大浮点数比能表示的最大实数要大得多。因为浮点数是用科学记数法表示的,所以可以表示比实数更大的数,但同时也会有精度损失的问题。而实数可以表示所有实数,但是需要无限的数,因此不能用有限的字节数来表示。 ### 回答2: 8字节能表示的最大浮点数比能表示的最大实数要小得多。 在计算机中,浮点数采用IEEE 754标准来表示,通常采用32或64来存储,其中64就是8字节。根据这个标准,浮点数的存储可以分为三个部分:符号、指数和尾数。 在64浮点数中,符号占用1,指数占用11,尾数占用52。根据指数数,可以表示的指数范围为-1024到1023。根据尾数数,可以表示的小数精度为2的-52次方(约为2.22×10的-16次方)。因此,根据这些限制,64浮点数能表示的最大浮点数约为1.8×10的308次方。 而实数是指包括整数、有理数和无理数等所有实数的集合,它们具有无穷多的数字和精度。实数是连续的,无法用有限的数来表示。因此,无论是采用多少字节的存储,计算机都无法完全精确地表示所有的实数。只能通过近似来表示实数。 综上所述,8字节能表示的最大浮点数是有限的,而能表示的实数是无穷大的,所以8字节能表示的最大浮点数比能表示的最大实数要小得多。 ### 回答3: 在计算机中,浮点数的表示方式采用的是IEEE 754标准,一般使用32或64二进制表示。对于32浮点数,其中1用来表示符号,8用来表示指数,剩下的23用来表示尾数。对于64浮点数,1用来表示符号,11用来表示指数,剩下的52用来表示尾数。 根据IEEE 754标准,浮点数的表示范围是有限的,而实数是无限的。浮点数能够表示的最大正数和最小正数取决于指数的范围,尾数的长度以及尾数的精度表示。32浮点数能够表示的最大正数约为3.4 × 10^38,最小正数约为1.4 × 10^-45。64浮点数能够表示的最大正数约为1.8 × 10^308,最小正数约为4.9 × 10^-324。 与之相比,实数是没有上限和下限的。实数的范围是无穷的,包括所有的有理数和无理数。因此,实数的范围要比浮点数大得多。 但是,浮点数在计算机中的表示方式是有限的,并且存在舍入误差。这意味着浮点数不能完全准确地表示所有的实数。在进行浮点数计算时,我们需要注意舍入误差可能带来的影响,以及避免由此带来的计算错误。 综上所述,浮点数能够表示的范围是有限的,实数的范围是无限的。虽然浮点数能够表示的最大数值比实数要小得多,但实数是浮点数的一个超集,包含了所有可能的数值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值