Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
8cryptdo — Реверс-инжиниринг документации и инструментов для шифрования прошивки 8BitDo | Kitploit
Инструменты/GitHubGitHub/aeromodes/8cryptdo
Безопасность встроенных системИнструменты шифрования/дешифрованияБезопасность IoTОбратная инженерияКриптографияБезопасность оборудования и IoTАнализ Бинарных ФайловСтатьи и ИсследованияАнализ Прошивок
GitHubaeromodes/8cryptdo

8cryptdo

291 день назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Реверс-инжиниринг документации и инструментов для шифрования прошивки 8BitDo

Репозиторий

8CryptDo

Документация и инструменты, полученные путём обратной разработки, для шифрования прошивок, используемого 8BitDo в нескольких продуктах на базе GD32.

См. 8cryptdo.py — инструмент для расшифровки и повторного шифрования, реализующий алгоритм, описанный в этом документе.

Инструмент потенциально позволяет создавать кастомные прошивки. Однако этот репозиторий не содержит официальных данных прошивок. Скрипт для загрузки последних файлов прошивок можно найти в репозитории fwupd/8bitdo-firmware, где также заархивированы некоторые более старые бинарные файлы прошивок.

Заголовок

Если посмотреть на файл прошивки .dat снаружи, в нём присутствует 28-байтовый открытый заголовок.

root@kitploit:~
import struct

raw = open("sn30-v2_07.dat", "rb").read()
version, addr, payload_len = struct.unpack("<III", raw[:12])
# version     = 207        -> firmware == v2.07
# addr        = 0x08003400 -> destination (probably)
# payload_len = 99328      -> section payload length in bytes

В последних файлах прошивок после этих значений размещено неизвестное 32-битное значение. Остальная часть заголовка заполнена нулями.

Слой цепочки без ключа

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

root@kitploit:~
def rotr(x, r):
    return ((x >> r) | (x << (32 - r))) & 0xFFFFFFFF

dechained = [words[0]]
for i in range(1, len(words)):
    dechained.append(words[i] ^ rotr(words[i - 1], 3))

Эта цепочка сбрасывается на каждой границе блока из 128 слов. Первое слово блока (pos = 0) не имеет члена-предшественника, а pos = 1..127 образуют цепочку от предыдущего слова.

Слой без ключа не добавляет безопасности. Его снятие выявляет более чистую промежуточную форму (dechained), где открытый текст скрывает только поток ключа.

root@kitploit:~
P[i] = dechained[i] ^ keystream[i]

Разложение потока ключа на множители

В репозитории fwupd/8bitdo-firmware есть каталог старых прошивок. Во всех этих прошивках, по-видимому, присутствовал слой цепочки.

XOR нескольких из них друг с другом после удаления слоя цепочки выявляет длинные участки нулей и читаемую структуру.

root@kitploit:~
plaintext_diff = [a ^ b for a, b in zip(dechained_a, dechained_b)]

Если в обоих используется один и тот же поток ключа...

root@kitploit:~
a = dechained_a[i] ^ dechained_b[i]
b = (P_a[i] ^ keystream[i]) ^ (P_b[i] ^ keystream[i])
c = P_a[i] ^ P_b[i]
assert a == b == c

поток ключа сокращается.

Это классический многоразовый одноразовый блокнот. Такой поток ключа безопасен только при однократном использовании. По какой-то причине 8BitDo решила использовать не просто поток ключа для каждого продукта, а единый поток ключа для нескольких продуктов. Это значительно ослабило безопасность их шифра и сделало возможным остальной анализ, приведённый здесь.

Чтение потока ключа

Когда вы знаете, что два открытых текста объединены операцией XOR, вы можете угадать часть одного и вычесть свою догадку, чтобы прочитать другой. Это восстанавливает keystream в этом месте.

root@kitploit:~
keystream[i] = dechained[i] ^ P[i]
for fw in all_fw:
    P[fw][i] = dechained[fw][i] ^ keystream[i]

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

Использование этого увеличило количество известных слов потока ключа с ~1 500 до нескольких тысяч, что составляет примерно 18% читаемой прошивки.

Правило потока ключа

При большей выборке keystream стали видны закономерности. В нём были позиции, отстоящие на 16 слов, которые изменялись на почти постоянную величину, и каждый байт изменялся на предсказуемую величину.

Путём проб и ошибок было обнаружено, что каждое слово потока ключа в блоке подчиняется формуле:

root@kitploit:~
STEP  = 0x92A753FA
A_MUL = 0x80000301
ROT   = 18
UINT32_MAX  = 0xFFFFFFFF

def rotr(x, r):
    r &= 31
    return ((x >> r) | (x << (32 - r))) & UINT32_MAX if r else x

def block_base(block):
    return (block * A_MUL + block // 2) & UINT32_MAX

def keystream(block, pos, block_key):
    counter = (block_base(block) + pos * STEP) & UINT32_MAX
    mask = rotr(block_key, ROT * pos)
    return counter ^ mask

Внутри блока выполняется простой счётчик (block_base(block) + pos*STEP), и он объединяется операцией XOR с маской, которая начинается с block_key и поворачивается на 18 бит на каждом шаге. Весь блок из 128 слов определяется одним 32-битным числом, block_key.

Одно известное слово разблокирует целый блок. Преобразование формулы даёт block_key из любого известного слова потока ключа:

root@kitploit:~
def block_key_from_known(block, pos, known_keystream):
    counter = (block_base(block) + pos * STEP) & UINT32_MAX
    return rotr(known_keystream ^ counter, -ROT * pos & 31)

Таблица seed

Откуда взялся ключ каждого блока? Он раскладывается на две 16-битные половины, взятые из таблицы seed из 256 элементов:

root@kitploit:~
def block_key_of(block, seed_table):
    hi = seed_table[block]
    lo = seed_table[block ^ 0xF9]
    return (hi << 16) | lo

Сама таблица seed, по-видимому, не имеет конкретной формулы; вероятно, это 512 константных байтов, хранящихся где-то в загрузчике каждого устройства. Однако XOR с 0xF9 даёт прекрасный побочный эффект: закон зеркала. Блок и его партнёр block ^ 0xF9 строятся из одних и тех же двух элементов таблицы, но в обратном порядке.

root@kitploit:~
mirror = block_key_of(block ^ 0xF9, seed_table)
assert mirror == rotr(block_key_of(block, seed_table), 16)

Это позволило взломать ранее неизвестные области. Вместо слепого угадывания полного 32-битного block_key можно угадать две 16-битные половины и оценить, какой выбор превращает оба блока в правдоподобные строки или ARM-инструкции.

Благодаря этому было получено 100% покрытие всех данных прошивок, использующих этот конкретный шифр.

Оставшаяся работа

Этот шифр, по-видимому, охватывает большинство продуктов 8BitDo примерно 2020 года, а также текущие продукты, которые всё ещё используют SoC GD32 (включая SN30 Pro).

Некоторые устройства, такие как M30 и Zero 2, по-видимому, являются многосекционными: часть данных прошивки использует этот шифр, а другая прошивка использует иную схему шифрования.

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

Скачать инструмент