Нередко при создании кастомных прошивок для роутера возникает необходимость в подделке подписи, чтобы можно было грузить прошивку через стоковую веб‑морду. А для подделки подписи нам понадобится изучить процесс проверки образа в стоковой прошивке. Предлагаю окунуться в реверс прошивки на примере роутера D-Link DIR-806A B1. Давай разберемся, как в нем работает проверка подписи.
У этого роутера 8 Мбайт флеш‑памяти и 64 Мбайт — оперативной. Работает он на чипе MediaTek MT7620A. Этот камень основан на архитектуре MIPS и поддерживается ядром Linux. Загрузка стоковой прошивки происходит через U-Boot. Там есть встроенный TFTP-клиент для восстановления в случае, если залил неудачную прошивку. Главное — не убить бутлоадер, иначе придется паять.
На мой взгляд, отличная железка для экспериментов. Один из минусов — у DIR-806A в оригинальной комплектации нет USB. Однако трассировка USB присутствует на плате, так что при должном уровне знаний и сноровки порт можно распаять самостоятельно.
Мы же начнем с подключения к UART, так что не забудь запастись преобразователем уровней для UART. При подключении нужно не забыть «покрестить» RX и TX. То есть RX-линию подключаем к TX, а TX — к RX. Параметры соединения — 57600 8N1.
Итак, подключились к UART. Попробуем прошить пациента, одновременно поглядывая в консоль. Вдруг там есть какие‑то строки, которые помогут нам найти участки кода, отвечающие за процесс прошивки?
Удача! Роутер пишет в консоль сообщение о начале обновления и указывает раздел, на который льется прошивка. При реверсе будем искать строки, а по строкам — место в коде. Классика! Попробуем поискать строку start... в прошивке:
Мы нашли утилиту fw_updater. Попробуем ее запустить:
Похоже, именно она накатывает новую прошивку в раздел Linux.
Если бы никакого текста не было, пришлось бы реверсить саму веб‑морду. Там обязательно есть сообщения, коды ошибок и прочее, что потом можно поискать. Оттуда же можно выйти на утилиты прошивки. Либо наудачу поискать утилиты, в названиях которых будут слова fw, firmware, update и всё в этом духе. Еще можно искать в прошивке строки CRC, image и прочее.
Теперь попробуем найти, откуда fw_updater вызывается в прошивке:
Мы обнаружили, что fw_updater используется в библиотеке libdhal.so. Это очень любопытно. Попробуем взглянуть, что у нее внутри. Для реверса будем использовать Ghidra. Она позволяет преобразовать бинарный исполняемый файл в код на С. Это очень удобно, и не придется копаться в ассемблерном листинге.
Еще нам понадобится утилита, которая поможет перетащить нужный файл на компьютер. Можно воспользоваться любым средством пересылки файлов по сети. Если ничего подходящего на роутере нет, то можно распаковать прошивку на компьютере тем же binwalk. Универсальных сценариев не существует.
В нашем случае есть netcat. Перекидываем libdhal.so на компьютер:
Создаем проект в Ghidra и дизассемблируем библиотеку. После беглого изучения нетрудно найти функцию с говорящим названием — check_firmware_in_buffer. Вот ее листинг на С:
Именно в этой функции принимается решение о том, подходит прошивка для роутера или нет. Проверяется три вещи: размер образа, магическое число и хеш MD5.
После анализа этого кода приходим к выводу, что:
Нетрудно понять, что магическое число располагается в последних четырех байтах прошивки и равно 0xc0ffee.
Теперь разберемся с хешем:
Видим, что хеш считается из массива local_95 размером 16 байт и файла прошивки (за исключением 20 байт в конце). 20 байт здесь — это размер MD5 плюс 4 байта магического числа. Массив local_95 строится из параметра pcVar4, в котором содержится UUID устройства.
Что происходит в этом куске кода:
Это конвертация байта из текстового представления в машинный код.
Самое время проверить, так ли мы хороши. Напишем скрипт на Python для расчета MD5:
На вход скрипт принимает прошивку. Результат выполнения ниже:
Сравнив полученный хеш с исходной прошивкой по смещению
Теперь мы легко можем создать прошивку, которая будет загружаться через стоковый веб‑интерфейс.
Хеш MD5, а тем более, как в нашем случае, сложенный из UUID и образа, встречается редко. Конкретная реализация будет зависеть от производителя, и тут кто во что горазд. Например, у отечественных вендоров вроде SNR или Keenetic с этим проще. В SNR заменили расчет суммы CRC32 ядра на CRC32 всей прошивки. А в Keenetic прописывают в прошивке магическое число, CRC32 и ID устройства.
Примерно так же по сложности обстоят дела с прошивками роутеров Xiaomi. Там тоже есть и минимальный размер прошивки, и магическое число. Только вместо MD5 используется RSA. В общем, как повезет. Но еще не встречалась подпись, которую нельзя было бы отреверсить.
Автор @41exey
Источник https://xakep.ru
У этого роутера 8 Мбайт флеш‑памяти и 64 Мбайт — оперативной. Работает он на чипе MediaTek MT7620A. Этот камень основан на архитектуре MIPS и поддерживается ядром Linux. Загрузка стоковой прошивки происходит через U-Boot. Там есть встроенный TFTP-клиент для восстановления в случае, если залил неудачную прошивку. Главное — не убить бутлоадер, иначе придется паять.
На мой взгляд, отличная железка для экспериментов. Один из минусов — у DIR-806A в оригинальной комплектации нет USB. Однако трассировка USB присутствует на плате, так что при должном уровне знаний и сноровки порт можно распаять самостоятельно.
Мы же начнем с подключения к UART, так что не забудь запастись преобразователем уровней для UART. При подключении нужно не забыть «покрестить» RX и TX. То есть RX-линию подключаем к TX, а TX — к RX. Параметры соединения — 57600 8N1.
Итак, подключились к UART. Попробуем прошить пациента, одновременно поглядывая в консоль. Вдруг там есть какие‑то строки, которые помогут нам найти участки кода, отвечающие за процесс прошивки?
Код:
signallin(6) start... mtd: "Linux"
libmtd (_mtd_write_ex): to "/dev/mtd6", size: 0x6c4764, offset: 0x0, buffer: 0x2afa6000
Код:
# grep -nr "start..." /sbin
/sbin/fw_updater:12411:(%d) start... mtd: "%s"
Код:
FW_UPDATER
(6) usage: fwupdater
Если бы никакого текста не было, пришлось бы реверсить саму веб‑морду. Там обязательно есть сообщения, коды ошибок и прочее, что потом можно поискать. Оттуда же можно выйти на утилиты прошивки. Либо наудачу поискать утилиты, в названиях которых будут слова fw, firmware, update и всё в этом духе. Еще можно искать в прошивке строки CRC, image и прочее.
Теперь попробуем найти, откуда fw_updater вызывается в прошивке:
Код:
GREP -NR "FW_UPDATER" /LIB
/lib/libdhal.so:88250:/sbin/fw_updater
/lib/libdhal.so:88254:/tmp/fw_updater
Еще нам понадобится утилита, которая поможет перетащить нужный файл на компьютер. Можно воспользоваться любым средством пересылки файлов по сети. Если ничего подходящего на роутере нет, то можно распаковать прошивку на компьютере тем же binwalk. Универсальных сценариев не существует.
В нашем случае есть netcat. Перекидываем libdhal.so на компьютер:
Код:
cat /lib/libdhal.so | nc 10.0.0.245 5000
C:
undefined4 check_firmware_in_buffer(int param_1,int param_2,undefined4 param_3,undefined4 param_4)
{
uint uVar1;
ulong uVar2;
int iVar3;
char *pcVar4;
char local_98;
char local_97;
undefined local_96;
undefined local_95 [16];
undefined auStack_85 [17];
undefined auStack_74 [92];
logmessage("check_firmware","Check signature in the firmware",param_3,param_4);
if (param_2 < 0x80) {
pcVar4 = "Too small fw";
}
else {
uVar1 = *(uint *)(param_1 + param_2 + -4);
if ((uVar1 >> 0x18 | uVar1 >> 8 & 0xff00 | uVar1 << 0x18 | (uVar1 & 0xff00) << 8) == 0xc0ffee) {
local_96 = 0;
pcVar4 = "cef285a2e29e40b2baab31277d44298b";
do {
local_97 = pcVar4[1];
local_98 = *pcVar4;
uVar2 = strtoul(&local_98,(char **)0x0,0x10);
local_95[(uint)(pcVar4 + -0x83218) >> 1] = (char)uVar2;
pcVar4 = pcVar4 + 2;
} while (pcVar4 != "");
md5_init(auStack_74);
md5_append(auStack_74,local_95,0x10);
md5_append(auStack_74,param_1,param_2 + -0x14);
md5_finish(auStack_74,auStack_85);
param_3 = 0x10;
iVar3 = memcmp((void *)(param_1 + param_2 + -0x14),auStack_85,0x10);
if (iVar3 == 0) {
logmessage("check_firmware","Signature OK!",param_3,param_4);
return 2;
}
pcVar4 = "Wrong signature!";
}
else {
pcVar4 = "Wrong magic or version";
}
}
logmessage("check_firmware",pcVar4,param_3,param_4);
return 0;
}
После анализа этого кода приходим к выводу, что:
- param_1 — указатель на буфер с файлом прошивки;
- param_2 — размер буфера.
Код:
uVar1 = *(uint *)(param_1 + param_2 + -4);
Теперь разберемся с хешем:
Код:
md5_append(auStack_74,local_95,0x10);
md5_append(auStack_74,param_1,param_2 + -0x14);
Что происходит в этом куске кода:
Код:
local_98 = *pcVar4;
uVar2 = strtoul(&local_98,(char **)0x0,0x10);
local_95[(uint)(pcVar4 + -0x83218) >> 1] = (char)uVar2;
Самое время проверить, так ли мы хороши. Напишем скрипт на Python для расчета MD5:
Код:
import sys, os, hashlib
size = os.path.getsize(sys.argv[1])
with open(sys.argv[1], "rb") as f:
data = f.read(size - 20)
hash_md5 = hashlib.md5()
hash_md5.update(bytes.fromhex("cef285a2e29e40b2baab31277d44298b"))
hash_md5.update(data)
print(hash_md5.hexdigest())
Код:
user@debian:~/md5$ python3 md5.py 2019.03.19-18.04_DIR_806A_MT7620A_3.0.1_release.bin
e5fd006108c91a7fd4e43b23575fa7cd
0x6c4750, понимаем, что хеш рассчитан правильно.
Теперь мы легко можем создать прошивку, которая будет загружаться через стоковый веб‑интерфейс.
Хеш MD5, а тем более, как в нашем случае, сложенный из UUID и образа, встречается редко. Конкретная реализация будет зависеть от производителя, и тут кто во что горазд. Например, у отечественных вендоров вроде SNR или Keenetic с этим проще. В SNR заменили расчет суммы CRC32 ядра на CRC32 всей прошивки. А в Keenetic прописывают в прошивке магическое число, CRC32 и ID устройства.
Примерно так же по сложности обстоят дела с прошивками роутеров Xiaomi. Там тоже есть и минимальный размер прошивки, и магическое число. Только вместо MD5 используется RSA. В общем, как повезет. Но еще не встречалась подпись, которую нельзя было бы отреверсить.
Автор @41exey
Источник https://xakep.ru