[工控安全][原創]西門子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 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM