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

    Наша компания наконец решила перейти на 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:
    Поделиться публикацией
    Комментарии 26
      +6
      Очень ценю такое количество усидчивости и упрямства. Я бы точно поленился и просто добавил или заменил резистор на светодиоде внутри аппарата.
        +2
        Вначале так и хоте, разобрать аппарат да проложить чего-нибудь между светодиодами к кнопками, но аппараты на гарантии, и их не мало.
          0
          Но теперь светодиод совсем выключен
          0
          Пф, кусочек изоленты поверх клавиш со светодиодами. И гарантия остается, и светодиоды не мешают.
            +1
            Это слишком некрасиво для такого аппарата. Он заслуживает лучшего.
          +2
          >Если интересно, могу ещё написать подробную статью о провижинге телефонов грандстрим (есть нюансы, о которых нигде не писалось) и о закрытом web-api

          Напишите конечно, интересно
            +3
            Ок, напишу, но позже, как с API до конца разберусь. API кстати, довольно функциональный, странно что они его не документировали и не выложили на публику.
              +1
              Поддерживаю, пишите об API и провижинге что есть.

              Пока раскопал самое что интересное в документации — это как заменять рингтон на произвольный. Несколько дней заливали мелодии коллегам — приходит человек с обеда, а его телефон теперь при звонке играет мелодии Газманова или Пугачевой: )
                +1
                Пока для затравки скажу, что в телефон можно влить любой конфиг через веб, без всякой авторизации, надо знать только ip :) Такое вот дырявое api
            0
            Эх, кто бы подсказал, как все кнопки в SPA502G поотключать…
              –2
              Я один подумал о том, что это можно использовать во вред? Например, собрать прошивку с закладкой, которая, например, будет отсылать информацию злоумышленнику о звонках? Или вообще редиректить на другой сип-номер…
                +1
                Вполне, а учитывая, что в телефонах grandstream есть ещё и дыра, позволяющая загрузить в него произовольный конфиг, не имея пароля, в котором можно изменить сервер обновления прошивки на свой.
                0
                У многих устройств есть дыры в админке при тестовых пингах, трейсах и пр. — нет проверок на спецсимволы и кавычки, в результате чего можно запустить телнет с правами рута на произвольном порту. А дальше все зависит от того, что включено в busybox и есть ли он там вообще. В хороших вариантах можно сразу получить netcat, а значит передавать любые данные в любом направлении.
                Воспоминания
                Самый жесткий хак у меня был 3G модема, спаренного с роутером — в мир смотрел только роутер с веб-мордой на португальском, рута не было, дорогого кабеля тоже, все порты закрыты. Взломав админку через ping, я запустил telnet, зашел туда, скачал через wget полную версию busybox, а через netcat форварднул /dev/ttyACM0 на компьютер, где стоял com2tcp и софт для прошивки модема.

                  0
                  Угу, вот такую дыру я и искал, оказалось не так просто найти.
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    У них внутри Линукс? А не пробовали «заполучить» исходники у GrandStream?
                    0
                    вот бы CardDAV ему прикрутить…
                      0
                      У меня несколько оффтоповый и общий вопрос:
                      на сколько легально разрабатывать и распространять собственные прошивки для покупемых электронных устройств: фотоаппаратов, телефонов и т.д. Выкладывать какие-нибудь самостоятельно измереные параметры, которых нет в официальных документацияй.
                      Эти моменты как-нибудь регулируются?
                        0
                        Нашёлся способ получения рута на GXV3140, подробности добавил в конец статьи
                          0
                          А почему закупили именно Grandstream, а не другое из китайского айпифоностроения?
                          Яркий зелёный светодиод при Idle на BLF — этож косяк, который надо в тестах отсеивать для себя. Или специально купили, чтобы соорудить этот путь джедая?)
                            0
                            Хотели изначально Yealink, но доллар скаканул, и елинк уже не потянули, взяли грандстримы. Времени на пробовать разные телефоны не было, т.к. начальство требовало срочно покупать, пока бакс ещё не вырос, да и в городе у нас в живую пощупать нечего.
                              0
                              Yealink хорош, да. Но ценник на него слишком уж задран — факт.

                              Grandstream — хорошие по соотношению цена/качество китайцы.
                            0
                            Эх, в моем DP715 кейген не работает, команда strings в шелле не работает тоже :-( Пичалька.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое