• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья По следам Industrial Ninja: как взламывали ПЛК на Positive Hack Days 9

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
На прошедшем PHDays 9 мы проводили соревнование по взлому завода по перекачке газа — конкурс Industrial Ninja. На площадке было три стенда с различными параметрами безопасности (No Security, Low Security, High Security), эмулирующих одинаковый индустриальный процесс: в воздушный шар закачивался (а потом спускался) воздух под давлением.

Несмотря на разные параметры безопасности, аппаратный состав стендов был одинаков: ПЛК Siemens Simatic серии S7-300; кнопка аварийного сдува и прибор измерения давления (подсоединены к цифровым входам ПЛК (DI)); клапаны, работающие на накачку и спуск воздуха (подсоединены к цифровым выходам ПЛК (DO)) — см. рисунок ниже.

2ufqommgoloxmjwohpy2sphrbha.png


ПЛК, в зависимости от показаний давления и в соответствии со своей программой, принимал решение о сдуве или надуве шарика (открывал и закрывал соответствующие клапаны). Однако на всех стендах был предусмотрен режим ручного управления, который давал возможность управлять состояниями клапанов без каких-либо ограничений.

Стенды отличались сложностью включения данного режима: на незащищенном стенде сделать это было проще всего, а на стенде High Security, соответственно, сложнее.

За два дня были решены пять из шести задач; участник, занявший первое место, заработал 233 балла (он потратил на подготовку к конкурсу неделю). Тройка призеров: I место — a1exdandy, II — Rubikoid, III — Ze.

Однако во время PHDays никто из участников не смог одолеть все три стенда, поэтому мы решили сделать онлайн-конкурс и в начале июня опубликовали самое сложное задание. Участники должны были за месяц выполнить задание, найти флаг, подробно и интересно описать решение.

Под катом мы публикуем разбор лучшего решения задания из присланных за месяц, его нашел Алексей Коврижных (a1exdandy) из компании Digital Security, который занял I место в конкурсе во время PHDays. Ниже мы приводим его текст с нашими комментариями.

Первоначальный анализ

Итак, в задании был архив с файлами:
  • block_upload_traffic.pcapng
  • DB100.bin
  • hints.txt
Файл hints.txt содержит необходимые сведения и подсказки для решения задания. Вот его содержимое:
Петрович мне вчера рассказал, что из PlcSim можно загрузить блоки в Step7.
На стенде использовался ПЛК Siemens Simatic серии S7-300.
PlcSim — это эмулятор ПЛК, позволяющий выполнять и отлаживать программы для ПЛК Siemens S7.
Файл DB100.bin, судя по всему, содержит блок данных DB100 ПЛК:
Код:
00000000: 0100 0102 6e02 0401 0206 0100 0101 0102  ....n...........
00000010: 1002 0501 0202 2002 0501 0206 0100 0102  ...... .........
00000020: 0102 7702 0401 0206 0100 0103 0102 0a02  ..w.............
00000030: 0501 0202 1602 0501 0206 0100 0104 0102  ................
00000040: 7502 0401 0206 0100 0105 0102 0a02 0501  u...............
00000050: 0202 1602 0501 0206 0100 0106 0102 3402  ..............4.
00000060: 0401 0206 0100 0107 0102 2602 0501 0202  ..........&.....
00000070: 4c02 0501 0206 0100 0108 0102 3302 0401  L...........3...
00000080: 0206 0100 0109 0102 0a02 0501 0202 1602  ................
00000090: 0501 0206 0100 010a 0102 3702 0401 0206  ..........7.....
000000a0: 0100 010b 0102 2202 0501 0202 4602 0501  ......".....F...
000000b0: 0206 0100 010c 0102 3302 0401 0206 0100  ........3.......
000000c0: 010d 0102 0a02 0501 0202 1602 0501 0206  ................
000000d0: 0100 010e 0102 6d02 0401 0206 0100 010f  ......m.........
000000e0: 0102 1102 0501 0202 2302 0501 0206 0100  ........#.......
000000f0: 0110 0102 3502 0401 0206 0100 0111 0102  ....5...........
00000100: 1202 0501 0202 2502 0501 0206 0100 0112  ......%.........
00000110: 0102 3302 0401 0206 0100 0113 0102 2602  ..3...........&.
00000120: 0501 0202 4c02 0501 0206 0100            ....L.......
Судя по названию, файл block_upload_traffic.pcapng содержит дамп трафика загрузки блоков на ПЛК.

Стоит отметить, что этот дамп трафика на площадке конкурса во время конференции получить было немного сложнее. Для этого необходимо было разобраться в скрипте из файла проекта для TeslaSCADA2. Из него можно было понять, где находится зашифрованный с помощью RC4 дамп и какой ключ необходимо использовать для его расшифровки. Дампы блоков данных на площадке можно было получить с помощью клиента протокола S7. Я для этого использовал демоклиент из пакета Snap7.

Извлечение блоков обработки сигнала из дампа трафика

Взглянув на содержимое дампа, можно понять, что в нем передаются блоки обработки сигнала OB1, FC1, FC2 и FC3:

sz43ydme-e17zhg3yuudd1xxmva.png


Необходимо извлечь эти блоки. Это можно сделать, например, следующим скриптом, предварительно сконвертировав трафик из формата pcapng в pcap:
Python:
#!/usr/bin/env python2

import struct
from scapy.all import *

packets = rdpcap('block_upload_traffic.pcap')
s7_hdr_struct = '>BBHHHHBB'
s7_hdr_sz = struct.calcsize(s7_hdr_struct)
tpkt_cotp_sz = 7
names = iter(['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin'])
buf = ''

for packet in packets:
    if packet.getlayer(IP).src == '10.0.102.11':
        tpkt_cotp_s7 = str(packet.getlayer(TCP).payload)
        if len(tpkt_cotp_s7) < tpkt_cotp_sz + s7_hdr_sz:
            continue
        s7 = tpkt_cotp_s7[tpkt_cotp_sz:]
        s7_hdr = s7[:s7_hdr_sz]
        param_sz = struct.unpack(s7_hdr_struct, s7_hdr)[4]
        s7_param = s7[12:12+param_sz]
        s7_data = s7[12+param_sz:]
        if s7_param in ('\x1e\x00', '\x1e\x01'):  # upload
            buf += s7_data[4:]
        elif s7_param == '\x1f':
            with open(next(names), 'wb') as f:
                f.write(buf)
            buf = ''

Изучив полученные блоки, можно заметить, что они всегда начинаются с байтов 70 70 (pp). Теперь нужно научиться их анализировать. Подсказка к заданию наводит на мысль, что для этого необходимо использовать PlcSim.

Получение человекочитаемых инструкций из блоков

Для начала попробуем запрограммировать S7-PlcSim, загрузив в него несколько блоков с повторяющимися инструкциями (= Q 0.0) с помощью ПО Simatic Manager, и сохраним полученный в эмуляторе PLC в файл example.plc. Посмотрев на содержимое файла, можно легко определить начало загруженных блоков по сигнатуре 70 70, которую мы обнаружили ранее. Перед блоками, судя по всему, записан размер блока в виде 4-байтового little-endian значения.

01vpra5veml-vbfozvhainxy-ig.png


После того как мы получили сведения о структуре plc-файлов, появился следующий план действий для чтения программ PLC S7:
  1. С помощью Simatic Manager создаем в S7-PlcSim структуру блоков, аналогичную той, что мы получили из дампа. Должны совпадать размеры блоков (достигается с помощью наполнения блоков нужным количеством инструкций) и их идентификаторы (OB1, FC1, FC2, FC3).
  2. Сохраняем PLC в файл.
  3. Заменяем содержимое блоков в полученном файле на блоки из дампа трафика. Начало блоков определяем по сигнатуре.
  4. Полученный файл загружаем в S7-PlcSim и смотрим содержимое блоков в Simatic Manager.
Замену блоков можно произвести, например, следующим кодом:
Код:
with open('original.plc', 'rb') as f:
    plc = f.read()
blocks = []
for fname in ['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin']:
    with open(fname, 'rb') as f:
        blocks.append(f.read())

i = plc.find(b'pp')
for block in blocks:
    plc = plc[:i] + block + plc[i+len(block):]
    i = plc.find(b'pp', i + 1)

with open('target.plc', 'wb') as f:
    f.write(plc)

Алексей пошел по, возможно, более сложному, но все равно правильному пути. Мы предполагали, что участники воспользуются программой NetToPlcSim, чтобы c PlcSim можно было общаться по сети, загрузят блоки в PlcSim через Snap7, а потом скачают эти блоки в виде проекта из PlcSim с помощью среды разработки.
Открыв полученный файл в S7-PlcSim, можно прочитать перезаписанные блоки с помощью Simatic Manager. Основные функции управления устройствами записаны в блоке FC1. Особое внимание привлекает переменная #TEMP0, при включении которой, судя по всему, управление ПЛК переводится в ручной режим на основе значений битовой памяти M2.2 и M2.3. Значение #TEMP0 устанавливается функцией FC3.

02tqptegvjst2hzny9d8sc4rmva.png


Для решения задания необходимо проанализировать функцию FC3 и понять, что нужно сделать, чтобы она вернула логическую единицу.

Блоки обработки сигналов ПЛК на стенде Low Security на площадке конкурса были устроены аналогичным образом, но для установки значения переменной #TEMP0 достаточно было написать строку my ninja way в блок DB1. Проверка значения в блоке была устроена понятно и не требовала глубоких знаний языка программирования блоков. Очевидно, что на уровне High Security добиться ручного управления будет значительно сложнее и необходимо разбираться в тонкостях языка STL (один из способов программирования ПЛК S7).

Реверс блока FC3


Код довольно объемный и человеку, незнакомому с STL, может показаться сложным. Разбирать каждую инструкцию в рамках данной статьи нет смысла, подробно с инструкциями и возможностями языка STL можно ознакомиться в соответствующем мануале: Statement List (STL) for S7-300 and S7-400 Programming. Здесь я приведу тот же самый код после обработки — переименования меток и переменных и добавления комментариев, описывающих алгоритм работы и некоторые конструкции языка STL. Сразу отмечу, что в рассматриваемом блоке реализована виртуальная машина, исполняющая некоторый байт-код, находящийся в блоке DB100, содержимое которого нам известно. Инструкции виртуальной машины представляют собой 1 байт операционного кода и байты аргументов, по одному байту на каждый аргумент. Все рассмотренные инструкции имеют по два аргумента, их значения в комментариях я обозначил как X и Y.


Получив представление об инструкциях виртуальной машины, напишем небольшой дизассемблер для разбора байт-кода в блоке DB100:
Код:
import string
alph = string.ascii_letters + string.digits

with open('DB100.bin', 'rb') as f:
    m = f.read()

pc = 0

while pc < len(m):
    op = m[pc]
    if op == 1:
        print('R{} = DB101[{}]'.format(m[pc + 2], m[pc + 1]))
        pc += 3
    elif op == 2:
        c = chr(m[pc + 1])
        c = c if c in alph else '?'
        print('R{} = {:02x} ({})'.format(m[pc + 2], m[pc + 1], c))
        pc += 3
    elif op == 4:
        print('R0 = 0; R{} = (R{} == R{})'.format(
            m[pc + 1], m[pc + 1], m[pc + 2]))
        pc += 3
    elif op == 5:
        print('R0 = 0; R{} = R{} - R{}'.format(
            m[pc + 1], m[pc + 1], m[pc + 2]))
        pc += 3
    elif op == 6:
        print('CHECK (R{} == R{})\n'.format(
            m[pc + 1], m[pc + 2]))
        pc += 3
    else:
        print('unk opcode {}'.format(op))
        break
В результате получим следующий код виртуальной машины:


Как видно, данная программа просто проверяет каждый символ из DB101 на равенство определенному значению. Итоговая строка для прохождения всех проверок: n0w u 4r3 7h3 m4573r. Если данную строку поместить в блок DB101, то активируется ручное управление ПЛК и можно будет взорвать или сдуть воздушный шар.


Вот и все! Алексей продемонстрировал высокий уровень знаний, достойный индустриального ниндзя :) Победителю мы отправили памятные призы. Большое спасибо всем участникам!


Автор: Positive Technologies,
Positive Hack Days 9
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх