ICSFuzz: Manipulating I/Os and Repurposing Binary Code to Enable Instrumented Fuzzing in ICS Control Applications阅读笔记

由于第四次工业革命,过去的10年里,工业控制系统(ICS)急速增长,但是对应在可编程逻辑芯片(PLC)的网络安全防护却有所缺失。PLC应用也从未被考虑进有传统信息安全威胁的应用中。本文研究了利用PLC漏洞,测试了IEC 61131-3所有编程语言的不同并且引入漏洞。基于这个研究,提出了一个fuzz框架测试程序函数的安全性。为了验证框架的准确性,使用一个内部二进制程序的数据库,这些程序从线上收集。最终通过发现漏洞的能力展示了工具的准确性。

本文主要有以下贡献

  • 分析了基于所有可用的IEC 61131-3语言的控制应用的组成,强调了由不同语言和编译工具引入独特的特性的复杂性

  • 开发了一个针对PLC的fuzz框架,可以发现二进制程序中导致crash或漏洞利用的点

  • 扩展了fuzz框架,使它包含PLC应用宿主软件的系统函数

  • 开发并整合了PLC二进制文件,供未来的ICS安全研究人员使用

  • 通过发现合成和常规漏洞来展示工具的有用性,并展示破坏主机系统和工业过程的利用方法

问题定义和威胁模型

场景定义:一个工业场景,PLC是一个功能,从传感器接收输入,通过执行器确保操作的正确性,也是一个调试控制逻辑应用的主机。工程师(HMI端)通过网络连接和Codesys控制设备,传感器和执行器也是通过网络连接的,直接和设备连接。

威胁模型:攻击者可以给PLC传递一些数据,例如:

  • 工程师和PLC之间的中间人攻击,Codesys的HMI端能够实时指出二进制文件中任何变量的值

  • 传感器和PLC之间的中间人攻击,这种攻击已经出现在很多公开的ICS相关研究

  • 能够隐蔽操控数据的固件木马

  • 恶意雇员,这种场景下一个恶意的内部人员通过不受监督的HMI终端篡改PLC二进制文件的值

Codesys运行时

Codesys是一个跨平台的满足IEC 61131-3的开发环境。通过专家交流和Shodan搜索,发现至少20%的PLC使用Codesys。

Codesys运行时平台是所有相关函数的后端控制器以及负责系统和用户交互。

8.png)

图1是基于Codesys程序的系统栈

Codesys自身在/usr/bin中,通过一个作为系统初始化一部分的进程部署,进程位于/etc/init.d中。初始化过程中,进程初始化一系列通信相关函数,例如网络。所有的函数以线程形式实例化,是主进程的子线程,通过clone()启动。KBUS函数用于进程间的信息交互,控制GPIO过来的数据。一个定时任务也实例化了,主要用于应用的控制。控制进程的优先级,跟踪互斥量的使用。

表1列出了Codesys的活跃线程。

控制应用在Codesys初始化过程启动,通过一个文件打开系统调用打开硬编码的文件夹,然后把控制代码数据拷贝进内存。复制完成后就执行。以一系列的用户函数控制。控制代码压进一个新实例化的线程栈中,强制所有栈有可执行权限。这个动作允许了任意代码执行。随着控制进程执行,Codesys也会初始化两个工具类,KBUS_CYCLE是KBUS和应用的中介,VISU提供了控制进程基于嵌入其中代码的可视化。

控制应用二进制程序

虽然控制二进制程序和传统二进制格式有一些类似特性,但终究还是有一些不同。控制程序,像传统的二进制程序一样,包含头、程序主体,数据段、链接库。主要的不同就是无法独立运行,这也是本文的最大挑战。[28]通过逆向工程提供了一种Codesys编译的程序视图。

文件以一个包含能够使其运行的关键信息,最重要的字段是程序入口,栈大小和动态链接库信息。然后就是设置常量,以及允许IDE调试的子过程。

控制二进制程序还包含对于函数(F)或函数块(FB)的调用,这些都是静态链接的并且以两个连续的子过程包含在文件中。第一个包含代表F/FB的功能,第二个初始化本地内存。PLC主函数PLC_PRG封装在下一个子过程中,里面包含控制逻辑。动态链接的功能通过符号表找到,符号表中包含两个字节,用于计算跳转偏移。

控制程序分析

PLC编程语言

International Electrotechnical Commission (IEC)发布了IEC 61131-3标准用于标准化PLC编程。这是一种非活跃编程语言,顺序功能图语言(SFC)由对其他编程语言的调用组成,因此不具有内部特征。

  • 梯形图(LD):这种语言重新编译了电路用于代替硬件连线延迟控制,由于LD控制功能性组件,代表大型实时系统并且维护可视化理解变难。LD也缺乏对于原生算术计算和数据结构的支持

  • 函数块图(FBD):FBD也是基于一个连接函数块的连线图,FBs也是PLC编程语言使用的编程结构,和传统编程语言的函数用法相同

  • 结构化文本(ST):这种编程语言最接近于高级计算机语言,基于Pascal,使用条件语句、循环以及指针、数组类似的结构

图2展示了三种编程语言的不同。

编程语言的比较

要理解不同编程语言的相似性,研究安全问题,就要比较各个编程语言的不同

使用diff工具查看不同编程语言产生的二进制文件的不同。最大的不同就是编译器会插入不同数量的NOP指令,大多数都没有使用保留的NOP指令,但是使用典型的冗余操作指令。NOP指令在嵌入式系统中很常见,用于引入时间延迟,比如内存读写时,避免引发各种问题。PLC是朝可靠性优化而不是性能。

不同语言的Codesys实验结果显示,不同语言产生相似的机器码,Codesys在产生机器码之前,会产生中间语言。因此剩下的篇幅中,我们会关注编译好的PLC程序的二进制代码,选择ST进行测试,因为它提供额外的数据和函数。

PLC应用的潜在脆弱函数

本小节通过比较C/C++的脆弱函数和PLC环境下对应的函数,来展示PLC是否内存安全。

标准的编程语言中,内存危险的根源是能够从程序中直接访问内存。PLC也允许通过指针内存管理,表2展示了本文分析的函数

本文分析包含SysStrCpy (SysLibStr library), Concat (Standard library), as well as SysMemCpy, SysMemMove, SysMemSet, and SysMemCmp, 和Codesys SysLibMem library的所有部分。

分析结果表示被测试函数的一部分保留了遗留的漏洞并且导致Crash。Crash的原因是基于变量的函数。例如SysMemCpy和SysMemMove不考虑源和目的缓冲区的大小,就会导致缓冲区溢出。

Fuzzing ICS

Fuzz控制程序

之前的章节分析了控制程序缺少二进制的标准化流程,这些因素引入了fuzz的复杂性

  • 执行不能直接被execve控制

  • 执行失败不会提供反馈

  • 输入不能通过传统的方式传送给控制程序

  • 输入分发很难同步,由于控制二进制程序的扫描周期运行格式

  • 由于编译工具的闭源,插桩不能以传统的方式进行

fuzzing的配置可以分为执行控制和输入控制两部分,前者控制执行以及和控制程序的交互、接收各种信号,后者改变输入,希望输入能够导致未知状态。

执行控制

控制程序分为两类:同步和异步。同步执行即控制程序定期扫描对应的内存空间,基于输入值给出输出,异步执行接收外部信号,只需要执行一次。

  • 对于同步执行,每一个时间周期进行输入分发,最大化利用所有时间周期

  • 对于异步执行,直接开始执行,发送数据然后等待结束信号

PLC情况下的终止执行没有别的信号,由调度线程管理。可以看到的就是一系列futex系统调用,这些系统调用让VISU和KBUS_CYCLE睡眠。控制线程的结束可以被直接祖先进程监测到。本文修改了Codesys的启动脚本来监测,通过fork和wait管理进程。

输入控制

输入可以是物理信号、模拟信号或数字信号,信号传递过程如下

  1. IO模块受到了传感器通过GPIO传输到PLC的信号

  2. GPIO接收并储存信号和数据

  3. KBUS打开GPIO设备并读取数据

  4. KBUS_CYCLE_TASK分发信号

图3就是IO模块到控制程序传递数据的过程

作者通过逆向工程和调试得到了GPIO的功能和数据控制过程。

输入通过间接方式传递给控制程序,即首先找到GPIO的内存映射,然后直接将数据写入对应的位置。通过一个简单的跟踪程序执行syscall。

插桩

使用插桩测试fuzz的回馈。使用angr执行程序的特定部分,插桩过程中是将一些无用的NOP替换为桩代码,将数据输出到stdout,如图4所示

Fuzzing运行环境

即对Codesys fuzzing

参考

[28]:Anastasis Keliris and Michail Maniatakos. ICSREF: A framework for automated reverse engineering of indus- trial control systems binaries. In NDSS, 2019.