使用工具模拟 Modbus 协议并抓包分析一些基础的功能码。

Environment

  • Kali Rolling (2019.4) x64
  • ModbusPal.jar
  • mbtget
  • Metasploit Framework

Preparation

启动 ModbusPal.jar,添加至少一个 Modbus Slave 作为模拟的 PLC:

为新建的 Modbus Slave 分别添加 coils(比特)和 holding registers(16 位无符号整型),并设置一些值:

下载并安装 mbtget 工具:

$ git clone https://github.com/sourceperl/mbtget.git & cd mbtget
$ perl Makefile.PL
$ make
$ make install

Operate with Function Code

使用 mbtget -h 查看命令行参数,其中只需要关注用于读写数据的几个参数:

root@kali:~# mbtget -h
usage : mbtget [-hvdsf] [-2c]
               [-u unit_id] [-a address] [-n number_value]
               [-r[12347]] [-w5 bit_value] [-w6 word_value]
               [-p port] [-t timeout] serveur

command line :
  -h                    : show this help message
  -v                    : show version
  -d                    : set dump mode (show tx/rx frame in hex)
  -s                    : set script mode (csv on stdout)
  -r1                   : read bit(s) (function 1)
  -r2                   : read bit(s) (function 2)
  -r3                   : read word(s) (function 3)
  -r4                   : read word(s) (function 4)
  -w5 bit_value         : write a bit (function 5)
  -w6 word_value        : write a word (function 6)
  -f                    : set floating point value
  -2c                   : set "two's complement" mode for register read
  -hex                  : show value in hex (default is decimal)
  -u unit_id            : set the modbus "unit id"
  -p port_number        : set TCP port (default 502)
  -a modbus_address     : set modbus address (default 0)
  -n value_number       : number of values to read
  -t timeout            : set timeout seconds (default is 5s)

首先对 coils 进行读取,-a 表示地址,-n 表示读取的数量:

root@kali:~# mbtget -r1 -a 0 -n 10 127.0.0.1
values:
  1 (ad 00000):     1
  2 (ad 00001):     1
  3 (ad 00002):     1
  4 (ad 00003):     1
  5 (ad 00004):     0
  6 (ad 00005):     0
  7 (ad 00006):     0
  8 (ad 00007):     0
  9 (ad 00008):     0
 10 (ad 00009):     0

在 Wireshark 中抓包并筛选,前后分别为 TCP 三次握手和四次挥手。中间两条 Modbus 协议即为我们进行的操作,首先是发出进行读取的请求,Function Code 是 Read Coils,Bit Count 为 10,即读取 10 个比特:

Response 中返回的数据即为前 10 个比特的值:

接下来对 holding registers 进行读取:

root@kali:~# mbtget -r3 -a 0 -n 10 127.0.0.1
values:
  1 (ad 00000):     0
  2 (ad 00001):     0
  3 (ad 00002):     0
  4 (ad 00003):     1
  5 (ad 00004):     2
  6 (ad 00005):     3
  7 (ad 00006):     4
  8 (ad 00007):     5
  9 (ad 00008):     0
 10 (ad 00009):     0

和前面类似,Function Code 是 Read Holding Registers,Word Count 为 10,即读取 10 个字:

Response 中返回前十个 holding registers 中的内容:

然后对写 coil 操作进行设置:

root@kali:~# mbtget -w5 1 -a 2 127.0.0.1
bit write ok

抓包看到的 Function Code 为 Write Single Coil,Reference Number 为 2(即第 3 个位置),Data 为 0:

Response 包中返回修改后对应位置的值:

写 holding register 的操作类似:

root@kali:~# mbtget -w6 233 -a 5 127.0.0.1
word write ok

抓包得到 Function Code 为 Write Single Register,Reference Number 为 5,Data 为 0xe9(233):

Response 返回修改后的结果:

使用 MSF 同样可以进行类似的操作,如读寄存器内容:

msf5 > use auxiliary/scanner/scada/modbusclient
msf5 auxiliary(scanner/scada/modbusclient) > set ACTION READ_HOLDING_REGISTERS
ACTION => READ_HOLDING_REGISTERS
msf5 auxiliary(scanner/scada/modbusclient) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf5 auxiliary(scanner/scada/modbusclient) > set DATA_ADDRESS 5
DATA_ADDRESS => 5
msf5 auxiliary(scanner/scada/modbusclient) > run
[*] Running module against 127.0.0.1

[*] 127.0.0.1:502 - Sending READ HOLDING REGISTERS...
[+] 127.0.0.1:502 - 1 register values from address 5 :
[+] 127.0.0.1:502 - [3]
[*] Auxiliary module execution completed

写寄存器内容:

msf5 > use auxiliary/scanner/scada/modbusclient
msf5 auxiliary(scanner/scada/modbusclient) > set ACTION WRITE_REGISTER
ACTION => WRITE_REGISTER
msf5 auxiliary(scanner/scada/modbusclient) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf5 auxiliary(scanner/scada/modbusclient) > set DATA 123
DATA => 123
msf5 auxiliary(scanner/scada/modbusclient) > set DATA_ADDRESS 6
DATA_ADDRESS => 6
msf5 auxiliary(scanner/scada/modbusclient) > run
[*] Running module against 127.0.0.1

[*] 127.0.0.1:502 - Sending WRITE REGISTER...
[+] 127.0.0.1:502 - Value 123 successfully written at registry address 6
[*] Auxiliary module execution completed

抓包的结果和 mbtget 相同。

Addition

MSF 中还有一些模块可以用来执行 PLC 命令:

msf5 > use auxiliary/scanner/scada/modicon_command
...
msf5 > use auxiliary/admin/scada/modicon_stux_transfer_ASO

使用 nmap 探测 PLC 开启的端口:

nmap -n -T4 -p- -scan-delay=1 [TARGET]

使用 snmpcheck 可以获取指定 PLC 的相关指纹信息:

snmpcheck -t [TARGET]

Joke

工控安全很难做好是因为原本设备的使用期限就很长(十年甚至二十年以上),本身维护的开销又很大,一个漏洞的修复很难落实到每一点上。然而真正要做到安全,则需要从管理、设备、操作人员等多个方面协作才能实现。

References

https://www.youtube.com/watch?v=iGwm6-lyn2Y
https://www.youtube.com/watch?v=rP_Jys1_OJk


ics

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

施耐德140NOE77101固件逆向分析
工控安全基础概念初探