译者:ljcnaix@看雪论坛
与 Modbus 相比,EtherNet/IP 是一个更现代化的标准协议。由工作组 ControlNet International 与 ODVA 在 20 世纪 90 年代合作设计。EtherNet/IP 是基于通用工业协议(Common Industrial Protocol,CIP)的。CIP 是一种由 ODVA 支持的开放工业协议,它被使用在诸如 DeviceNet 和 ControlNet 以及 EtherNet/IP 等串行通信协议中。美国的工控设备制造商 Rockwell/Allen-Bradley 已经围绕 EtherNet/IP 进行了标准化,其他厂商如 Omron 也在其设备上支持了 EtherNet/IP。EtherNet/IP 已经变得越来越受欢迎,特别是在美国。尽管 EtherNet/IP 比 Modbus 更现代化,但仍然存在协议层面的安全问题。EtherNet/IP 通常通过 TCP/UDP 端口 44818 运行。此外,EtherNet/IP 还有另一个端口 TCP/UDP 端口 2222。使用这个端口的原因是 EtherNet/IP 实现了隐式和显示两种消息传递方式。显式消息被称为客户端/服务器消息,而隐式消息通常被称为 I/O 消息。
EtherNet/IP 是为了在以太网中使用 CIP 协议而进行的封装。EtherNet/IP 的 CIP 帧封装了命令、数据点和消息等信息。CIP 帧包括 CIP 设备配置文件层、应用层、表示层和会话层四层。数据包的其余部分是 EtherNet/IP 帧,CIP 帧通过它们在以太网上传输。EtherNet/IP 分组结构如图 5-12 所示。
CIP 规范对数据包结构有很多的规定,这意味着每个使用 EtherNet/IP 的设备必须实现符合规范的命令。下面是 EtherNet/IP 首部中封装的 CIP 帧字段:
Command
两字节整数,对应一个 CIP 命令。CPI 标准要求,设备必须能接收无法识别的命令字段,并处理这种异常。
Length
两字节整数,代表数据包中数据部分的长度。对于没有数据部分的请求报文,该字段为0。
Session Handle
会话句柄(session handle)由目标设备生成,并返回给会话的发起者。该句柄将用于后续与目标设备的通信。
Status
Status 字段存储了目标设备执行命令返回的状态码。状态码 “0” 代表命令执行成功。所有的请求报文中,状态码被置为 “0”。其它的状态码还包括:
0x0001 无效或不受支持的命令
0x0002 目标设备资源不足,无法处理命令
0x0003 数据格式不正确或数据不正确
0x0065 接收到无效的数据长度
Sender Context
命令的发送者生成这六字节值,接收方将原封不动的返回该值。
Options
该值必须始终为 0,如果不为零,数据包将被丢弃。
Command-specific data 该字段根据接收/发送的命令进行修改。
如果请求发送方是工程师站,大多数会话中执行的第一条命令是 “List Identity”
命令。如下所示的数据包,命令字段是 0x63
,代表 “List Identity”
命令,上下文是 “0x00006a0ebe64”
。这个命令与 Modbus 功能码 43 非常相似,可以查询设备信息,如供应商、产品、序列号、产品代码、设备类型和版本号等。使用在 Github 项目 pyenip 中找到的 Python 脚本 ethernetip.py,你可以查询 Ethernrt/IP 设备的信息。默认情况下,这个脚本不会解析一些响应,你需要取消脚本底部的 testENIP()
函数的注释后,它才会发送和接收 “List Identity”
命令。在执行脚本的同时,你可以使用 Wireshark 查看请求和响应的数据包。
我们在这个例子中没有提供脚本代码,因为它大约有 1000 行代码。你可以通过访问这里来获取脚本。
Digital Bond 在项目 Redpoint 中实现了一个和 pyenip 很像的脚本,可以用来从远程设备中获取信息。Redpoint 脚本使用了上一节提到的 “List Identity”
命令字,并使用 NES 脚本来解析请求。这个脚本有一个有意思的地方,它的 “Conmmand Specific Data”
部分包含了一个套接字地址(ip 地址和端口号)。这是暴露的远程设备的真实 ip 地址和端口号,即使它位于 NAT 设备之后。
通过 Shodan 搜索( https://www.shodan.io/search?query=port%3A44818 ),我们发现大量的设备暴露的 IP 字段和实际扫描的 IP 地址不同。所以我们得出结论,大多数的 Ethernet/IP 设备部署在内部网络中,而不是直接暴露在互联网上。如下图 5-15所示的是使用 nmap 扫描 CompactLogix 控制系统的扫描结果,可以看到暴露的设备 ip 和扫描 ip 不匹配,说明目标系统位于路由器或防火墙之后。
上图显示了一些信息,包括设备的制造商 “Rockwell”。设备的制造商在响应中是一个两字节的制造商 ID,它映射了一组支持 Ethernet/IP 的厂商名单。但是,这个厂商名单不是公开的。我们在深入研究 Wireshark 捕获的数据包后,发现数据包被 Wireshark 解析后,制造商 ID 被替换成了制造商名称。这说明 Wireshark 拥有如何映射制造商ID和名称的信息。通过对 Github 上 Wireshark 源代码的一些搜索,我们发现了如下代码片段,它告诉我们该如何解析制造商 ID。在解析工控协议的时候,Wireshark 常常是一个强大而好用的资源。
使用像 “List Identity” 这样的命令,你可以简单的重放数据包,几乎不用修改数据包。会话句柄将被设置为 0,意味着没有会话生成,因为该命令只是简单的发送命令和接收系统响应。为了进一步与设备进行通信,需要发送注册会话命令(0x65)。这个命令会设置会话句柄 ID,这个 ID 将用于后续会话的通信。如下图 516所示,注册会话的请求使用标准ID “0x00000000”
,目标设备返回了它生成的会话句柄 “0x03A566BB”
。
Ethernet/IP 具有和大多数工控协议相似的问题。资讯和培训公司 Kenexis 发布了针对 Ethernet/IP 的中间人攻击示例演示。这些示例可以在它们的 Github 项目主页上找到( https://github.com/kenexis/PortableICS-MITM )。与 Modbus 不同,简单的数据包重放对 Ethernet/IP 的某些指令无效。这使得攻击变得稍微复杂了一些。然而,对于大多数攻击者而言,只要对 Ethernet/IP 的协议稍有了解,这点困难将是微不足道的。一旦会话句柄通过协商被确定,只要通过手动改变序列号,就可以实现像之前 Modbus-vcr 工具那样的中间人攻击。
就像 Modicon 利用功能码 90 来终止 CPU,一些 Ethernet/IP 设备也支持类似的命令字。Digital Bind 的 Basecamp 项目中,发布了一个 Metasploit模块,可以被用来终止一个 Allen-Bradley ControlLogix 控制系统中的大量 PLC,以及其它的一些坏坏的事情,比如使以太网卡崩溃。
Digital Bond 的 Ruben Santamarta 在撰写 Basecamp 项目的 Writeup“Attacking ControlLogix”时写道“我们发送的每个数据包必须包含会话句柄。这就是全部,然后我们 Hack 了控制器。在协议层面没有更多的安全机制了。” [译者注:reversemode.com 上的文档我下载不下来,有能够下载的朋友求分享]。Ruben 指出,只要了解 Session Handle 即可轻松攻击 Ethernet/IP。是这个攻击奏效的另一个关键是 Allen-Bradley 实现的一个命令字。Allen-Bradley 在 NOP(0x00)命令中实现了终止 CPU 的功能。
这个命令在 CPI 或 Ethernet/IP 的规范中没有记录,是 Allen-Bradley/Rockwell 控制器的私有实现。通过对大量设备的测试,我们发现,在一些旧的固件中,不仅 ControlLogix CPU 被终止,而且设备崩溃,需要重新启动硬盘。对于当前的型号,PLC 必须拔下并重新插入才能再次运行。极少数情况下,PLC 需要重新编程。
我们还是坚持一贯的建议,如果你想测试你的 Ethernet/ip 设备,请只对非生产设备执行这些测试,并确保你已经被授予对设备执行 exploit 的许可,因为在设备上执行这些测试的后果是不可测的。