本文主要是从工业控制网络必备的组件 PLC (可编程控制器)出发,阐明了一种新型后门的实现。本文主要内容是来自 BLACK HAT 2015 上柏林自由大学 scadacs 团队发表的演讲,这是他们的paper原文Internet-facing PLCs - A New Back Orifice。我会滤掉他们论文凑字数的部分,并在他们给出的核心思路上增加一些实现方面的具体技巧和资料。
不了解工控安全的哥们儿可以先去这篇文章上补一下基础知识 工控安全入门分析 。
下面几个部分都是一些背景和基础知识,如果已经对plc工控安全非常了解,可以直接跳到攻击描述(0x03)部分。
本文中,我们研究攻击者如何通过公网plc访问到深层工业网络。
我们采取的方法是将plc变成网关(本文采用西门子系列plc相关技术和特性),这种方法在缺乏适当权限认证手段的plc上是可行的。经验丰富的攻击者拥有某plc的访问权限时,可以往上面上传或者下载代码,只要代码是由MC7字节码组成,这是plc的原生代码形式。我们研究了运行时环境中的plc,并发现可以通过上传mc7代码来实现很多网络服务。特别是,我们实现了
并且他们的实现完全只依靠编译为MC7字节码的STL语言代码。我们的扫描器和代理可以部署在plc中,并且不会中断plc中原有程序的运行,这可以使运维很难意识到plc已被感染。为了说明和分析深层工业网络入侵,我们开发了一个概念性证明工具,PLCinject(附上github项目地址: SCADACS/PLCinject)。根据我们的概念性证明,xxxxxxxx(这段太tmd复杂,我实在没法准确翻译,主要意思就是讲运行在plc上的恶意软件会使其原有代码扩展增加,如果我们定时观测原有代码和感染恶意代码后的程序,在统计学上这两者的运行效果有明显的差异,然而其对生产过程的影响微乎其微,除非运营者主动监控从PLC中发出的恶意访问的流量,否则很难在生产过程中发现)。此外,攻击者可以利用我们的方法,通过工业控制网络来攻击企业的业务网络。这意味着网络管理必须警惕从业务网络正面和背面发起的双向攻击。
我会在文章最后补充一下针对施耐德plc的编码。

上图展现了典型的使用自动化系统的公司结构。工业控制系统由这么几层构成。在顶部是企业资源规划(ERP)系统,其保存着当前可用资源和生产能力的相关数据。制造执行系统(MES)能够管理多个工厂或平台,并且从ERP系统接受任务。在MES下的系统位于工厂内部,监督、控制和数据采集(SCADA)系统控制生产线。他们提供关于目前生产状态的数据,并且他们提供干预手段。存储着有关生产过程的逻辑的设备称为可编程逻辑控制器(PLC)。人机交互界面(HMI)显示当前的进度,并且允许运营者与生产过程相互作用。
本文将着眼于针对 PLC 的攻击。
PLC 原本仅仅是为自动化控制而开发,在其开发之初,其应用场景是极其封闭的,几乎不能与工业内网外的任何第三方设备有所接触,但是近几年互联网的迅猛发展,和物联网、智能硬件的出现,开始逐渐有工业 PLC 暴露在公网之中,大家可以去seebug和shodan上搜索schneider或者siemens等厂商型号来发现公网上的plc设备。尽管如此,目前PLC的安全性是十分十分差的。首先来说,plc的固件迭代更新缓慢,虽然厂商可能进行维护和更新,但是给工业控制网络中的正在运行的线上plc更新固件,代价是异常巨大的,一次关机可能就是整个工厂的停止运行。其次,目前的plc已经有了一些比较低级的访问控制手段,但是很少有人会主动开启,因为它会降低plc的运行效率和稳定性。 因此,一般来说,如果某个plc面向公网开放,我们可以向其加载任意代码。
除了在权限控制上的严重问题,攻击者有可能利用plc作为一个进入生产网络甚至公司内网的网关。在本文中,我们分析和讨论这一威胁载体,并且,我们将证明,这种利用方式是真实可行的。出于演示的目的,我们开发一个运行在plc上的端口扫描器和一个socks代理。这个扫描器和代理使用plc的原生编程语言Statement List(STL)编写。
硬件
PLC由一个 CPU (一般带有通讯模块,如工业以太网、modbus、profinet等等,和一些服务的接口,如ftp、web、telnet等等),和其外部附加的数字量和模拟量输入输出模块共同组成(有时外部还会附加专用的通讯模块)。本文使用西门子 S7-314C-2 PN/DP。
执行环境
这部分如果学过计算机组成原理会比较容易看懂,这部分主要讲plc的代码执行流程,跟后面我们的攻击方式的隐蔽性可行性和代理编写时的通信稳定性密切相关。
西门子PLC运行着实时操作系统,他初始化周期性时间监视。随后操作系统周期性执行四个步骤,如下图:

在第一步中,CPU复制过程镜像的输出值来输出模块的状态。第二步,CPU读取输入模块的状态,并且更新过程映像的输入值。第三步,用户程序在时间片中执行1毫秒的持续时间。每个时间片被分割成三个部分,依次执行:操作系统,用户程序和通信。时间片的个数取决于当前的用户程序。默认情况下,时间应该不长于150毫秒,工程师可以配置不同的值。如果规定的时间用尽,中断例程被调用,在通常情况下CPU返回到周期的开始状态,并重新开始循环时间监视。
我们使用 STEP 7 为plc进行编程,我使用的版本是v5.5。
参考这篇文章进行安装 step7 v5.5 cn 软件下载、仿真器安装、授权
注意,仿真器是需要额外安装的,在上文中有。
工程师可以使用梯形图(LAD),功能块图(FBD),结构化控制语言(SCL)和语句表(STL)来为PLC编程。与基于文本的SCL和类似汇编的STL,LAD和FBD语言是图形化的。PLC程序被分成组织块(OB),功能(FC),功能块(FB),数据块(DB),系统功能(SFC),系统功能块(SFB)和系统数据块(SDB)这几个单元。OB,FC和FB包含着实际的代码,而DB存储着数据结构,SDB存储PLC的当前配置。带有前缀M的内存地址被用于内部数据存储寻址。
一个PLC程序至少由一个组织块(称为OB 1)组成,这就相当于C程序中的main函数。它将由操作系统调用。存在更多的用于特定用途的组织块,比如,OB 100。这个块在PLC启动时被调用一次,并通常用于初始化系统。
关于各种编程语言的语法,再次不再赘述,请自行查阅相关资料。
这一部分尽管原文中针对通讯过程做了较为详细的阐述,但是我不会做太多解释,因为本文主要侧重恶意代码的编写。
但是还是要多说一句,这些嵌入式设备通常都是使用裁剪过的vxworks等系统,我个人认为,目前工业控制系统的渗透攻击和漏洞挖掘,在固件分析还没有特别深入的现状下,针对工控网络的通讯协议进行攻击是最为高效的手段。我大体翻了下wooyun上的工控漏洞,其实还是web渗透那一套,其实对于工业网络,有时候可用性甚至比保密性更加重要(比如伊朗核设施爆炸)。
西门子plc使用其自身的S7Comm协议来传输块。这是一种基于TCP/IP和基于TCP的ISO传输服务的远程过程调用(RPC)协议。包封装如下图:

协议提供了以下功能:
被传输的块被结构化,其由头部,数据部分和尾部构成。
详细的传输过程请查看原文。这里仅给出一些已知字节的结构:

尾部包含用于调用功能的参数信息。并不是所有头部和尾部的字节都被我们所知晓,但是我们已经确定了用来理解其内容的必要区域。
这部分主要是为了解释进行恶意代码注入的方式,其实完全可以使用step 7 进行代码注入,甚至可以说该协议的所有操作基本都可以使用 step 7 完成,但是如果我们弄懂了协议的结构内容和作用,就可以编写利用脚本,自动化攻击。
西门子plc提供了一个系统库,该库包含了可以建立任意TCP/UDP连接的功能。攻击者可以使用完整的TCP/UDP支持来扫描公网plc背后的本地生产网络。 其实根据我的了解,应该只有profinet上的通讯可以完成完整的TCP/UDP请求,这就要求我们使用的西门子plc的型号中,必须带有 PN,比如原文中使用的 S7-314C-2 PN/DP,除此之外还有s7-319-3 PN/DP等等。
我们首先下载plc的OB1块,之后添加一条CALL指令调用任意一个我们可控制的函数,在我们给出的样例中,该函数叫做FC 666。之后,patche后的OB1,也就是FC 666和其他块(这里可能包含很多的块,比如我们自己编写的FC块,背景数据块,共享数据块等等),将被上传到PLC。下图展示了代码注入过程:

在下个执行周期时,新上传的包含攻击代码的程序就会被执行,并且不会造成任何服务中断(这里我认为并不一定,在RUN模式下还是会有中断发生,在RUN-P模式下就会自动在下一个周期执行而不会发生中断)。这个过程使得攻击者能够在plc上运行任意恶意代码。我们随这篇文章发布了一款名叫PLCinject的工具,他可以自动化完成这个过程。有了这种技术,攻击者可以执行如下图所示的攻击流程:

在第一步中,攻击者注入一个SNMP扫描器,它会与plc上的正常代码一同运行。在完成一个针对本地网络的完整SNMP扫描(第二步)之后,攻击者可以从plc中下载扫描结果(第三步)。攻击者现在拥有了公网plc背后内网的一张缩略图。之后他溢出SNMP扫描器并注入一个socks代理(第四步)。这使得攻击者可以通过充当代理的plc到达本地生产网络的所有plc。在之后的两节中我们将阐明SNMP扫描器和SOCKS代理的实现。我们不会详细解释每个操作和系统调用的细节。对于这些的详细描述我们参考了S7-300 Instruction list S7-300 CPUs and ET 200 CPUs和Siemens. (2006) System Software for S7-300/400 System and Standard Functions Volume 1/2。
本文用到的几个系统调用为: SFC 51 "RDSYSST", FB 65
"TCON", FB 63 "TSEND" ,FB 64 "TRCV", FB 67 "TU
SEND", FB 68 "TURCV", UDT 65 "TCON_PAR".
请在上面给出的那篇Siemens. (2006) System Software for S7-300/400 System and Standard Functions Volume 1/2查找其参数和输入输出。
西门子plc不能作为一个TCP端口扫描器来使用,因为TCP连接函数TCON直到该函数成功建立连接之前,都无法被终止。此外,最多只能在西门子S7-300并发地运行8个TCP连接。因此,该PLC只能在不会发生8个连接同时失败的情况下作为TCP扫描器(如果8个连接同时失败,那么这8个连接函数就无法断开,那么扫描就无法继续进行了)。此限制并不适用于无状态的UDP连接。这就是我们要使用基于UDP的简单网络管理协议(SNMP)的原因。SNMP v1.0 在 RFC 1157[23]中被定义,并且它是为监视和控制网络设备而开发的。大量网络设备和大部分西门子 Simatic PLC 有默认支持SNMP。西门子plc在开启SNMP功能的情况下是非常活跃的。通过使用OID 1.3.6.1.2.1.1.1读取SNMP系统基本信息(sysDesc)对象,西门子plc将发送其产品类型,产品型号,硬件和固件版本,形如以下SNMP响应:
#!bash
Siemens, SIMATIC S7, CPU314C-2 PN/DP, 6ES7 314-6EH04-0AB0 , HW: 4, FW: V3.3.10.
该系统描述可以用来在漏洞和exp库中匹配已发现的plc。plc的固件不会经常打补丁。主要有两种原因:一方面,plc的固件升级将中断生产过程,这将造成亏损;另一方面,plc的固件补丁能够导致某些产品质量问题,这对客户来说是不能容忍的。这就是为什么找到一个拥有已知漏洞的西门子plc设备的可能性非常之高。该SNMP扫描器可以被分解为以下步骤:
plc编程与使用C语言在X86系统上正常编程完全不同,如果学过verilog这种针对硬件的编程语言的话就比较容易理解。每个PLC程序周期性执行,所以他需要在每步之后将程序状态存储到状态变量中。在此我们只解释SNMP扫描步骤1到3。 下图显示第一步的一段代码片段,调用RDSYSST函数。

RDSYSST函数读取内部系统状态表(SSL)来获得plc本地IP,SSL请求通常是用于诊断。
这里给出siemens官网上的一个实例:读取本地ip
14和15行将在RDSYSST功能繁忙的情况下停止该函数。 下图展示了程序如何计算本地网络的第一个ip地址和ip总量。

这是通过将plc的本地ip和子网掩码进行按位AND运算来完成的,这将返回本地网络的起始地址(24-30行)。现在SNMP扫描器需要知道子网的ip总量。因此,我们将子网掩码与0xFFFFFFFF进行XOR运算(35-39行)。结果即为子网的ip总量。 下图展示了如何使用STL语言建立一个UDP连接。首先我们需要调用TCON方法,并使用我们TCON_PAR_SCAN这个DB作为该函数的背景数据块。

在UDP协议的情况下,TCON函数不能建立连接,他只能在TCP协议下完成,因为与UDP相反,他的连接是定向的。 但是只调用一次TCON是不够的,当#connect变量在两次调用之间从0上升到1时,连接函数才开始工作。这就是为什么我们在连接函数首次出现之后编写了一个切换功能(10-11行)。这将在一个周期里,TCON首次被调用之后改变#connect的值,使之从False变为True。TCON函数在下一个周期被调用时,检测到上升沿信号,之后开始执行。下一步操作是,基于UDP协议的SNMP数据包,并且接收响应。这将通过调用TUSEND和TURCV函数来完成。之后,SNMP扫描完成,所有数据被存储到可以被攻击者下载的数据块中(step 3)。
给出一个实例udp协议交换
关于 TCON 这个函数,其他参数没什么问题,关键在于CONNECT这个参数,这个参数需要一个指针实参,指向以UDT 65为模版建立的DB块。关于UDT 65的细节可以在上文中的手册中找到,但是我们要手动建好这么个数据结构真是非常蛋疼,所以我找到了一个专门的用来建立TCON这个连接参数的工具 Open Communication Wizard_V2.3.3
一旦攻击者已经发现所有的SNMP设备,包括本地plc,下一步就是要连接到他们。这可以通过使用可访问的plc作为进入内网的网关来实现。为了做到这一点,我们选择在plc上实现一个socks5代理。这有两个主要原因,首先,SOCKS协议是轻量级的,非常容易实现。另外所有应用都可以使用此类型的代理,要么应用是原生支持socks协议的,要么可以使用所谓的proxifier来为任意程序添加对socks的支持。socks5协议在RFC 1928[24]中被定义。通过代理无差错地TCP连接到目标需要以下几步:
我们的实现提供了最小化的必要功能,他不支持身份验证,所以我们跳过了第三步。我们也不支持错误处理。并且,只支持连接IPV4地址。一旦客户端连接,我们期望该信息会经过以下几个步骤: