【学术论文】基于静态二进制分析的工控协议逆向解析

工业控制系统(Industrial Control Systems,ICS)是工业生产的基础,ICS的安全直接关系到经济、社会发展的稳定和国家安全。与其他计算机系统不同,工业控制系统中存在着大量的私有非标准非公开协议[1],而通信协议作为工控设备间信息交互的基础,其安全性是工控系统安全的重要组成部分,研究工控协议解析技术对提高工业控制系统安全性具有重要意义。

传统的协议逆向分析方法需要耗费大量的时间和人力。目前,协议自动化逆向解析领域主要有两种方法:一种是基于网络报文序列分析的方法,另一种是基于程序执行轨迹的方法。基于网络报文序列分析的方法对于文本协议处理效果比较好,但是缺乏针对性对协议的语义分析[2],尤其在面对工业控制环境中普遍存在的多层封装的应用层协议时有些力不从心;而基于执行轨迹方法以动态二进制程序分析为基础,包括Polyglot、AutoFormat、Tupni、Prospex等方法,这些方法要求样本的覆盖率,需要对程序反复地运行调试[3],但调试过程中对代码的直接修改严重破坏了工控系统的稳定性,虽然这种方法解析结果更为准确,但并不适合工业控制系统要求高稳定、高实时的实际情况。

针对以上问题,本文提出了一种基于静态二进制分析的工控协议解析方法,此方法以只读的方式访问二进制文件,既能获取协议语义,又能保证不破坏工控系统的稳定性。分析的结果除提供协议信息以外,还可以在通信协议健壮性测试平台中作为fuzzing测试模块的输入,解决普通的fuzzing系统在面对私有协议时无法有针对性地构造测试样本的问题。

1

总体设计

本文将此工控协议解析方法作为工业控制系统健壮性测试平台的一个子系统来加以介绍,协议健壮性测试平台结构如图1所示。

当今的ICS中越来越广泛地使用了基于x86平台的Windows操作系统,故本文中的方法在实现时主要针对Windows操作系统,反汇编引擎使用Hex-Rays公司的IDA Pro。IDA Pro是一款强大的静态二进制分析工具,能够提供功能丰富的IDC函数库(IDA Pro的一种原生脚本语言)和软件开发包(Software Development Kit,SDK)[4]

协议解析子系统主要包括以下子模块:文件扫描子模块、协议提取子模块和格式化处理子模块。其中,协议解析模块包括数据预处理阶段、交叉引用分析阶段、协议帧重构阶段、语义提取阶段;格式化处理模块的输出既可以作为测试平台模糊测试模块的输入,为模糊测试样本数据的构造提供参考,又可以作为测试平台交互模块的输入,为用户提供图形化的结果展现。协议解析模块的子模块结构如图2所示。

2

文件扫描模块

扫描模块的扫描目标是组态软件等运行于通用计算机的工控软件。通常,工控软件体积较为庞大,由众多功能模块构成,但协议分析只需要其中的通信模块,对ICS软件的所有功能模块执行协议解析算法不但会增加时间开销,也会降低分析的准确度。扫描模块的主要作用就是定位协议分析的对象,为协议分析模块过滤掉与通信无关的操作。

文件扫描子模块有两种实现方式,一种是远程扫描,扫描进程运行于测试平台,通过远程读取工程师站或操作员站上的ICS软件可执行文件并进行分析来实现过滤操作;另一种是以硬件插卡的方式通过USB接口在工控机本地执行扫描进程,并收集扫描结果反馈给测试平台。这两种实现方式的区别在于扫描进程的运行位置不同,远程扫描对工控机没有性能影响,但需要工控机打开文件访问权限;本地扫描在扫描过程中可能会占用一定的CPU时间,但不需要额外的权限,其具体差异如表1所示。

以上两种实现方法在算法本质上是没有区别的,都是通过读取动态链接库(Dynami Link Library,DLL)文件的导入表和导入函数表来查找和预测DLL涉及到的操作。一般来讲,涉及到TCP协议通信则需要导入WS2_32.dll中的send和recv函数,UDP协议则需要导入WS2_32.dll中的sendto和recvfrom函数[5],通过扫描模块搜索到的某协议通信模块的导入表以及导入函数如图3所示。另外,通过扫描并过滤WriteFile等系统调用还可以找到ICS软件自己封装的一些发包和封包函数,通过这些函数还可以解析那些不基于TCP/IP的工控协议,如基于COM串口的工控协议,这是基于网络流量的协议分析方法无法做到的。

3

协议提取模块

协议解析模块基于IDA Pro的IDC脚本和SDK来实现,以IDA脚本或IDA插件的形式提供协议解析服务。

3.1 数据预处理

ICS使用的软件种类繁杂,在实现结构上也是千差万别,有些厂商在设计软件时并没有严格地遵循模块化设计的原则,软件通信模块没有独立地封装在DLL中,而是与其他的功能代码混杂在一起放入DLL,甚至分散在多个DLL中。数据预处理针对这种情况,通过读取IDA Pro反汇编后的汇编代码,对DLL中的函数进行标记处理,剔除与通信过程无关的函数。对于无法确定的函数,则选择保留处理。

筛选算法同时使用两种标准,第一种标准的依据是向上的代码交叉引用,利用了函数调用的层次结构;第二种标准的思想源自于动态二进制逆向分析中常用的污点算法,经过修改后基于数据交叉引用实现,用于此处的静态二进制分析场景[6]

定义 二元组f(N,F)表示DLL中的一个函数,其中,N为函数名,F为标记,取值从UNKNOW、STAY、DELETE中枚举。

筛选算法的基本流程如下:

(1)将目标DLL中的函数(包括DllMain、导出函数和内部函数)f加入到函数集合S中初始的标记F均为DELETE。

(2)使用第一种标准,以发送、接收数据包的函数地址为底层起点,使用IDC函数Rfirst和Rnext访问其所有的引用函数fn(N,F),并将fn(N,F)的标记F置为STAY。

(3)迭代执行步骤(2),直至所有引用了起点函数的函数标记均被置为STAY。

(4)使用第二种标准,以步骤(2)中底层函数的参数中使用的内存缓冲区为污点源,逆序搜索被标记为污点的内存区域的访问位置,如果有fn(N,F)中引用了污点内存区域,则将fn(N,F)的标记F置为STAY;如果代码中存在以污点内存为左值的赋值操作,则将作为右值的内存区域也标记为污点内存,并记录污点传播的关系,系统为S中的每个fn(N,F)维护一个污点关系数据结构func_pollut,数据结构内容如表2所示;如果函数fn(N,F)没有显示地引用污点内存区域且其引用的其他内存位置无法直接判断是否与污点内存有关,则将fn(N,F)的标记F置为UNKNOW。

(5)迭代执行步骤(4)。枚举集合S中的函数,删除所有标记F为DELETE的函数元素,此时的集合S则为待处理的目标集合。

3.2 交叉引用分析

从软件逆向工程的角度来看,被不同的上层函数引用次数越多的底层函数通用性越好,封装程度越高,在设计协议封装和解析的软件模块中,则往往表现为在函数中处理了某一类通用性较强的帧结构,比如携带数据载荷的帧和心跳帧;而被不同的上层函数引用次数较少,甚至仅在某一处有过引用的函数,在协议封装和解析的软件模块中往往完成一些连接建立、通信对端认证等控制类的操作,这一类帧属于控制帧。

协议解析系统在交叉引用分析阶段的主要工作是调用IDC函数获取交叉引用信息并将信息归类存储在数据结构func_info中,某协议的函数依赖关系如图4所示。通过对获得的引用数据进行统计来推断函数类型,并将推断结果作为协议帧分类阶段的输入。结构func_info的内容如表2所示。

函数类型推断使用的分界值与协议的复杂度有关,一般来说,与控制帧相关的函数与底层函数间的距离短,被调用的次数少,调用底层函数的次数多。经过实验对比,具体的分界点取值为距底层函数的距离为2、被调用次数为1、调用底层函数次数为3~5时,系统具有较高准确度,系统在此处预留配置接口,用户可以根据协议复杂度指定分界点取值。另外,通过IDA Pro自带的WinGraph32应用程序系统可以获得较为直观的函数依赖图形,依赖图直接作为模块的一项输出结果呈现给用户,用户可以根据自己的判断为系统指定重点分析的模块或校正系统的推测结果,提高解析精准度。

3.3 协议帧重构

协议帧重构阶段以前两个阶段中获得的函数依赖关系和对函数类型的推定为参考,判断函数代码特征,对目标协议中存在的帧进行重构与分类。算法中涉及到的函数代码特征主要包括距底层函数的距离是否为1、是否存在较多的幻数赋值操作、是否定长等。一个典型的控制帧组帧操作如图5所示,可以看到明显存在较多的幻数赋值操作。

帧重构的算法流程如下:

(1)将帧集合A初始化为空集,将函数集合S中标记F为STAY和UNKNOW的函数按照上一阶段获取的func_info结构的route_len域值升序排列,存储到顺序表D中。

(2)从顺序表D中取出一个函数fn(N,F),如果fn(N,F)的函数距为1,检查底层函数调用参数,并在本函数的代码中检索缓冲区长度参数的数据引用,如果缓冲区长度为定值,且该值与集合A中所有定长帧的帧长都不相同,则创建一个定长帧,帧长度为缓冲区长度,并将该帧加入到集合A中。

(3)如果步骤(2)中无法确定缓冲区长度为定值,则创建一个不定长帧,将该帧加入到集合A中。

(4)针对步骤(2)或步骤(3)中新加的帧,从底层函数调用地址处向上检索代码,直至函数头或另一次底层函数调用,对所有的幻数赋值操作,直接将字段长度和幻数值记录到新加的帧的对应结构中;对于变量赋值操作,记录字段长度后字段值暂时以符号代替。统计幻数赋值数量H和变量赋值数量B,如果H>2B,则将此帧标记为控制帧,否则标记为数据帧。

(5)如果fn(N,F)的函数距不为1,检索函数代码,记录所有的对污点内存区块的幻数赋值操作,检索完毕后访问其func_pollut结构,通过污染源所在的函数编号在集合A中检索对应的帧结构,并以幻数值替代帧结构中相应位置的符号。

(6)重复执行步骤(2),直到顺序表D中的所有函数均被访问。

算法执行完毕后,集合A中的帧即是经过初步构造后的协议帧,此时的帧结构记录了初步字段结构和常量字段值。

3.4 语义提取

协议的语义信息主要包括分隔符、关键字、校验域、长度域、指示域等,通过检索处理协议帧的字段的二进制代码是否存在相关的特征,可以提取到协议帧的语义信息[7]。例如:校验域通常伴随着大量的移位运算;关键字和分隔符会涉及到常量赋值操作,它们的主要区别在于关键字通常为一般数据,分隔符的值通常可以映射成ASCII表中的的特定字符;长度域也会涉及到常量赋值操作,但对于同一类帧结构来说,不同的调用点常量值一般不同。

对于一些无法提取比较显著的特征的代码段,系统选择的策略是直接提取相关的二进制代码,转储到文件后在数据结构中记录该二进制代码段与对应的协议字段的关联关系。

3.5 格式化处理

以上过程处理得到的协议格式有可能存在冗余,同一种变长帧由于个别字段值不同而被记录为两种或多种帧,另外,帧之间的逻辑关系信息也不完整。在格式化处理模块,系统通过比对集合中帧的已知语义来实现去冗余,不定长帧之间虽然长度和某些字段值不完全相同,但只要通过语义对比发现已知语义重复率超过阀值,就判定两种帧应是同一种帧,对二者执行合并操作。

最后,系统通过对集合中的帧进行格式化处理,将协议信息以一种标准的格式输出到结果文件。模糊测试模块通过读取文件获取协议的帧结构和语义等信息,并针对协议结构构造测试用例。

4

实验与分析

协议解析系统的主体部分、语义提取和格式化处理通过C语言实现,数据预处理和交叉引用分析通过IDC脚本实现,协议帧重构通过C语言调用IDA Pro SDK实现。

在实验中,将独立实现的系统应用于分析组态王软件提供的某私有协议的驱动程序,成功获取了部分协议格式,利用得到的协议格式可以实现与设备的简单互通,证明了方法的正确性和有效性。经解析,该协议在连接建立时考虑了协议版本的识别,实验分析得到的协议帧如图6所示。

另外,在对某DCS系统进行渗透测试的项目中,本协议解析系统作为协议健壮性测试平台的一个模块,为模糊测试模块提供了参考。模糊测试模块以协议解析系统的输出作为输入,根据解析结果,针对此DCS系统的应用层协议生成了80组测试用例,在测试过程中多次造成了目标系统的通信异常,证明了该方法的实用性。模糊测试结果示例如图7所示,图中的曲线为系统心跳帧携带的正弦数据流,测试开始后可明显观测到心跳帧发生中断和错序。

5

结束语

本文介绍了一种通信协议逆向解析方法,该方法结合静态二进制代码分析工具,将动态污点算法思想应用于静态二进制分析过程,实现了对工控协议的逆向解析并为模糊测试提供了参考,实验结果证明了方法的有效性。该方法具有对被测系统影响小、针对性较强的特点,适用于工业控制环境,具有较强的实用价值。

(0)

相关推荐