Полезные статьи

Kraken выявила критические уязвимости в холодных кошельках Trezor

Kraken Security Labs разработала способ извлечения seed из обоих криптовалютных аппаратных кошельков, предлагаемых лидером отрасли Trezor: Trezor One и Trezor Model T.

Атака требует всего 15 минут физического доступа к устройству. Впервые были раскрыты подробные шаги для атаки на эти устройства.

Вот как мы это сделали:

  • Эта атака основывается на сбое напряжения для извлечения зашифрованного seed. Это первоначальное исследование потребовало некоторых ноу-хау и нескольких сотен долларов для необходимого оборудования, но мы предполагаем, что мы (или преступники) могли бы массово произвести удобный для потребителя девайс, который можно было бы продать примерно за 75 долларов.
  • Затем мы взламываем зашифрованный seed, который защищен 1-9-значным PIN-кодом, но является тривиальным для брут форса.

 

При атаке используются внутренние недостатки микроконтроллера, используемого в кошельках Trezor. К сожалению, это значит, что команде Trezor трудно что-либо сделать с этой уязвимостью без перепроектирования аппаратного обеспечения.

А до тех пор, вот что вы можете сделать, чтобы защитить себя:

— Не открывайте никому физический доступ к вашему кошельку Trezor.

— Вы можете навсегда потерять свою крипту.

— Подключите вашу BIP39 парольную фразу к клиенту Trezor.

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

Эта атака очень похожа на наше предыдущее исследование против кошелька KeepKey, потому что KeepKey является производной и все устройства полагаются на одно и то же семейство чипов. Trezor знает об этих недостатках с момента разработки кошельков.

Другие команды, такие как Ledger Donjon, также выполняли варианты этой атаки, хотя все подробности до сих пор не обнародованы.

Эти чипы не предназначены для хранения секретов, и наше исследование подчеркивает, что поставщики вроде Trezor и KeepKey, не должны полагаться только на них для обеспечения безопасности вашей криптовалюты.

Нам повезло, что мы сотрудничаем с командой Trezor для координации раскрытия этой уязвимости, и вы также должны ознакомиться с их реакцией на это. Павол Руснак, технический директор SatoshiLabs, добавляет: «Мы рады, что Kraken Security Labs инвестирует свои ресурсы в улучшение безопасности всей экосистемы Bitcoin. Мы дорожим таким ответственным сотрудничеством и раскрытием информации».

В Kraken Security Labs мы пытаемся обнаружить атаки на крипто-сообщество до того, как это сделают плохие парни. Мы ответственно раскрыли все подробности этой атаки команде Trezor 30 октября 2019 года. Сейчас мы раскрываем эту уязвимость, чтобы крипто-сообщество могло защитить себя до того, как команда Trezor исправит эту уязвимость.

Технические детали

Извлечение seed из кошельков Trezor происходит не впервые. Trezor принял значительные меры по уменьшению последствий предыдущих атак на оборудование, включая успешные меры по предотвращению сбоев , продемонстрированные во время выступления на 35-м Конгрессе (Chaos Communication Congress) в рамках конференции Wallet.Fail. Эта атака основывается на результатах этих исследований, направленных на то, чтобы смягчить последствия от атак.

Наша атака начинается с повторного включения встроенного загрузчика процессора с использованием атаки с внедрением ошибки. Этот встроенный системный загрузчик может считывать флеш содержимое устройства, но при выполнении команды проверяет уровень защиты чипа. С помощью второй атаки можно обойти эту проверку, и тогда весь флэш-контент устройства может быть извлечен по 256 байт за раз. Повторением атаки можно извлечь весь флеш-контент. Поскольку прошивка Trezor использует зашифрованное хранилище, мы разработали скрипт для взлома PIN-кода дампинга устройства, что привело к полному нарушению безопасности кошельков Trezor. Скрипт смог взломать любой 4-значный PIN-код менее чем за 2 минуты. Данная атака демонстрирует, что STM32-семейство микроконтроллеров Cortex-M3/Cortex-M4 не должно использоваться для хранения конфиденциальных данных, таких как криптографические seed-ы, даже если они хранятся в зашифрованном виде.

STM32F205 и STM32F427 — это флэш-микроконтроллеры, используемые в Trezor One и Trezor T. Многие производные Trezor One, такие как Keepkey, также используют STM32F205. И STM32F2, и STM32F4 являются микроконтроллерами ARM Cortex-M3 семейства STM32 компании ST Microelectronics. STM32F2 и STM32F4 предоставляют все периферийные устройства, необходимые для реализации аппаратного кошелька, включая PLL, а также интерфейсы, такие как USB. STM32F205 предлагает 2 общих интерфейса программирования ARM: JTAG и ARM SWD. В дополнение к этим интерфейсам программирования, STM32F205 также предлагает встроенный загрузчик, который может быть использован для программирования устройства с помощью таких интерфейсов, как UART, USB и CAN.

Защита от чтения Flash и SRAM в STM32

В семействе STM32 реализован механизм безопасности, известный как Read Protection или RDP. Поскольку единственным энергонезависимым хранилищем на устройствах ARM Cortex-M является флэш-память, значение RDP хранится в специальной странице флэш-памяти, которая в противном случае недоступна для записи из кода приложения. Значение RDP определяется битами конфигурации микроконтроллера, известными как Option Bytes. На устройствах STM32 есть 3 значения Option Bytes, соответствующих трем уровням RDP.

Kraken выявила критические уязвимости в холодных кошельках Trezor

Таблица 1: Уровни RDP и соответствующие значения Option Bytes на устройствах семейства STM32.

  • 0 уровень — Полный доступ к Flash и SRAM
  • 1 уровень — Доступ к чтению SRAM, но не Flash
  • 2 уровень — Запрет к доступу Flash и SRAM

Поскольку единственной энергонезависимой памятью на микроконтроллерах STM32 является флэш-память, это также единственная энергонезависимая память для криптографических seed и приватных ключей. Поэтому флэш-память должна быть защищена от считывания. К счастью, Trezor и все его производные правильно используют функцию RDP и при первой загрузке поставляются с RDP и/или устанавливают RDP на уровень 2 (см. таблицу 1). На практике не разрабатываемые прошивки на пользовательских устройствах всегда находятся на RDP2 (RDP Level 2), что не позволяет злоумышленнику получить доступ к SRAM или Flash. Но, как показывают исследования Wallet.Fail и Chip.Fail, понижение уровня RDP2 до RDP1 может быть выполнено при загрузке с перебоями напряжения. Как только устройство находится в RDP1, его SRAM может быть считан по протоколу отладки ARM SWD.

Из-за сложной логики Power-On-Reset (POR) в STM32 нормальное подтверждение сброса, т.е. софт сброс, при котором линия NRST удерживается на низком уровне в короткий промежуток времени, не приводит к полному включению-выключению и перезапуску BootROM. Это также несколько подтверждается тем фактом, что изменение конфигурации безопасности, то есть изменение Option Bytes для изменения уровня RDP, обычно требует циклического выключения питания чипа. И наоборот, это также значит, что после успешного сбоя чипа и соответствующего понижения уровня безопасности, это понижение уровня безопасности будет оставаться в силе до тех пор, пока чип не будет подвергнут циклическому включению. Это означает, что злоумышленник может многократно выполнить сбой устройства, проверять, был ли сбой успешным, при этом выполняя Bootrom или очень рано вставлять код приложения без загрузки кода приложения. В результате нет никаких контрмер, которые были бы эффективны против такого класса атак, так как злоумышленник может гарантировать, что сбой был успешным до того, как код приложения был загружен. Как только атакующий успешно выполнит сбой устройства, атакующий просто выполняет софт сброс, система продолжает работать в режиме RDP1, позволяя атакующему произвольно считывать содержимое SRAM-памяти в любой время. Это особенно проблематично, так как многие библиотеки, реализующие криптографию, необходимую для подписания криптовалютных транзакций, полагаются на загрузку конфиденциальной информации в SRAM для вычислений. Также, криптографический код может быть загружен в SRAM во время вывода кошелька, а PIN пользователя может быть загружен для сверки с пользовательским вводом. Если проверяется базовая прошивка или вычисляется контрольная сумма для проверки целостности прошивки, части или все эти данные могут также подвергаться атакам [O’Flynn Circuit Cellar].

Процесс загрузки STM32 и параметры сбоев

Большая часть поведения микроконтроллера определяется значениями, которые он считывает при включении. К ним относятся обвязанные контакты, которые считываются при загрузке (контакты BOOT в документации STM32) и биты конфигурации безопасности (Option Bytes в документации STM32). Обратите внимание, что многие из следующих деталей были определены с помощью эмпирического реверс-инжиниринга поведения загрузки STM32F2. Большинство микроконтроллеров Cortex-M содержат ROM-ы, которые выполняются при загрузке, обычно называемые BootROM. BootROM — это первые части программного обеспечения, выполняемые чипом, которые отвечают за загрузку важных параметров, таких как конфигурация безопасности чипа. Затем выполняется пользовательское приложение или код приложения. В случае аппаратного кошелька — это собственно прошивка производителя. Заметим, что поскольку описанная в этой работе атака-сбой нацелена на код BootROM, ее нельзя надежно смягчить какими-либо контрмерами, реализованными в прошивке производителя. Уязвимость в микропрограммном обеспечении приводит к неотъемлемым уязвимостям аппаратного обеспечения, которые не могут быть исправлены, и требует полной замены основного аппаратного обеспечения новой ревизией аппаратного обеспечения.

Из-за относительной сложности STM32F2, загрузка STM32 занимает очень много времени, приблизительно 1.2 — 1.8 мс после подачи питания на чип. Время загрузки может быть надежно измерено двумя способами: либо измерением потребляемой мощности прибора и измерением времени, необходимого для начального увеличения потребляемой мощности, например, с помощью LSCM, либо наблюдением за поведением линии сброса (NRST/JTAG RST) микроконтроллера. В пределах первых 100us — 200us выполняется BootROM чипа.

Повторное включение JTAG, SWD и встроенного загрузчика BootROM

Во время запуска STM32 перед выполнением кода приложения (прошивки кошелька) выполняется встроенный BootROM. BootROM реализует несколько проверок, которые, в свою очередь, конфигурируют состояние безопасности устройства. К ним относятся включение/выключение интерфейсов отладки JTAG и SWD, а также встроенного последовательного загрузчика BootROM. Поскольку семейство STM32 основано на флэш-памяти, единственной энергонезависимой памятью (NVM), доступной в этих устройствах, является флэш-память [Obermaier]. Поскольку флэш-память является единственным доступным NVM на STM32, конфигурация безопасности также должна храниться во флэш-памяти.

Существует специальная область флэш-памяти для хранения конфигурации безопасности и устройства, известная как Option Bytes (OB). Самое важное для общей безопасности устройства, OB содержит уровень защиты от чтения (RDP уровень), который, по сути, и является конфигурацией безопасности устройства. Уровни RDP варьируются от уровня 2, на котором отключены все интерфейсы отладки и системный загрузчик, до уровня 1, на котором включены ограниченные функциональные возможности системного загрузчика и ограниченные возможности отладки, и до уровня 0, на котором обеспечивается полный доступ. По умолчанию Trezor и его производные настроены на уровень RDP 2, самый сильный уровень безопасности, предлагаемый STM32.

Во время выполнения BootROM проверяется значение RDP. Если во время проверки RDP значение не является RDP2, то BootROM проверяет загрузку двух выделенных I/O пинов в соответствии с предварительно определенным паттерном загрузки, то есть логическими уровнями выводов BOOT0 и BOOT1. Если паттерн загрузки не выполняется, регулярное выполнение продолжается и код приложения выполняется с флэш-памяти. Однако, если паттерн выполнен, т.е. в случае STM32F2, если BOOT0 высокий, а BOOT1 низкий, то включается встроенный последовательный загрузчик BootROM и загрузчики DFU. Поскольку STM32 поддерживает несколько последовательных протоколов, один или несколько последовательных загрузчиков инициализируются и отключаются при соблюдении условий синхронизации на одном из последовательных интерфейсов. После синхронизации загрузчик входит в режим, в котором он получает и выполняет команды.

Kraken выявила критические уязвимости в холодных кошельках Trezor

Примечание: Таблица 2: Паттерны активации загрузчика, найденные в datasheet STM32F205.

STM32 BootROM Загрузчик

Загрузчик STM32 BootROM поддерживает различные команды, в том числе команды, способные читать и записывать флэш.

Kraken выявила критические уязвимости в холодных кошельках Trezor

Примечание: Таблица 3: Список поддерживаемых команд загрузчика, из документа «Протокол USART, используемый в загрузчике STM32».

Перевод команд:

  • Get(2) — Получение версии и разрешенных команд, поддерживаемыех текущей версией загрузчика
  • Get Version & Read Protection Ststus(2) — Получение версии загрузчика  и статуса защиты от чтения флеш-памяти
  • Get ID(2) — Получение ID чипа
  • Read Memory(3) — Считывание до 256 байт памяти, начиная с адреса, указанного в приложении
  • Go(3) — Переход к пользовательскому коду приложения, расположенному во внутренней флеш-памяти или в SRAM
  • Write Memory(3) — Запись до 256 байт в RAM или флеш-память,начиная с адреса, указанного в приложении
  • Erase(3)(4) — Удаление одной или всех страниц флеш-памяти
  • Extended Erase(3)(4) — Удаление одной или всех страниц флеш-памяти, используя two byte adressing mode (доступен для версии загрузчика 3.0 USART и выше)
  • Write Protect — Включение защиты от записи для некоторых секторов
  • Write Unprotect — Отключение режима записи для всех секторов флеш-памяти
  • Readout Protect — Включение защиты от чтения
  • Readout Unprotect — Отключение защиты от чтения

Команда «Read Memory» может быть использована для чтения до 256 байт с флэш-памяти устройства. Анализ BootROM показал, что данная команда каждый раз при вызове команды производит проверку уровня RDP, и позволяет считывать содержимое флеш-памяти только в том случае, если уровень RDP установлен на 0-й уровень.

Обход проверки RDP для команды чтения памяти

В таких кошельках, как Trezor One, во время производства STM32 устанавливается на уровень RDP 2. Это отключает все функции отладки и отключает встроенный загрузчик BootROM. При скачках напряжения возможно повреждение значения RDP, считываемого из Option Bytes, как показано в исследовании Wallet.Fail. Это эффективно позволяет злоумышленнику понизить конфигурацию безопасности девайса с уровня RDP 2 до уровня RDP 1. Понижение значения RDP с уровня RDP 1 до уровня RDP 0 было признано на практике невозможным в связи с расстоянием хэмминга между RDP 0 и RDP 2. Выполнив сбой напряжения во время выполнения BootROM, можно повторно включить интерфейсы отладки JTAG и SWD. Было определено, что встроенный загрузчик BootROM может быть подключен аналогичным образом.

На STM32F205 при сбое примерно на 170us во время выполнения BootROM, возможно повторное включение JTAG и SWD. Система эффективно включит JTAG и SWD интерфейсы с тем же самым поведением (с теми же ограничениями по доступу), что и RDP уровень 1. Было определено, что сбой напряжения на входе BootROM на 180us приведет к повторному включению встроенного загрузчика BootROM с тем же самым поведением, что и на первом уровне RDP. После установления связи со встроенным системным загрузчиком BootROM, то есть завершения синхронизации, можно выдавать команды, доступные в системном загрузчике BootROM на уровне RDP 1, например, GET ID. Однако, команды, недоступные на уровне RDP 1, приведут к тому, что STM32 вернет NACK и завершится ошибкой.

Было определено, что для определенных команд, то есть Read Memory, обработчик команд системного загрузчика BootROM проверяет, является ли уровень RDP устройства уровнем 0 для каждой выданной ему команды. Сбой напряжения, приуроченный к совпадению с проверкой уровня RDP обработчика команд, в то время как обработка команд, которые отключены для уровней RDP, отличных от уровня 0 RDP, может привести к обходу проверки уровня RDP и, в результате, к выполнению команды. В зависимости от конфигурации RDP устройства (в обход обработчика команд, который должен был вернуть NACK), возможен сбой команд. В результате можно выполнять команды, которые недоступны на уровне RDP 1 или RDP 2. При применении к команде Read можно произвольно считывать флэш-память с микроконтроллера. Так как криптографические seed-ы многих кошельков на базе STM32 хранятся во флэш-памяти STM32, seed-ы этих устройств могут быть скомпрометированы.

Аппаратная настройка сбоя в напряжении для дампа флэш-памяти

Плата Digilent Arty A7 FPGA была использована для создания сбоя и импульсов, а также для инструментов STM32 и точной синхронизации сбоя. Секционная плата на основе FTDI FT232H, Adafruit FT232H, использовалась для последовательной связи UART с обработчиком команд загрузчика BootROM. Мультиплексор Maxim MAX4619 использовался для мультиплексирования между номинальным рабочим напряжением для напряжения ядра процессора STM32 и напряжением сбоя, то есть GND или 0 В. Для упрощения взаимодействия использовалась плата BreakingBitcoin, которая представляет собой совместимую с выводами плату BreakingBitcoin Trezor. Такая же атака может быть выполнена на месте в аппаратном кошельке. Однако удалить микроконтроллер и поместить его в гнездо было проще, чем спаять все соединения (в основном Boot0 и Boot1) для атаки на месте.

Kraken выявила критические уязвимости в холодных кошельках Trezor

Примечание: Рисунок 1: Настройка сбоя. Слева вверху аналоговый мультиплексор MAX4619. 64-контактное гнездо LQFP64 для STM32F205. Справа красный адаптер FT232H USB-UART для подключения к загрузчику BootROM UART. Справа вверху — плата разработки Digilent Arty A7 FPGA.

Брутфорс PIN-кода для Flash-дампа

Trezor шифрует свое конфиденциальное хранилище с помощью ключа, производного от PIN-кода и salt. Salt включает в себя аппаратный salt, хранящейся в байтах OTP, и рандомный salt от флэш-памяти, генерируемый при подготовке устройства. Аппаратный salt  имеет длину 44 байта и может считываться одним чтением option bytes.

Trezor организует хранение в «записи», которые могут быть идентифицированы по значению приложения и значению ключа. Запись, содержащая salt, зашифрованный ключ шифрования данных (EDEK), зашифрованный ключ аутентификации хранилища (ESAK) и проверочный PIN-код (PVC) можно найти под значением app-value 0 и key-value 2. Найти эту запись можно, так как она всегда начинается с шестнадцатеричных байтов «02 00 3C 00». Полная запись имеет длину 64 байта, поэтому в сочетании с аппаратным salt считыванием достаточно двух успешных считываний (так как на одно считывание можно сбрасывать 256 байт).

После извлечения salt, EDEK, ESAK и PVC, необходимо извлечь дополнительную аппаратный salt из байтов OTP. Это можно сделать путем повторного включения интерфейса SWD или повторного включения загрузчика Bootrom и выдачи соответствующих Read Memory Commands.

На одноядерном процессоре, использующем библиотеку Python «pycryptodome», производительность составила около 85 хэшей в секунду. Это можно значительно оптимизировать с помощью GPU. Даже на такой относительно медленной скорости 4-значный пин может быть сбручен менее чем за 2 минуты.

Kraken выявила критические уязвимости в холодных кошельках Trezor

Пример брутфорс скрипта:

#!/usr/bin/env python3

import hashlib
import sys
import argparse
import binascii
import struct
from Crypto.Cipher import ChaCha20_Poly1305

parser = argparse.ArgumentParser(description=’Crack some wallets.’)
parser.add_argument(‘flash_dump’, help=’The flash dump to parse.’)
parser.add_argument(‘–otp’, help=’Randomness from OTP as hex.’, default=44*”00″)
parser.add_argument(‘–debug’, type=bool)

args = parser.parse_args()
flash_file = open(args.flash_dump, “rb”)

def find_header():
while True:
data = flash_file.read(4)
if data == b”\x02\x00\x3c\x00″:
return
elif data == None:
print(“Couldn’t find header.”)
sys.exit(1)

find_header()

# Salt from flash
salt = flash_file.read(4)

# EDEK + ESAK
edek = flash_file.read(48)
pvc = flash_file.read(8)

# Salt computation:
# hardware_salt (from collect_hw_entropy)
# random_salt (Random buffer generated, stored in flash)
# ext_salt – unused
hardware_salt = hashlib.sha256(binascii.unhexlify(args.otp)).digest()

salt_assembled = hardware_salt + salt
print(f”Hardware salt: {binascii.hexlify(hardware_salt)}”)
print(f”Assembled salt: {binascii.hexlify(salt_assembled)}”)

def trezor_pbkdf(pin, salt):
# PIN is always prefixed with 1
pin_bytes = struct.pack(“<I”, int(“1″ + pin))
if args.debug:
print(f”PIN bytes: {binascii.hexlify(pin_bytes)}”)
dk = hashlib.pbkdf2_hmac(‘sha256’, pin_bytes, salt, 10000, dklen=352/8)
return dk

def chacha_enc(kek, keiv, data):
cipher = ChaCha20_Poly1305.new(key=kek, nonce=keiv)
# Return DEK & TAG
return cipher.encrypt_and_digest(data)

def chacha_dec(kek, keiv, edek):
cipher = ChaCha20_Poly1305.new(key=kek, nonce=keiv)
return cipher.decrypt(edek)

for i in range(1, 9999):
if i % 100 == 0:
print(f”Currently trying: {i}”)
dk = trezor_pbkdf(str(i), salt_assembled)

# First 256 bits are Key Encryption Key (KEK)
KEK = dk[:int(256/8)] # Remaining 96 bits are Key Encryption Initialization Vector (KEIV)
KEIV = dk[int(256/8):]

if args.debug:
print(f”KEK: {binascii.hexlify(KEK)}”)
print(f”KEIV: {binascii.hexlify(KEIV)}”)

DEK = chacha_dec(KEK, KEIV, edek)
# Encrypt to get the TAG, unfortunately seems to be the only
# way to retrieve it from Pycryptodome.
enc, TAG = chacha_enc(KEK, KEIV, DEK)
if args.debug:
print(f”DEK: {binascii.hexlify(DEK)}”)
print(f”TAG : {binascii.hexlify(TAG)}”)
print(f”PVC : {binascii.hexlify(pvc)}”)

if pvc in TAG:
print(“SUCCESS”)
print(“PIN is:”)
print(i)
sys.exit(0)

Ключевое слово

Salt, состоящий из аппаратного salt, к которой добавлен ​​salt из записи, используется в комбинации с PIN (с префиксом 1 и преобразуется в 32-байтовое целое число с прямым порядком байтов) для получения ключа с помощью PBKDF2-HMAC с SHA256 и производным ключом длиной 362 бита. Первые 256 бит результата используются в качестве ключа шифрования ключей (KEK), а последние 96 бит — в качестве вектора инициализации ключа шифрования (KEIV).

Верификация ключа

Ключ шифрования данных (DEK) генерируется из EDEK путем расшифровки EDEK с помощью KEK и KEIV. Используется алгоритм шифрования ChaCha20 с кодом аутентификации сообщения Poly1305. Если первые 64 бита PVC идентичны первым 64 битам TAG, то PIN правильный и DEK успешно восстановлен.

Похожие статьи

Кнопка «Наверх»
Закрыть