Уязвимость аппаратного кошелька Coldcard привела к краже более чем 88 млн bitcoin

iconMetaEra
Поделиться
AI summary iconСводка
Уязвимость в аппаратном кошельке Coldcard, связанная с неправильно настроенным ГСЧ, привела к краже более 594 bitcoin, что эквивалентно более чем 88 миллионам долларов США. Проблема затронула модели Coldcard Mk2, Mk3, Mk4, Q и Mk5, открыв частные ключи для атак методом перебора. Beosin отследил совокупные средства на несколько адресов Bitcoin. Инцидент вызывает опасения в рамках CFT и подчеркивает срочность соблюдения MiCA по мере усиления регулирования криптовалют в ЕС.

Потери превысили 88 миллионов долларов США: анализ уязвимости аппаратного кошелька Coldcard и отслеживание похищенных средств 31 июля около 500 аппаратных кошельков Coldcard были взломаны, и было похищено 594 биткоина на сумму около 38 миллионов долларов США. Затем компания Coinkite, ответственная за производство кошельков Coldcard, подтвердила наличие уязвимости в процессе генерации ключей, затронувшей несколько поколений продуктов: Coldcard Mk2, Mk3, Mk4, Q и Mk5.

На данный момент ущерб, причиненный атакой на эту уязвимость, превысил 88 миллионов долларов США, и атака продолжается. Пользователям Coldcard следует как можно скорее перевести свои средства на другие адреса. Ниже приведен анализ Beosin данной уязвимости и информация о отслеживании похищенных средств.

I. Анализ уязвимости

Анализ записей коммитов кода прошивки Coldcard показывает, что в предыдущем коммите 37e4af5451c260c1e7d429fe8972c4cb5e68ee59 команда разработчиков обновила несколько файлов, связанных с конфигурацией MK4, включая mpconfigboard.h:

// У нас есть собственная версия этого кода. #define MICROPY_HW_ENABLE_RNG (0)

На стороне MicroPython STM32 этот макрос управляет компиляцией пути по умолчанию для аппаратного генератора случайных чисел (RNG) и универсальной реализации случайных чисел. Установка его в значение 0 приводит к тому, что путь по умолчанию для аппаратного RNG не будет использоваться в качестве бэкенда для универсальной функции rng_get().

Его примечание указывает, что разработчик реализует RNG самостоятельно; при проверке пользовательского файла rng.h обнаружено, что объявлены только два объекта MicroPython:

MP_DECLARE_CONST_FUN_OBJ_0(pyb_rng_get_obj);MP_DECLARE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj);

Соответствующая реализация:

/// \function pyb_rng_get()///// Возвращает 30-битовое аппаратно сгенерированное случайное число: или ошибка!//STATIC mp_obj_t pyb_rng_get(void){    // Получить и вернуть новое случайное число    return mp_obj_new_int(rng_get_or_fault() >> 2);}
/// \function rng_get_bytes()/// Заполняет буфер случайными битами; вызывающая сторона должна предоставить буфер нужного размера.STATIC mp_obj_t pyb_rng_get_bytes(mp_obj_t buffer_io) {
    mp_buffer_info_t bufinfo;    mp_get_buffer_raise(buffer_io, &bufinfo, MP_BUFFER_WRITE);
    mp_uint_t count = bufinfo.len;    if(count < 1) {        mp_raise_ValueError(NULL);    }
    // Чтение 32-битных слов и распаковка в предоставленный буфер    random_buffer(bufinfo.buf, count);
    return mp_const_none;}
MP_DEFINE_CONST_FUN_OBJ_0(pyb_rng_get_obj, pyb_rng_get);MP_DEFINE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj, pyb_rng_get_bytes);

rng_get_or_fault() сама считывает периферию STM32 RNG:

static uint32_t rng_get_or_fault(void){    // Включить периферию RNG, если она еще не включена    rng_init();
    // Ждать готовности нового случайного числа, занимает примерно 10 мкс    uint32_t start = HAL_GetTick();
    while (!(RNG->SR & RNG_SR_DRDY)) {        if (HAL_GetTick() - start >= RNG_TIMEOUT_MS) {            // сбой оборудования... ничего не возвращать!            mp_raise_OSError(MP_EFAULT);        }    }
    // Получить и вернуть новое случайное число    last_value = RNG->DR;
    return last_value;}

Это означает, что пользовательский код Coldcard действительно стремится использовать аппаратный RNG, но гарантирует использование этой аппаратной функции только при вызове pyb_rng_get* или его внутренней random_buffer().

При создании кошелька фактически вызывается следующий код, где функция создания кошелька в shared/seed.py:

асинхронная функция make_new_wallet(nwords):    # Выбрать новый случайный семя.    await ux_dramatic_pause('Генерация...', 3)    seed = generate_seed()    words = await approve_word_list(seed, nwords)    если words:        await commit_new_words(words)

Этот вызов сначала попадает в модуль shared/random.py Coldcard. В предыдущих версиях random.py явно зависел от ngu.random и сохранял:

# random.py — подмножество модуля random, без совместимости, с использованием криптографически стойкого ГСЧ  
# для байтов используйте ngu.random.byte(len)  
# bytes = ngu.random.bytes

То есть для инициализации кошелька используется random.bytes, а не pyb.rng(), и пользовательская функция pyb_rng_get_obj не переопределяет random.bytes автоматически.

Поскольку MICROPY_HW_ENABLE_RNG установлен в значение 0, при генерации кошелька не использовался аппаратный RNG, а была вызвана функция pyb_rng_yasmarang из micropython/ports/stm32/rng.c:

#if MICROPY_HW_ENABLE_RNG
uint32_t rng_get(void) {    // используем аппаратный генератор случайных чисел STM32    ...}
#else
// Для МК без RNG нам всё ещё необходимо предоставить функцию rng_get()// Псевдослучайный генератор не идеален, но пока мы используем его.
// Генератор случайных чисел Yasmarangstatic uint32_t pyb_rng_yasmarang(void) {    static bool seeded = false;    static uint32_t pad = 0, n = 0;    ...}
uint32_t rng_get(void) {    return pyb_rng_yasmarang();}
#endif

pyb_rng_yasmarang является генератором псевдослучайных чисел, и его использование для генерации семян аппаратного кошелька крайне небезопасно: злоумышленник может получить ключ методом перебора. В настоящее время Coinkite явно исключил stm32/rng.c из файла makefile:

# Не компилировать резервный ГСЧ MicroPython. Файл rng.c, специфичный для платы,  
# предоставляет rng_get(), и этот пустой объект удовлетворяет списку объектов из основной ветки.  
$(BUILD)/rng.o: CFLAGS += -Dpyb_rng_yasmarang=error-do-not-want-this  
$(BUILD)/rng.o:  
$(ECHO) "SKIP stm32/rng.c"  
$(Q)$(CC) $(CFLAGS) -x c -c /dev/null -o $@

Два. Отслеживание украденных средств

В настоящее время средства нескольких жертвенных кошельков были переведены и собраны на нескольких адресах, но еще не подверглись дальнейшей очистке. Beosin Trace с помощью угрозо-ориентированной информации и анализа поведения в цепочке выявил следующие адреса сбора:

bc1qq85v2c926eg6pgxhwp6q7lf6cnsz80qs3fcu9r (562 BTC)

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_369595_usmImxZpHsKmu9X__1785899827?w=1080&h=656

bc1qx76cae2706qd5q576feh7xq8rfcsjpf2htfhe3 (398,47 BTC)

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_304781_-IeWjFtIQ4RhvCbz_1785899827?w=1080&h=674

Кроме того, существуют следующие адреса сбора, потоки средств которых аналогичны предыдущему графику, и на данный момент дальнейших переводов не зафиксировано:

  • bc1q8jy96fe5lf8vfugydnte3cguk92gpev7kwtp3q (89,62 BTC)
  • bc1q0rvn88w08j75k4h48lf9fvhan7unjp7vjf5q6m (64,9 BTC)
  • bc1qtfrwa4j6rmj9rsgspv6a0yjumkg39js2numu75 (45,9 BTC)
  • bc1qmd5m5ktv7m5ffujxv4248fxv36myvdx79n8jp6 (30,18 BTC)

Атаки на кошелек Coldcard продолжаются, команда Beosin продолжает мониторить дополнительные адреса сбора и анализировать соответствующие потоки средств.

Три. Заключение

Эта серьезная инцидент с безопасностью аппаратного кошелька Coldcard возникла из-за ошибки в реализации генерации случайных чисел командой разработчиков: для создания ключевого семени кошелька использовался псевдослучайный генератор. Команда разработчиков должна проводить непрерывное и всестороннее тестирование и аудит кода, а пользователям Coldcard следует как можно скорее перевести свои активы и внимательно следить за дальнейшими безопасностными объявлениями от Coinkite.

Beosin — ведущая компания в области безопасности блокчейна и регуляторного соответствия, специализирующаяся на аудите смарт-контрактов до запуска проекта, мониторинге и блокировке рисков безопасности во время работы проекта, возврате украденных средств, противодействии отмыванию денег (AML) на виртуальных активах и расследованиях. Beosin предоставляет «все-in-one» решения для соблюдения нормативных требований и безопасности блокчейна более чем 200 сервисам виртуальных активов, 4500 проектам Web3 и регуляторным и правоохранительным органам более чем в 20 странах и регионах мира.

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