大单GreatDane

独立信息安全研究员,专注于工业互联网安全领域。已于6月20日结束求职签约雇主。

导航

统计

[工控安全][原创]西门子PLC固件逆向之定位s7comm协议的一个切入口

[原创]西门子PLC固件逆向之定位s7comm协议的一个切入口

mailto:wangkai0351@gmail.com

【未经同意禁止转载】

鉴于本博客涉及的信息安全技术具有破坏计算机信息系统的风险,建议读者在学习/研究/探讨之前,确保已经充分了解以下内容:

本博客所讨论的技术仅限于研究和学习,旨在提高计算机信息系统的安全性,严禁用于不良动机,任何个人/团队/组织不得将其用于非法目的,否则后果自负,特此声明。

Choice,the problem is choice!

在西门子PLC CPU固件逆向工程的研究工作中[1],我常常找不到一个合适的切入点。

众所周知,一个CPU芯片私有、嵌入式操作系统私有、文件系统私有、应用层协议栈私有,甚至开发以上软件的编译器都是私有,并且没有符号表symbol table时,逆向工作是多么的困难。这和常见的物联网设备逆向[2]的难度是不可同日而语的。

那么,没有头绪的情况下,我并没有choice的空间,只有妄图依靠灰箱知识找到一个突破口。

我们知道s7comm的protocol id是0x32,所以使用“== 0x32”这个条件去全局搜索反编译结果,搜索结果如下

Line 112668:         if ((piParm1 == 0x32) && (piParm1[0x16] == 0x14)) {
Line 391457:     if (bVar1 == 0x32) {
Line 446366:   if (psParm1 == 0x32) {
Line 491055:   if ((((pcParm1 == 0x32) && (pcParm1[1] == 0x01)) && (pcParm1[10] == 0xf0)) &&
Line 492543:   if (pcVar4 == 0x32) {
Line 530415:     if (uParm2 == 0x32) goto LAB_00241bd0;

我很快定位到491055行这条语句,如下所示。

undefined4 s7comm_job_setup_communication_judge(char *pcParm1)
//当然,在没有符号表的情况下,这个函数名是我自己修改的
{
  undefined4 uVar1;
  
  if ((((*pcParm1 == 0x32) && (pcParm1[1] == 0x01)) && (pcParm1[10] == 0xf0)) &&
     ((*(short *)(pcParm1 + 0xc) != 0 && (*(short *)(pcParm1 + 0xe) != 0)))) {
    uVar1 = 1;
  }
  else {
    uVar1 = 0;
  }
  return uVar1;
}

看到这个非常冗长的判断条件自然令人兴奋,它和我们熟悉的s7comm协议中建立应用层连接的报文似乎存在千丝万缕的关系。

我们找到一个s7comm协议的数据包例子,对比s7comm_job_setup_communication_judge函数的判断条件和真实的setup communicaiton报文有什么关系?

我以网络上公开的一个s7comm数据包集合为例[3],下图展示了我要分析的一条setup communicaiton报文。

从上图中抽出s7comm应用层的字段描述和值,摆在下面。

//job_setup_communication报文应用层payload
0000   32 01 00 00 02 00 00 08 00 00 f0 00 00 01 00 01   2...............
0010   01 e0                                             ..

Header: (Job)
    Protocol Id: 0x32
    ROSCTR: Job (1)
    Redundancy Identification (Reserved): 0x0000
    Protocol Data Unit Reference: 512
    Parameter length: 8
    Data length: 0
Parameter: (Setup communication)
    Function: Setup communication (0xf0)
    Reserved: 0x00
    Max AmQ (parallel jobs with ack) calling: 1
    Max AmQ (parallel jobs with ack) called: 1
    PDU length: 480(字节?)

接着,我们细致分析固件中这条判断条件

  if ((((*pcParm1 == 0x32) && (pcParm1[1] == 0x01)) && (pcParm1[10] == 0xf0)) &&
     ((*(short *)(pcParm1 + 0xc) != 0 && (*(short *)(pcParm1 + 0xe) != 0)))) {

分开来写,

条件1)s7comm.header.protid == 0x32,s7comm

条件2)s7comm.header.rosctr == 0x01,Job

条件3)s7comm.param.func == 0xf0,Setup communication

条件4)s7comm.param.maxamq_calling != 0,Max AmQ calling

条件5)s7comm.param.maxamq_called != 0,Max AmQ called

可以看出,这五个判断条件和s7comm数据包实例中字段是可以一一对应上的,且符合我们对s7comm协议的灰盒知识。

由此,印证了我们之前的推论,s7comm_job_setup_communication_judge函数是判断PLC设备接收到的某条报文是不是符合条件的s7comm.header.rosctr为job&&s7comm.param.func为setup communication报文。

我们找到了s7comm_job_setup_communication_judge作为西门子PLC CPU设备固件逆向的一个突破口,围绕这个突破口,我们展开下一阶段的工作设想。

从s7comm_job_setup_communication_judge函数向前看

  1. 函数判断条件中那没有涉及到的字段有

    1)s7comm.header.redid

    2)s7comm.header.pduref

    3)s7comm.header.parlg

    4)s7comm.header.datlg

    5)s7comm.param.setup_reserved1

    6)s7comm.param.pdu_length

    为什么没有判断,job_setup_communication中各个字段有什么作用?

  2. s7comm_job_setup_communication_judge函数的输入极有可能是socket_recv的报文队列,它的地址是什么?

  3. socket_recv函数和这个队列的关系?

从s7comm_job_setup_communication_judge函数向后看

  1. 如果s7comm_job_setup_communication_judge函数输出等于1,下面PLC应该构造ack_data_setup_communication报文,构造函数是什么?
  2. 构造好的ack_data_setup_communication报文,如何通过socket_send函数发送出去?

以上问题,我们日后将一一尝试解答。

致谢

感谢众多安全研究员公开发表自己的研究成果,并热情地回复我的疑问,这些人包括但不限于Dillon Beresford/Ali Abbasi/Thomas Weber/Ralf Ramsauer/Gene blue。


  1. 我手上under test的西门子PLC CPU模块属于早期的S7-1200系列(十分抱歉,我不能公开该设备的订货号、固件版本等指纹信息),它既支持s7comm协议,也支持s7comm-plus协议;因为产品硬件版本和固件版本太低,极大概率不支持s7comm-plus-plus协议。我使用IDA Pro和Ghidra等工具反汇编/反编译了该设备的固件。 ↩︎

  2. 《揭秘家用路由器0day漏洞挖掘技术》 ↩︎

  3. https://github.com/gymgit/s7-pcaps/blob/master/snap7_s300_setupCommunication.pcapng ↩︎

posted on 2020-04-12 12:15  大单GreatDane  阅读(237)  评论(0编辑  收藏