Тайна прошивок

    Авторы: к.ф.-м.н. Чернов А.В. (monsieur_cher) и к.ф.-м.н. Трошина К.Н.

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

    В этой статье мы расскажем об одной интересной задаче, которая была поставлена перед нами несколько лет назад. Заказчик попросил разобраться с бинарной прошивкой устройства, которое выполняло управление неким физическим процессом. Ему требовался алгоритм управления в виде компилируемой С-программы, а также формулы с объяснением, как они устроены и почему именно так. По словам Заказчика, это было необходимо для обеспечения совместимости со «старым» оборудованием в новой системе. То, как мы в итоге разбирались с физикой, в рамках данного цикла статей мы опустим, а вот процесс восстановления алгоритма рассмотрим подробно.

    Практически повсеместное использование в массовых устройствах программируемых микроконтроллеров (концепции интернета вещей IOT или умного дома SmartHome) требует обратить внимание на бинарный анализ встраиваемого кода, или, другими словами, бинарный анализ прошивок устройств.

    Бинарный анализ прошивок устройств может иметь следующие цели:

    • Анализ кода на наличие уязвимостей, позволяющих получить несанкционированный доступ к устройству или к данным передаваемым или обрабатываемым этим устройством.
    • Анализ кода на наличие недокументированных возможностей, приводящих, например, к утечке информации.
    • Анализ кода для восстановления протоколов и интерфейсов взаимодействия с устройствами для обеспечения совместимости данного устройства с другими.

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

    Анализ формата бинарного файла


    Если в мире «настоящих» операционных систем форматы исполняемых файлов стандартизированы, то в мире встраиваемых программ каждый вендор может использовать свое проприетарное решение. Следовательно, анализ бинарного файла прошивки приходится начинать с анализа формата бинарного файла.

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

    Файл с прошивкой оказался текстовым файлом. Он содержал строки примерно следующего вида:

    :04013000260F970CF8
    :10020000004D000B043F000B34AD010C002FFE4D30
    :02023000FD0BC1
    :1004000018001A0000001E0008005E000200190052

    Внимательно посмотрев на набор этих строк, мы сообразили, что это – вполне стандартный для микроконтроллеров формат Intel HEX. Файл состоит из записей, в каждой из которой указывается ее тип, адрес размещения в памяти, данные и контрольная сумма. Само по себе использование формата Intel Hex предполагает, что файл, скорее всего, не зашифрован и представляет собой образ программы, размещаемой в памяти.

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



    Мы получили файл с образом памяти какого-то 16-битного или 8-битного микроконтроллера. И что это за микроконтроллер – непонятно. Мы взяли IDA Pro и попробовали дизассемблировать файл всеми возможными вариантами поддерживаемых процессоров. И ничего. Ни один процессор из поддерживаемых IDA Pro не подошел: листинг либо не сгенерировался, либо содержал явную бессмыслицу. Возможно, это была программа для одного из поддерживаемых IDA Pro процессоров, но мы что-то делали не так. Например, нужна была просто дополнительная обработка файла образа. В любом случае, здесь можно было приостановить работы и запросить дополнительную информацию о бинарном файле.

    Все процессоры примерно одинаковы


    Но нам стало интересно, а что мы можем понять по бинарной программе, даже если процессор, для которой она скомпилирована – неизвестен. Ответ «ничего» — неинтересен, даже если мы сможем понять чуть-чуть, это лучше, чем ничего. Очевидно, что текстовые строки могут дать информацию о программе, но мы целим в большее – понять что-то из структуры программы.
    Различных процессорных архитектур – большое количество. Эволюция вычислительной техники порождала даже самые необычные архитектуры типа троичных компьютеров. Однако микропроцессоры и микроконтроллеры, существующие в настоящее время, по крайней мере массовые, удивительно похожи друг на друга.

    Ниже мы перечислим базовые принципы, общие для современных микропроцессоров.

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

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

    Следующие принципы, вообще говоря, не являются базовыми архитектурными принципами, но практически повсеместно выполняются, в особенности для машинного кода, не написанного вручную на языке ассемблера, а сгенерированного компилятором языка высокого уровня.

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

    Как правило, подпрограммы имеют одну точку выхода, возвращающую управление в точку вызова, но это менее существенно, чем требование одной точки входа для каждой подпрограммы. Такой код обычно получается в результате компиляции программы. Оптимизатор периода компоновки (link-time optimizer) может частично разрушить эту структуру для уменьшения размера программы, а размер программы – критичен для встраиваемых систем. Тем более эту структуру может разрушить обфускатор кода.

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

    Как можно применить эти принципы к первичному анализу бинарного кода?

    Сделаем базовое предположение, что в системе команд процессора есть инструкция RET (возврат из подпрограммы). Эта инструкция имеет какое-то фиксированное бинарное представление, которое мы и будем искать. Если RET не единственный, как в x86, где у RET есть аргумент – размер области параметров подпрограммы, или если RET является побочным эффектом более сложной операции, как в ARMv7, где значение PC можно извлекать из стека одновременно со значениями других регистров(ldmfd sp!, { fp, pc }), тогда, скорее всего, наш эвристический поиск не даст результатов.

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

    • Поток байт, из которого формируются инструкции, причем разные инструкции кодируются разным числом байт. В этой категории самый известный представитель – семейство x86, начиная от первых процессоров 8080 и до самых современных 64-битных процессоров. Одна инструкция процессора x86_64 может кодироваться последовательностью от 1 до 16 байт. К этому же семейству процессоров с переменной длиной инструкции относится 8051, используемый в микроконтроллерах.
    • Поток 16-битных значений. При этом каждая инструкция имеет фиксированный размер – 16 бит.
    • Поток 16-битных значений, при этом инструкции имеют переменный размер. Один из представителей этого семейства – архитектура PDP-11, в которой непосредственно команда занимает первые 16 бит, а за ней могут идти либо непосредственные значения, либо адреса в памяти для прямой адресации. Сюда же можно отнести кодирование THUMB а архитектуре ARM.
    • Поток 32-битных значений, каждая инструкция имеет фиксированный размер – 32 бита. Это – большинство 32- и 64-битных RISC-процессоров: ARMv7, ARMv8, MIPS.

    Сделать выбор между байтовым потоком переменной длины и потоком 16-битных слов поможет просмотр образа памяти «на глаз». Как бы не кодировались инструкции процессора, в программе достаточной длины они неизбежно будут повторяться. Например, на x86 инструкция

    add    %ebx,%eax

    которая складывает значения регистров eax и ebx и помещает результат в eax, кодируется двумя байтами:

    01 d8.

    На ARMv7 инструкция

    add	r0, r0, r1

    которая складывает значения регистров r0 и r1 и помещает результат в r0, кодируется 32-битным значением e0800001.

    В достаточно большой программе подобные инструкции будут повторяться не один раз. Если интересующая нас последовательность байт (например, 01 d8) встречается по произвольному невыровненному адресу, можно сделать предположение, что у процессора инструкции кодируются потоком байт переменного размера. Если же значение, например, e0800001 встречается только по адресам, кратным 4, можно сделать предположение о фиксированном размере инструкций процессора. Конечно же, здесь возможна ошибка, что мы приняли байты данных за инструкцию, или так случайно получилось, что некоторая инструкция всегда оказалась выровнена. Однако, влияние такого «шума» на программу достаточного размера будет невелико.

    Когда мы посмотрели на анализируемую прошивку под таким углом, стало понятно, что скорее всего у рассматриваемого процессора инструкции кодируются 16-битными значениями.

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

    Значение (hex) Количество Частота
    0b01	       854	  5.1%
    0800	       473	  2.8%
    8c0d	       432	  2.6%
    2b00	       401	  2.4%
    4e1c	       365	  2.2%
    0801           277	  1.6%
    

    Инструкцию RET будем искать среди этих наиболее часто встречающихся в коде 16-битных значений. Сразу же мы будем искать инструкцию CALL – парную к инструкции RET. Инструкция CALL имеет по крайней мере один параметр – адрес перехода, поэтому фиксированными значениями не обойтись.

    Сделаем предположение, что во многих случаях немедленное после конца одной подпрограммы, то есть после инструкции RET, начинается другая подпрограмма, и эта подпрограмма вызывается инструкцией CALL из другой точки программы. Большое количество переходов на адрес, непосредственно следующий за RET, будет одним из признаков, отличающих инструкцию CALL. Конечно же, это правило не универсальное, так как на некоторых платформах, в частности, ARMv7, непосредственно после завершения из подпрограммы обычно располагается пул констант. В этом случае мы можем рассматривать некоторый разумный диапазон адресов непосредственно после RET в качестве точек перехода инструкции RET.

    В случае инструкции CALL для перехода к подпрограмме вариантов ее кодирования может быть достаточно много. Во-первых, процессор может использовать разный порядок байт в слове: little-endian, как на большинстве современных процессоров, когда многобайтное целое число записывается в памяти, начиная с младшего байта, и big-endian, когда многобайтное целое число записывается в памяти, начиная со старшего байта. Практически все современные процессоры работают в режиме little-endian, но отбрасывать другие возможные порядки байт в слове не стоит.
    Во-вторых, инструкция CALL может использовать либо абсолютную адресацию точки перехода, либо адресацию относительно текущего адреса. В случае абсолютной адресации закодированная инструкция содержит адрес, на который требуется перейти в каких-то битах закодированной инструкции. Чтобы обеспечить вызов подпрограммы из любой точки 16-битного адресного пространства в любую другую точку по абсолютному адресу 16-битного слова закодированной инструкции не хватит, так как кроме адреса перехода нужно еще где-то хранить биты кода операции. Поэтому имеет смысл рассматривать два 16-битных слова подряд и пробовать разные варианты разбиения адреса перехода между этими словами.

    Альтернатива абсолютному кодированию адреса подпрограммы – относительное кодирование. В закодированную инструкцию мы записываем разность адреса подпрограммы и текущей точки. Относительное кодирование обычно предпочтительнее абсолютного, потому что, во-первых, программа с относительными переходами позиционно-независима, то есть может размещаться в памяти с любого адреса без каких-либо изменений в бинарном коде. Во-вторых, для кодирования смещения можно резервировать меньше бит, чем размерность адресного пространства, исходя из того, что во многих случаях вызываемая подпрограмма находиться не так далеко от вызывающей. Однако, если смещение для вызова выходит за диапазон представимых значений, придется вставлять специальные инструкции-«трамплины».

    Относительное кодирование адреса подпрограммы может выполняться с некоторыми вариациями: во-первых, адрес текущей точки программы может браться либо как адрес текущей инструкции, либо как адрес следующей инструкции, как в процессорах x86, либо адрес еще какой-то инструкции около текущей точки. Например, в процессорах ARM за точку отсчета берется адрес текущей инструкции +8 (то есть не адрес следующей после CALL инструкции, а адрес следующей за следующей инструкции). Кроме того, поскольку в нашем случае программа записывается в виде потока 16-битных слов, логично ожидать, что и смещение будет выражено в словах. То есть, чтобы получить адрес вызываемой подпрограммы, смещение потребуется умножить на 2.

    С учетом всего вышеописанного получаем следующее пространство перебора для поиска пары CALL/RET в бинарном коде.

    Сначала в качестве кандидатов для инструкции RET берем 16-битные слова из списка наиболее часто встречающихся значений в коде. Дальше для поиска инструкции CALL перебираем:

    • Big-endian и little-endian порядок байт в слове
    • Абсолютное и относительное кодирование адреса подпрограммы в инструкции.

    Для абсолютного кодирования рассматриваем два 16-битных значения подряд, а именно, перебираем различные варианты размещения битового поля, хранящего абсолютный адрес, в 32-битном слове, а также для относительного кодирования рассматриваем и 16-битные значения, и два 16-битных слова подряд. Дальше перебираем различные варианты размещения битового поля, хранящего смещения. Проверяем, выражается ли смещение в байтах или в 16-битных словах, то есть нужно ли умножать смещение на 2, проверяем разные варианты точки отсчета: адрес текущей инструкции, адрес следующей инструкции.

    Для каждого из вариантов в описанном выше пространстве перебора вычисляем статистики:

    • Сколько предположительных адресов начала подпрограмм не являются очевидно корректными, то есть расположены там, где нет ничего, либо где заведомо находятся данные (явные строки, или явные таблицы значений). Даже для значения, соответствующего правильному кодированию инструкции CALL, вполне возможно некоторое небольшое количество некорректных адресов начала подпрограммы, если значение, соответствующее инструкции CALL, случайно встретилось в данных.
    • Сколько предположительных адресов начала подпрограмм находится непосредственно после предположительной инструкции RET.
    • Сколько предположительных адресов начала подпрограмм используется более одного раза.

    Если наши предположения о паре инструкций CALL/RET верны, то она должна найтись в описанном пространстве перебора. Но возможно будут еще и ложные срабатывания. Ну хорошо, запускаем перебор.

    И находим только один возможный вариант!

    Trying 8c0d as RET
    After-ret-addr-set-size: 430
    Matching call opcodes for 1, ff00ff00, 1: 000b003d: total: 1275, hits: 843 (66%), misses: 432 (33%), coverage: 76%

    Итак, 16-битное слово 8c0d подходит в качестве кандидатуры для инструкции RET. Всего в прошивке 430 позиций программных адресов непосредственно после этой инструкции. Рассматривались 32-битные значения (два 16-битных слова подряд), при значении маски адреса, равном ff 00 ff 00 обнаружена инструкция с кодом 00 0b 00 3d. Таких инструкций всего 1275, из них 843 (то есть 66%) передают управление в точку, непосредственно следующую за кандидатом в RET. Таким образом, выявлены две инструкции:

    • RET: 8c0d (16-bit Little-Endian)
    • CALL HHLL: 0bHH 3dLL (2 16-bit Little-Endian)

    Инструкция CALL использует абсолютную адресацию, причем при записи адреса перехода сначала записывается старший байт, затем записывается младший байт. Вполне возможно, что это в реальности две инструкции процессора, каждая из которых загружает по половине адреса перехода, но с точки зрения анализа программы это и не важно. Зная инструкции CALL и RET, мы можем более точно разметить области кода и данных программы, что будет важно при дальнейшем анализе.

    Продолжение следует…

    Дальше мы расскажем, как восстанавливались условные и безусловные переходы и некоторые арифметические и логические операции.
    Ростелеком-Solar
    207,00
    Безопасность по имени Солнце
    Поделиться публикацией

    Комментарии 91

      +5

      Это не просто реверс инженеринг, это уже следующий уровень, даже не могу адекватно название придумать.
      Аплодирую стоя.


      p.s. помню фантастические сериалы типа звёздные врата, где учёные за дни и считанные годы разбирались с железом и софтом чужой цивилизации..


      Полагаю вы занимаетесь именно этим только взаправду.


      p.p.s. не думаю, что прошивку того же квантового процессора будет так просто разобрать, да просто нейронная сеть поставит исследователя в тупик

        0
        Спасибо! Работа была действительно интересная. Мы были обязаны про нее долгое время молчать, но вот сейчас срок «молчания» истек. Продолжение следует…
          0
          Пока шел срок молчания появились еще сотня другая архитектур. Интересно как будет дело обстоять с ними. Молчать придется еще дольше видимо.
        0
        Частотный анализ… «Золотой жук» Эдгара Аллана По сразу вспоминается.
          0
          А также «Пляшущие человечки» А. Конан-Дойля
          +5
          Спасибо за статью, интересно, только неожиданно прервалась, можно сказать, в самом начале( Тоже решал подобную задачу, и тоже начинал с определения размера инструкции «на глаз», потом CALL/RET, правда у меня был работающий девайс и дальше я восстанавливал инструкции в основном методом тыка.
            0
            И кстати, хорошо бы в посте опубликовать саму анализируемую прошивку.
              0
              Спасибо. Продолжение следует… Возможно по мотивам данной работы будут сделаны задачи для «Кубка реверса».
                0
                Ждём-ждём!
            0
            Частотный анализ и додуматься до CALL/RET, прям как детектив про Шерлока Холмса.

            По длине бинарника можно прикинуть, что за зверь. Прошивка для 8битки вряд ли будет больше 64Кб. Ну и исключить известные архитектуры, типа 8080, Z80, dsPIC.

            А еще, позвонить заказчику и попросить сфоткать начинку подробнее.
              +1
              Заказчику мы звонили, только Заказчик все, что знал, сказал в самом начале работы.
              0
              Очень интересно, жду продолжения!
                +1
                Спасибо. Оно будет достаточно скоро.
                +1
                Не прбовали поглядеть, по каким адресам грузится файл прошивки, и, исходя из этого, определить что за микроконтроллер используется? Насколько я знаю, у каждого семейства МК свои адреса маппинга памяти для различных нужд.
                  +2
                  Прошивка отображалась с нулевого адреса вместе с векторами прерывания. Это типичная ситуация.
                  0
                  После «ни один процессор из поддерживаемых IDA Pro не подошел» я бы предположил, что там используется к примеру форт или что-то подобное, и большая часть прошивки не является машинным кодом.
                    0
                    Разумное предположение, но все оно не подтвердилось.
                      +1
                      Скорей всего, японский МК или проц. Всякие NEC любили вдарять по экзотике.
                      Да и автомобильные МК тоже не похожи ни на что.
                        0

                        Есть ещё большое количество ASIC без (открытой) документации, скажем, TI и Atmel тоже в этом замечены. А уж про процессора для сотовых и говорить нечего.

                          +1
                          Прошивка авторами расколота, значит что-то доступное, а не кастом. Возможно, хорошо забытое старое. Ардуино на КПДВ тоже наверное с намёком.
                      +1
                      Если есть читаемые ASCII строки, то часто их адрес присутствует в коде команды загрузки указателя в регистр, а дальше идет CALL подпрограммы вывода или сравнения.
                        0
                        Здесь надо учитывать, что мы имеем дело не просто с приложением, а с прошивкой. Поэтому данное высказывание требует проверки.
                        –3
                        пробовали скормить прошивку IDA Pro или unidasm-у из комплекта MAME и прогнать все поддерживаемые архитектуры?
                        или в даташитах на известные MCU поискать наиболее часто встречающиеся в бинарнике опкоды?

                        в авторах вроде указаны люди с к.ф.-м.н. итп учеными степенями, но с системным подходом похоже проблемы…
                          +3
                          Мы писали, что IDA Pro мы пробовали и такой архитектуры он не поддерживает, unidasm-у тоже пробовали, тоже не поддерживает.
                          +1
                          Оффтопик
                          Скажите, пожалуйста, а упомянутый к.ф.-м.н. Чернов А.В. случаем не преподавал практикум по ЭВМ на факультете ВМиК МГУ примерно 15-20 лет назад?
                            +6
                            Сева, привет! Упомянутый к.ф.-м.н. Чернов А.В. до сих пор преподает там практикум. И у тебя преподавал. А выше упомянутая к.ф.-м.н. Трошина К.Н. в девичестве Катя Долгова училась с тобой в одной группе на 3-5 курсе :)
                            Мир тесен…
                              +1
                              Катя, привет! Тебя, конечно, не признал, но нечто подобное подозревал:)

                              Да, действительно очень и очень тесен.:) Тем более логично, что встречи итшников происходят на сайте хабра:)

                              PS До сих пор рассказываю всем историю как я чуть не сдал Чернову А.В программу на нобелевскую премию на практикуме:) Но не сдал все же.
                                +2
                                Да забавно :) Мир очень маленький…
                            0

                            Я встречал (дорабатывал) такой изврат, как виртуальная ассемблерная машина на ассемблерной же прошивке для PIC16.
                            Для реально существующих процессоров намного проще проверять по карте памяти (порты ввода/вывода, регистры периферии и т.п.)

                              0
                              Да, все так, если есть карты памяти, то их, конечно, надо использовать. Решая такие задачи, собираешь из того что, дали. И просить то, что надо, не приходится.
                                +1

                                Но от вас же просили компилируемый исходник, т.е. где-то существует тулчейн под вполне конкретный проц? А для этого тулчейна существуют вполне конкретные заголовочные файлы, ибо писать (uint16_t)0x4000080A=01 вместо GPIOA->ODR=1<<CS в конечном исходнике было бы несколько странно…
                                Плюс, например, комбинация режимов периферии (таймеров) и DMA, обвязанных ещё и схемотехнически, без знания периферии камня, схемы и осциллограмм в готовом устройстве — превращается просто в набор бессмысленных присвоений и адресов. Которые, на самом деле, являются обработчиками прерываний от DMA, скажем.
                                Ну то есть, видно, что труд вами проделан большой, но за абстракциями и недоговорённостями смысл потерялся.

                                  0

                                  Пардон, (uint16_t)a=b вместо
                                  GPIOA->ODR=1<<CS;

                                    +1
                                    У нас не было такой информации в доступе. Но Вы правы, такой tool chain, непременно существует.
                                      0
                                      Ему требовался алгоритм управления в виде компилируемой С-программы...

                                      И как вы собирались без тулчейна выдать ему компилируемую C-программу? =))
                                      То, как мы в итоге разбирались с физикой, в рамках данного цикла статей мы опустим...

                                      Если с физикой разбирались, то скорее всего у вас доступ устройству был, поэтому сделать оценку контроллера можно было.
                                        +3
                                        Так они не для старого проца программу должны были восстановить, а перенести на новую железяку.
                                          0
                                          Компилируемую С-программу мы сделали руками. Продолжение будет далее…
                                          Доступа к устройству никогда не было.
                                            0
                                            И у заказчика тоже никогда не было?
                                              0
                                              И у заказчика тоже никогда не было.
                                +1
                                Есть системы где шины и адреса и данных рандомно перепутаны. Для них Последовательное исполнение инструкций. Процессор исполняет инструкции, расположенные последовательно в памяти. естественно не выполняется.
                                Когда в руках живая плата — это прозванивается за 10 минут. Но в случае если есть только бинарник — было бы интересно как тут быть?
                                  0
                                  Нам тоже было бы интересно такую задачу порешать.
                                    0
                                    У меня например на этот счет нет идей… если у вас тоже — получается, что этот метод будет защитой от реверса вашим методом…
                                      +3
                                      Здесь мы рассказали, как решали конкретную задачу.
                                        0
                                        В которой вам просто повезло, что было не так! А могло бы и не быть — т.е. ваше решение базируется на везении и том, что линейное пространство в hex-ах встречается с более высокой вероятностью.
                                          0
                                          Это не везение, это знания. Здесь рассказано решение конкретной задачи, тем методом, которым ее удалось решить.
                                            0
                                            Вы сделали очень много последовательных допущений, каждое из которых волшебным образом сработало. (линейность пространства, напрямую хранятся адреса вызовов подпрограмм). Я вам привел пример, когда одно из них не срабатывает — этот пример поставил вас в тупик. В чем тогда ценность решения вашей конкретной задачи, если весь класс задач так не решается? В конечном счете вы могли просто собрать 10-низкоуровневых специалистов каждый по своей архитектуре и дать каждому посмотреть код глазами — и без всякого частотного анализа специалист по этой архитектуре ткнет пальцем «о — это мой». Т.е. равноценно обычному угадыванию… Например куча народу только глянув на первую страницу сможет подтвердить или опровергнуть гипотезу принадлежности к MCS51 по векторам 0-3-0b-13-1b и коду LJMP 02h. Или к Z80 по коду JP 0c3h и команде DI (0f3h) с которой все начинается в большинстве % случаев. (в АОН-о строении это веками наработанная практика, ибо как раз файл с одним и тем же названием легко мог принадлежать к одной из этих двух архитектур)…
                                              +1

                                              Уверен, они сделали гораздо больше количество параллельных допущений, большинство из которых не сработало. О том, которое сработало написали пост.
                                              Как понимаете, это не про везение.


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

                                                0
                                                А то, что частотный анализ перестает работать. Точнее он работать будет но смысла не имеет, ведь за RET-ом не будет начала процедуры…

                                                Я просто не вижу чем в данном конкретном случае частотный анализ, чем то лучше банального «угадывания» по набору априорных сведений об архитектурах — кроме того что он подразумевает писанину кода.
                                                  0
                                                  А еще там мог оказаться байткод для брейнфакВМ(тм) (что, кстати, более вероятно, чем raw-образ прошивки для параллельной ПЗУ). Был бы другой вариант решения, что с того?

                                                  Я, кстати, практически уверен что в рассматриваемом случае собака зарыта в конвертации hex2bin.
                                                  Ждем продолжения истории.
                                                  +1
                                                  Спасибо за комментарий, Вы абсолютно правы, все было именно так. Мы делали значительно большие допущений, экспериментов и всего остального, а написали только про то, что отработало.
                                    0
                                    Статья супер. Вопрос не совсем по теме, но близко: не пробовали ли реверсить прошивки ПЛИС?
                                      +1
                                      Спасибо. Нет, не пробовали, но с удовольствием попробуем.
                                        0
                                        С ПЛИСами есть одна засада — дойти до уровня RTL вполне себе может и получится, а вот далее полный швах.
                                        +3

                                        Вы не упомянули о случае косвенной адресации, когда адрес перехода хранится в ячейке памяти. Возможно, это не очень распространено в прошивках, но очень часто встречается в бинарном коде — такие вещи, как высокоуровневые классы — это всегда таблица переходов для их методов, например.

                                          +2
                                          Да и switch/case тоже часто так компилится, особенно кстати в прошивках, когда, например, есть какой-то входящий последовательный протокол.
                                            0
                                            это если case'ы подряд идут и компилятор достаточно умный. а вот для
                                            case 1:…
                                            case 2:…
                                            case 100500:…
                                            ничего кроме связки условных переходов и не придумаешь…
                                            0
                                            Продолжение следует…
                                              0
                                              Нет, адреса методов невиртуальных С++ классов линкеру известны, и он, скорее всего, сделает вызов по константному адресу (исключений я не знаю. Впрочем, все архитектуры я тоже не знаю ;-) ).
                                              Другой вопрос, что команд перехода может быть сильно больше одной (когда размер адресуемой памяти превышает размер регистра, фантазия проектировщика системы команд начинает бить ключом — может появиться пяток разных способов адресации в одном чипе).
                                              0
                                              Насколько часто компилятор проявляет энтузиазм, создавая подпрограммы там, где их не было в высокоуровневом коде? Зависит ли это от флагов оптимизации? А то бывают же случаи, когда на тыщу строк кода меньше десятка функции, да и то почти все — обработчики прерываний, тут бы поиск RET/CALL по статистике дал бы дубу. Я бы искал операции вроде ADD и MOV как самые часто встречаемые в вообще любой программе, а там уже по их кодам перебирал всевозможные контроллеры по каталогу
                                                0
                                                Насколько часто компилятор проявляет энтузиазм, создавая подпрограммы там, где их не было в высокоуровневом коде?

                                                AFAIK, никогда. Зато часто делает наоборот — инлайнит подпрограммы, так что в бинарнике уже нету RET/CALL.

                                                Я бы искал операции вроде ADD и MOV как самые часто встречаемые в вообще любой программе

                                                Им соответствуют сотни кодировок в зависимости от операндов, так что частотный поиск уже не поможет.
                                                  0
                                                  В прошивках минимизируется размер программы. Inlining сильно увеличивает размер кода, при оптимизации размера программы, вряд ли будет использоваться.
                                                  +2

                                                  На так давно помогал с прототипом защиты от подобного анализа для "мягких" процессоров.


                                                  Идейно защита достаточно проста — вставляется несколько скремблеров, в частности в шину данных и в декодер инструкций, конечно с зависимостью от адреса и ключа прошивки. Технически же сложности из-за необходимости экономить cells и latency, ну и возня с toolchain. Но получается очень неплохо, можно даже специально оставлять статистические bias/skew, которые при попытке их использовать уводят в совершенно неверном направлении ;)

                                                    0
                                                    Очень интересный опыт. Было бы интересно получить подробности работы.
                                                    +2
                                                    Очень любопытная статья! Подписался, жду продолжения. Но сразу возникла пара вопросов:
                                                    — во-первых, как на счет возможных моральных, и юридических проблем? Ведь, по сути, вы украли чью-то прошивку для вашего клиента (revers engineering кода запрещается, AFAIK, практически, любой коммерческой лицензией)
                                                    — во-вторых, насколько мне известно, revers engineering это очень дорогостоящее бизнес-решение. Что же такое «крутое» вы реверсировали? ;) Или ваш прайс дешевле разработки аналога с white paper?

                                                    P.S. В моей американской практике был один случай, когда мы за три недели «содрали» чужой дивайс (многоканальный цифровой фильтр). Но… схематика была переработана (значительно улучшена, в сравнении с конкурентом), firmware был написан с «white paper», по спекам, ну, а «фронт энд» был написан заново, на голову превосходя то убожество, что давал конкурент. Это, как мне кажется, немного другой случай…
                                                      +4

                                                      Возможно, эта прошивка была не украдена, а сбита ПВО.

                                                        0
                                                        Плюспять, насмешили! :D

                                                        P.S. ПВО ее, небось, и в Intel HEX сразу перевело? Ну, значит, скоро новый мульик будет :)
                                                          +1

                                                          так или иначе, правообладатели либо не в курсе, либо в суд подавать не станут :-)


                                                          К тому же, не стоит забывать про секретность — все подробности вроде конкретных кодов команд, а может и источник получения прошивки, наверняка изменены.

                                                            0
                                                            Это все понятно. И я очень далек от того, чтобы «осуждать» работу топикстартера, просто поинтересовался (а вдруг это «мериканский шпион» заказал декомпиляцию прошивки новой российской супер-дупер-гипер-атомной ракеты? :D ).

                                                            Меня больше второй вопрос заинтересовал: каков, хотя-бы, порядок стоимости подобного проекта? По некоторым косвенным сведениям, за работу подобного уровня в Штатах просят фантастические (для IT) деньги порядка $300/h (почти как адвокаты).
                                                        +1
                                                        Мы ничью прошивку не крали. У нам пришел Заказчик и попросил разобраться с тем, что она нам передал. Передавал прошивку по документам. Мы выполнили задачу.

                                                          +2

                                                          1280 ГК РФ:
                                                          Лицо, правомерно владеющее экземпляром программы для ЭВМ, вправе без согласия правообладателя и без выплаты дополнительного вознаграждения воспроизвести и преобразовать объектный код в исходный текст (декомпилировать программу для ЭВМ) или поручить иным лицам осуществить эти действия, если они необходимы для достижения способности к взаимодействию независимо разработанной этим лицом программы для ЭВМ с другими программами, которые могут взаимодействовать с декомпилируемой программой

                                                            +1
                                                            Там есть пункт «4. Применение положений, предусмотренных настоящей статьей, не должно противоречить обычному использованию программы для ЭВМ или базы данных и не должно ущемлять необоснованным образом законные интересы автора или иного правообладателя

                                                            Я понимаю, что Катю «проблемы индейцев не волнуют», но мы-то все взрослые люди…
                                                              0

                                                              А вы по каким критериям решили что имело место ущемление чьих-то законных интересов?

                                                                0
                                                                Это лишь мое предположение (впрочем, с достаточно высокой степенью вероятности, думаю). Возможно, однако, что я не прав, и заказчиком двигало чистое любопытство, а также осознанная реализация своих прав, обусловленная российскими законами ;)
                                                                  0
                                                                  Мы делали работу для обеспечения совместимости оборудования. Выступали как подрядчик.
                                                            0
                                                            В 200х году фирма к примеру заказала бифуркаторный бульбулятор с интерфейсом RS232 у другой компании, по договору у заказчика были допустим все права на код и прочее. Но благодаря раздолбайству(не передали исходники, схематику/передали, но они лежали на единственном диске, который навернулся со стола, потом запилил всю поверхность и перешел в разряд «восстановление данных — 10$ за мегабайт без гарантии») исходники были потеряны.
                                                            В 200х+y году фирма, которая это делала прекратила своё существование, ну и для вишенки на торте со всех складов и из продажи исчез применённый заказной микроконтроллер…
                                                            В 200x+y+z году надо было выпустить новую партию…
                                                            По любому законодательству в этом случае можно обреверситься что прошивкой, что схемой устройства, чтобы выпустить новую партию.
                                                            +1
                                                            Обычно задача стоит иначе: есть работающая железка, надо вытащить из нее прошивку. При этом известна модель процессора, потому что он в руках. Но обычно считывание прошивки запрещено битами защиты, что и является основным препятствием.
                                                            У вас же редкая ситуация — прошивка на руках в легко читаемом формате, но неизвестно от какого процессора. :-).
                                                              0
                                                              Мне кажется, или тут только что признались в неком деянии совершенном группой лиц по предварительному сговору и в крупных размерах?
                                                                0

                                                                Все история выдумана, совпадения с реальностью случайны.

                                                                  +2
                                                                  Не обязательно. У меня был случай, когда заказчик протерял исходники модуля, и модуль остался только в виде .jar файла, который мне потом передали с задачей восстановить исходники. Но там была Java, и было все элементарно.
                                                                    0
                                                                    И такое раздолбайство весьма часто встречается.
                                                                      0

                                                                      Аналогичная ситуация — есть apk, исходники утеряны, кто писал неизвестно, так как за это время много людей в компании поменялось. Такие ситуации сплошь и рядом

                                                                  +1
                                                                  Почему не выбрали «Реверс-инжиниринг»? Это ведь прямо оно самое.
                                                                    0
                                                                    Наверное, пропустили… Спасибо за комментарий.
                                                                      +1
                                                                      Оказывается, у вас все статьи пропустили этот тэг. Лучше добавьте, такой материал не должен затеряться.
                                                                    0
                                                                    А чем в итоге анализировали-то? Что-то самописное, походу?
                                                                      0
                                                                      Да, мы в итоге написали свой декомпилятор. Он долго был в отрытом доступе и с него было сделано множество клонов. В ближайшем будущем мы его реанимируем и выложим в открытый доступ обратно.
                                                                      0
                                                                      Там наверное в начале идёт таблица прерываний, полностью состоящая из инструкций JMP или типа того? Например в AVR там будут инструкции относительного перехода RJMP, по которым можно сразу найти начало основной программы. Ещё они будут почти одинаковые и выравнены по 4 байта, т.к. в AVR все команды 16/32 бит. Но, судя по найденным кодам, у вас там что-то другое. Интересно было бы попробовать поиграть в такую игру.
                                                                        0
                                                                        Игра секретная, нам про неё не расскажут. Вон, даже hex-файл в начале статьи исправленный, у него контрольные суммы не сходятся. Впрочем, даже если их поправить, там ничего сверхсекретного не видно: pastebin.com/1gzN7mnQ

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

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