Pull to refresh

Делаем кастомную прошивку для телефонов Grandstream

Reading time 7 min
Views 32K
Наша компания наконец решила перейти на ip телефонию, и мы закупили ip телефоны Grandstream разных моделей, среди них были модели GXP2130 и GXP2160. Всё бы ничего, но BLF клавиши на этих телефонах, в случае свободной линии, светятся жутко ярким зелёным цветом, сильно раздражая. Ниже расскажу, как я решал эту проблему.


Поиск уязвимости

Для начала прогнал прошивку телефона через binwalk и посмотрел в hex редакторе. Итог расстроил, прошивка была зашифрована, значит надо идти с другой стороны, попробовать получить рутовый доступ на сам телефон. В течение почти двух недель, я искал уязвимость в фильтрации полей в веб интерфейсе. В паре мест нашёл возможность передать свои параметры коммандам syslogd и udhcpd. В случае сислога, интереса это не предоставляла, а вот в случае udhcpd, была возможность указать параметр -s, который ссылался на скрипт, запускаемый для настройки интерфейса. Здесь можно было выполнить любую команду, НО ей нельзя было указать свои параметры, она всегда выполнялась с параметром defconfig. С помощью этой уязвимости, сделать ничего путнего так и не удалось. Поэтому поиск уязвимости продолжился. И я её нашёл! Не буду рассказывать где именно, т.к. производители могут её быстро прикрыть, а в будущем она может ещё пригодиться, ну и рут доступ можно получить без неё.
Далее я предположил, что производитель наверняка должен был оставить возможность получения рута и для себя, например для отладки, и полез искать это. Проанализировал все скрипты, выполняемые через web, ничего похожего там не нашёл. Далее начал анализ шелла, который доступен по ssh.

Анализ шелла

Когда мы подключаемся к телефону по ssh, то попадаем в шелл gs_config, из которого доступен небольшой список команд, обрабатываемых самим шеллом. Я предположил, что там могут быть сервисные команды, не описаные в help'е. Для этого я запустил
strings gs_config
чтобы посмотреть строки внутри бинарника, и увидел нечето любопытное:
console
fw_setenv console yes
gssu
/bin/sh
Быстро набрал эти команды в шелле, на что увидел следующее:
Grandstream GXP2130 Command Shell Copyright 2014
GXP2130> gssu
Challenge: fb72f22fc5e233ae
Response: 
Команда требует пароль, сгенерированный на основе некого challenge
Не долго думая, грузим gs_config в IDA и ищем там строку gssu

далее переходим по XFER в функцию sub_94BC

видим, что после ввода команды gssu, происходит вызов функции sub_B254, и в зависимости от её результата, выполнияется или не выполняется команда /bin/sh

Переходим в эту функцию и нажимаем F5, для переключения с ассемблера на C++ псевдокод
Пробежавшись глазами по коду видно, что вначале идёт генерация challenge, и полученный challenge кладётся в переменную s
printf("Challenge: %s\n", s);
потом принимается ввод от пользователя response и начинается сама генерация Response

Данная строка вытаскивает из nvram админский пароль, по которому вы логинитесь в веб морду и ssh
Далее он кладётся в переменную v13.
Потом анализируется содержимое переменной v1, которая является параметром нашей функции sub_B254. Её значение, говорит о том, для какой команды мы проверяем Response, таких команд должно быть три, но я нашёл только две: gssu и console
В случае gssu, мы получаем строку %s:sfTXrhCA2010:%s в переменной v14
Далее, через sprintf получаем итоговую строку вида Callenge:sfTXrhCA2010:Password и кладём её в переменную v27
Потом считаем от этой строки md5.

Дальше идёт цикл do...while на 8 итераций в котором мы проходим половину md5 суммы, и переводим её в hex строчку. Потом сравниваем её с введённым Response.
Алгоритм достаточно простой, вот реализация кейгена на питоне:
import hashlib
import sys

challenge=sys.argv[2]
pwd=sys.argv[1]
secret=':sfTXrhCA2010:'                         # /sin/sh
#secret=':dspg_cordless_config:'
#secret=':a50ba3e905c0627eb0a204d82880fb46:'    # console
str=challenge+secret+pwd
md5=hashlib.md5(str).hexdigest()
result=md5[:16]
print result


Работа с прошивкой

Ну вот, получать перманентно рута на телефоне мы научились, теперь пора изучить, как он распаковывает прошивку.
Беглым осмотром исполняемых скриптов на телефоне находим скрипт /sbin/provision, который собственно и отвечает за прошивку телефона.
Из него видно, что прошивка распаковывается на отдельные файлы командой prov_pipe_unpack, а далее, отдельные разделы прошивки дешифруются командой prov_pipedec. На самом деле, это один и тот же бинарник. Чтобы узнать все его возможности, я его так же закинул и IDA, где и нашёл интересующие нас команды, это:
prov_unpack
prov_dec
prov_enc
prov_pack
Останавливаться подробно на том, как это искалось в IDA я уже не буду, скажу лишь, что для облегчения реверс инжиниринга, на телефон был загружен gdbserver, и эти команды я прогонял в дебаггере.
Теперь об этих командах подробнее:
prov_unpack — распаковывает прошивку на отдельные файлы, запускается так:
prov_unpack  gxp1400fw.bin
Результат распаковки будет в текущей директории.
prov_dec — расшифровывает отдельные файлы прошивки
prov_dec nokey gxp1400prog.bin gxp1400prog.bin
Первый параметр — ключ прошивки, у заводских прошивок — это nokey, однако могут быть oem выриатны телефонов со своими ключами.
Второй параметр — файл который дешифруем
Третий параметр — по замыслу производителя это соответствующий образу раздел во флеше телефона, программа сравнивает версию из файла и уже прошитую версию, и если они равны, то ничего не делает. Мы же указываем вторым параметром ещё раз сам шифрованный файл прошивки, тогда всё происходит гладко. На выходе получаем расшифрованный файл gxp1400prog.bin
prov_enc — шифрует образ назад, но, образ ей нужен в специальном формате.
О формате образов подробнее:
Образ состоит из заголовка и собственно полезных данных, например файловой системы squashfs.
Ниже пример заголовка образа gxp2130prog.bin

Заголовок занимает первые 0x5C байт, вот его формат:

struct header
{
    DWORD signature;
    DWORD version;
    DWORD size_max;
    DWORD size;
    WORD image_id;
    WORD checksum;
    WORD ts_year;
    WORD ts_month_day;
    WORD ts_time;
    WORD oem_id;
    DWORD FW_V_Mask;
    WORD supported_bits1;
    WORD supported_bits2;
    WORD supported_bits3;
    WORD supported_bits4;
    WORD HW_id;
}

Назначение не всех полей мне понятно, но это не важно, рассмотрим основные:
version — это версия, если мы хотим чтобы наша прошивка прошилась в телефон, её версия должна быть выше текущей
size — размер полезных данных в прошивке, этот параметр использует prov_enc прои шифровании
checksum — контрольная сумма прошивки, используется при расшифровке прошивки, если не совпадёт, прошивка не прошьётся, об её генерации позже. Остальные поля заголовка, нужно оставлять как и в оригинале, разве что можно поправить дату.
Далее заголовок добивается нулями до размера 0х5С
Полезные данные идут со смещения 0x200, место между заголовком и полезными данными забивается единицами…
Так выглядит расшифрованyая прошивка и в таком виде она пишется во флеш, вместе с заголовком.
Утилита prov_enc, работает с другим форматом. На входе у неё должен быть файл, где вначале идут полезные данные, а сразу после них (т.е. в конце файла), заголовок, размерам 0x5C. prov_enc читает из заголовка размер полезных данных, шифрует их, а потом шифрует сам заголовок. У заголовка всегда шифруются только первые 32 байта, остальные байты не шифруются. Чтобы зашифрованный файл собрать назад в прошивку утилитой prov_pack, его необходимо перевести в первый формат, т.е. перенести уже шифрованный заголовок в начало файла, а шифрованное тело прошивки поместить по смещению 0x200.
Запускается prov_enc так:
prov_enc nokey gxp1400prog.bin gxp1400prog.bin
Здесь всё аналогично prov_dec.
prov_pack — собирает все шифрованные файлы прошивки в цельную прошивку, готовую для прошивки в телефон
prov_pack nokey gxp1400fw.bin gxp1400boot.bin gxp1400recovey.bin gxp1400core.bin gxp1400base.bin gxp1400prog.bin

На выходе имеем готовый для прошивки файл gxp1400fw.bin
С этими утилитами удобнее работать в виртульной машине qemu, чем в самом телефоне.

Патчим зелёный светодиод

Теперь перейдём собственно к тому, ради чего всё затевалось, отключению зелёного светодиода на BLF клавишах.
За GUI в телефоне отвечает процесс gs_gui, лежит он в /app/gui/ и использует гору библиотек из /app/gui/lib
Делаем grep по слову LED в папке /app/gui и находим библиотеку libFramework.so.1.0.0
Сливаем её к себе на комп и грузим в IDA, благо все функции там имеют человеческие названия.
Находим функцию с интересным названием turnOnMKPLED, из неё вызывается другая функция writeToFile(LEDCOLOR,int,bool)
Ниже её кусок:

Как видно, для работы со светодиодами используются файлы /proc/sys/dev/led/*
Попробовал записывать данные в эти файлы через echo, и нашёл что за BLF (MKP) клавиши отвечают файлы prog_green и prog_red.
Соответственно, чтобы запретить зажигать зелёный светодиод, надо просто запретить писать в файл prog_green. Я это сделал просто, в hex редакторе изменил одну букву в столе green.

Теперь пропатченный libFramework.so.1.0.0 надо залить назад в телефон. Создадим для это кастомную прошивку.
Директория /app содержится в образе gxp2130prog.bin. Распаковываем прошивку и расшифровываем этот образ. Далее в hex редакторе обрезаем все до смещения 0х200 и получаем squashfs образ.
Для работы со squashfs понадобится набор утилит squashfs-tools.
Версия 4.0 из дистрибутива Centos не смогла его распаковать, поэтому пришлось собирать из исходников версию 4.2
Распаковывем командой
./unsquashfs gxp2130prog.bin
Содержимое будет в директории squashfs-root
Далее меняем там файл libFramework.so.1.0.0 на наш и пакуем назад
./mksquashfs  squashfs-root new.bin -comp xz -all-root -noappend -always-use-fragments
Теперь нам надо подготовить заголовок. Для начала возьмём заголовок из оригинального gxp2130prog.bin и скопируем его в конец нашего squashfs образа. Теперь надо поправить в нём версию и размер. Размер — это размер самого squashfs образа, без заголовка. Теперь необходимо посчитать контрольную сумму. Вот код на Си для её генерации (на питоне аналогичный код почему-то работал в 200 раз медленнее, а запускалось это в qemu c эмуляцией arm)
#include <stdio.h>

void main(int argc, char *argv[])
{
        FILE *f;
        int summ=0;
        int word;
        char buff[32];
        int i;
        f = fopen(argv[1],"rb");
        if(f)
        {
                while(fread(buff,32,1,f) != 0)
                {
                        for(i=0;i<32;i+=2)
                        {
                                word = buff[i];
                                word |= buff[i+1]<<8;
                                summ += word;
                                summ &= 0xFFFF;
                        }
                }
        printf("%d\n",0x10000-summ);
        }
        else
                printf("Error\n");
}

Далее шифруем файл, переставляем заголовок и собираем прошивку, как описывалось в предыдущем разделе. Ну и прошиваем её.
Если захотите вернуть оригинальную прошивку, то на телефоне надо ввести от рута команду
nvram set force_upgrade=1
И телефон прошьёт любую прошивку. не зависимо от версии.

Если интересно, могу ещё написать подробную статью о провижинге телефонов грандстрим (есть нюансы, о которых нигде не писалось) и о закрытом web-api

upd: нашёлся secret для генерации recponse на GXV3140:
:gshz:
Tags:
Hubs:
+40
Comments 26
Comments Comments 26

Articles