Предисловие

Read in English:
Database Reverse Engineering, Part 1: Introduction
Database Reverse Engineering, Part 2: Main Approaches
Database Reverse Engineering, Part 3: Code Reuse, Conclusion
Read in Russian:
Database Reverse Engineering, Part 1: Introduction
Database Reverse Engineering, Part 2: Main Approaches
Database Reverse Engineering, Part 3: Code Reuse, Conclusion

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

Постановка задачи

Мы изучим базу данных программного обеспечения Microcat Ford USA, которое является одним из программ, разработанных производителями автомобилей для использования в сервисных центрах. Назначение такого программного обеспечения - отображение запчастей, времени ремонта, поиск автомобиля по VIN-номеру и так далее. Они предоставляют много места для работы реверс-инженера, поскольку они написаны для разных архитектур, на разных языках программирования, в разное время, программистами разного уровня и, что наиболее важно, актуальны и активно используются.

Одна из целей RE базы данных такого программного обеспечения - получить доступ к информации, предоставляемой программой пользователю. Другими словами, нам нужно работать с данными напрямую, минуя графический интерфейс. Обратный инжиниринг вообще не потребовался бы, если бы разработчики выбрали массовые СУБД, основанные на SQL, а не создавали бы свои собственные БД, что верно для большинства случаев, с которыми я встречался. Эти БД не стандартизированы среди разработчиков программного обеспечения для транспортных средств, полностью различаются между собой и имеют сложную внутреннюю структуру.

Наша задача будет заключаться в том, чтобы понять, как БД хранит информацию о транспортном средстве, как получить список деталей для каждого автомобиля и как обрабатывать схемы деталей. Хочу сразу сказать, что задача была выбрана для иллюстрации процесса реверс-инжиниринга базы данных, а Microcat Ford USA был выбран только из-за хорошего набора реальных задач. Нас не будет интересовать семантика данных, поэтому смело читайте, если вы не разбираетесь в машинах.

Первый подход к обратному проектированию базы данных

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

[2.1] Рассматривайте программу, работающую с БД, как СУБД.

Дело в том, что если есть база данных и программа, с ней взаимодействующая, то достаточно воспроизвести действия, выполняемые СУБД, чтобы понять внутреннее устройство БД. Нет необходимости реконструировать БД с нуля, поскольку есть программа, которая «знает» структуру БД. Полезность этих утверждений будет подтверждена на практике позже.

Первичный анализ

После установки программы видим две папки: MCFNA и FNA_Data.

C:\MCFNA
│   ANIBUTON.VBX
│   BTDESIGN.DLL
│   CMDIALOG.VBX
...
│   MCFNA.EXE
│   MCI.VBX
│   MCLANG02.DLL
...
│   XCDUNZIP.DLL
│   XCDZIP.DLL
│   XCEEDZIP.VBX
│
├───DMS
    ...
├───Lex
    ...
├───Locator
    ...
├───mcindex
    ...
└───Print32
    ...
C:\FNA_DATA
│   AAEXT.DAT
│   ASI.idx
│   avsmodel.dat
│   AVSXRef.DAT
│   Calib.dat
│   DSET.DAT
│   EngDate.dat
│   EngDateB.dat
│   EngXRef.dat
│   FNAc.DAT
│   grp.dat
│   grp.idx
│   grpavs.idx
│   grpbasic.idx
│   grpdesc.idx
│   grpinc.dat
│   grppub.dat
│   grpxref.idx
│   LM.txt
│   MCData.DAT
│   MCData.IDX
│   MCHELP32.EXE
│   MCImage.DAT
│   MCImage.IDX
│   MCImage2.DAT
│   MCOSI.DAT
│   MCPart.IDX
│   MCPartLx.IDX
│   MotorCS.IDX
│   MS.dat
│   MY.IDX
│   partid.DAT
│   PB.idx
│   PL.idx
│   RECALL.Dat
│   SecID.DAT
│   XGROUPS.IDX
│
├───Help
    ...
├───Install
    ...
├───Lex
│       FEULex.DAT
│       LexInd.IDX
│
├───MFNotes
    ...
├───Preface
    ...
├───Pricing
    ...
├───Res
    ...
└───VIN
    │   BrVIN.DAT
    │   Last8.idx
    │   MCMFC.DAT
    │   MCRego.DAT
    │   MCVIN.DAT
    │
    ├───Decode
    │       10.dat
    │       11.dat
    │       123.dat
    │       80_C_1.dat
    │       80_C_2.dat
    │       80_C_34.dat
    │       80_C_5.dat
    │       9.dat
    │       BR_10.dat
    │       BR_11.dat
    │       BR_123.dat
    │       BR_4.dat
    │       BR_567.dat
    │       BR_8.dat
    │       C_4.dat
    │       C_5.dat
    │       C_67.dat
    │       C_8.dat
    │       E_8.dat
    │       LTT_4.dat
    │       LTT_567.dat
    │       LTT_8.dat
    │       MED_8.dat
    │       Prod20-FNARelease-Vin-Decode
    │
    ├───TSL
    │       FNITVIN.dat
    │       TSL.dat
    │       TSLCatalogue.dat
    │       TSLHi.dat
    │       TSLPT.dat
    │       TSLRef.dat
    │       TSLSection.dat
    │       TSLVIN.IDX
    │       TSLVINR.IDX
    │       VBOM.dat
    │
    └───VCIS
        1F01.xcd
        1F0B.xcd
        1F14.xcd
        ...
        4M2P.xcd
        4M2R.xcd
        4M2S.xcd
        ...
        WF05.xcd
        Z6FD.xcd
        Z6FE.xcd

Из расширений файлов ясно, что MCFNA - это каталог исполняемых файлов, а FNA_Data - это каталог файлов данных. Обратите внимание, что существует только три расширения файлов данных: dat, idx и xcd. Не обманывайтесь, часто расширения ничего не значат.

Первичный анализ состоит из четырех этапов:

  1. обзор графического интерфейса пользователя;
  2. оценка файлов данных;
  3. оценка файлов кода;
  4. поиск возможностей повторного использования кода.

Обзор графического интерфейса

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

Оценка файлов данных

Быстро просматривайте каждый файл в шестнадцатеричном редакторе, чтобы оценить его структуру (текстовый / двоичный, открытый исходный код / ​​проприетарный и т. Д.). Если вы используете Hiew, начните с текстового режима и при необходимости переключитесь в шестнадцатеричный режим. Ниже представлены скриншоты нескольких файлов.

Как видите, несмотря на одно и то же расширение, файлы структурированы совершенно по-разному. Единственное стандартизованное расширение - xcd - это zip-архивы, но мы не будем их изучать.

Оценка файлов кода

Используя DiE, Hiew или любой другой инструмент PE, мы обнаруживаем, что исполняемые файлы имеют формат NE, то есть новые исполняемые 16-битные программы Windows. Многие из них имеют расширение файла VBX, и это Visual Basic, как можно догадаться по содержанию. Большинство DLL также написаны на этом языке.

Кроме того, некоторые библиотеки DLL и VBX были сжаты упаковщиком, который не может быть идентифицирован никаким инструментом PE / NE.

Поиск в Google показал, что это Shrinker, древний скомпилированный в Visual Basic 3.0 упаковщик файлов NE. Поиск распаковщика (NED 2.30) среди мертвых ссылок был большим успехом, потому что я не хотел тратить время на хлопоты статической распаковки или отладки NTVDM (что мне все же пришлось сделать, но по другой причине).

Мы оценили данные и код, с которым имеем дело - это наследие. Обобщим выполненные действия в виде основного подхода.

[2.2] Выполните первичный анализ данных и кода, с которыми вы работаете. Найдите в Интернете максимум информации по ключевым словам, которые вы нашли во время экзамена.

Поиск возможностей повторного использования кода

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

Мы применяем эту технику к нашей задаче и смотрим на экспорт во всех библиотеках. Практически в каждом из них мы находим много интересного, несмотря на неизвестную терминологию баз данных (CODESTR, DMC, NISSANSECNUM, TMCSECNUM и т. Д.).

[2.3] Оцените программные модули на предмет потенциальной возможности повторного использования кода. Используйте этот подход периодически по мере продвижения в обратном проектировании базы данных, поскольку обычно не всегда ясно, что можно использовать повторно, а что нет.

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

Иногда я встречаю программное обеспечение, написанное для простых в декомпиляции платформ, таких как Java и .NET. Был только один случай, когда существовал удобный интерфейс базы данных, во всех остальных случаях мне приходилось игнорировать исходные (декомпилированные) источники, которые были слишком большими и трудными для понимания в разумные сроки. Это те случаи, когда реконструировать проще, чем читать исходный код.

[2.4] Сложность повторного использования кода прямо пропорциональна количеству черных ящиков и обратно пропорциональна количеству выполняемых ими действий.

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

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

Выбор точки входа

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

Первый набор данных, который будет обработан в нашей задаче, - это информация об автомобиле. Более того, мы знаем, что программа отображает меню выбора транспортного средства при запуске, из которого можно сделать вывод, что транспортные средства являются исходной точкой входа; думайте об этом как о OEP базы данных. Исходя из этого, мы делаем предположение, что «транспортные средства» не зависят от других структур и являются правильным выбором EP.

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

  1. Начните с нужной точки в надежде, что знание предыдущей структуры точек не понадобится. Есть вероятность, что позже это не позволит понять сложные структуры. Идите по этому пути только тогда, когда вы знакомы с предметной областью и / или имеете опыт RE базы данных.
  2. Исследуйте дерево от корня до нужной точки. Это стабильный, но более трудоемкий способ. Я всегда выбираю это.

[2.5] Выберите точку входа в базу данных. Чем ближе он к исходной точке входа, тем меньше структур вам придется исследовать заранее. В идеале, если они совпадают.

Как мы можем убедиться, что EP выбран правильно? Я таких методов не знаю, кроме интуитивно-опытных. Продолжайте реконструировать, и если вы окажетесь в тупике, попробуйте найти другие способы. В крайнем случае используйте дизассемблер.

Разведка точки входа

После выбора EP вам необходимо найти файл или файлы, содержащие необходимые данные, в нашем случае это информация об автомобиле. Я делаю это, выбирая несколько ключевых слов и ищу их во всех файлах. Ключевые слова - это строки, которые точно характеризуют данные, которые вы ищете. Например, известно, что существует автомобиль с названием «Bob Cat ML 1980», в раскрывающемся меню «Двигатель» есть строка «2.3 L OHC» и т. Д.

При поиске строк следует помнить несколько вещей.

  1. Они могут быть в разных кодировках, обычно ASCII или UTF-16. С этим легко справиться с помощью Total Commander, который позволяет вам искать сразу в нескольких кодировках (возможно, Far Manager тоже может это делать).
  2. Их можно хранить в нестандартном виде, сжимать или сохранять в сжатых файлах.

Строка Bob Cat ML 1980 была найдена только в одном файле - FNAc.dat.

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

[2.6] Выясните, какие файлы представляют собой точку входа в базу данных.

Обратный инжиниринг точки входа

Определение базы данных было дано в первой части, и пора его вспомнить. База данных - это набор двоичных файлов, которые содержат структурированные данные и перекрестные ссылки друг на друга. Теперь нас интересует первая часть определения, а именно утверждение, что данные в файлах структурированы. Свойство структуры означает, что файл содержит записи одного формата. Например, одна запись в каталоге будет описывать одно транспортное средство. Следовательно, формат всех записей определяется автоматически после того, как мы определяем формат одной записи. Другими словами, достаточно исследовать значение байтов, относящихся к Bob Cat ML 1980 на скриншоте выше, чтобы понять значение байтов для всех других транспортных средств. Но в действительности файлы редко состоят из записей одного типа; Как правило, они состоят из нескольких таблиц, содержащих записи разных форматов.

Как реконструировать формат записи и формат файла в целом? Здесь применимы все методы обратного проектирования форматов файлов (вспомните иерархию уровней абстракции из первой статьи, я поместил базу данных RE наверху). Они известны давно (см. Ссылки в конце статьи), и я буду использовать их ниже, но не буду разделять их как основные подходы.

Первый шаг во время исследования файла - попытаться определить количество таблиц в файле. Это делается путем визуального поиска соседних записей, формат которых отличается. В качестве примера возьмем файл grpinc.dat. Посмотрите на приведенные ниже снимки экрана, на которых отчетливо виден контраст между двумя форматами записи.

Четыре цветные записи могут быть связаны, скажем, с таблицей 1, за которой следует сплошной текст, связанный, например, с таблицей 2. Каждая запись таблицы 1 имеет размер 0x3A. Размер записей таблицы 2 неизвестен, и не факт, что весь текст не является одной большой записью. Можно сделать вывод, что файл grpinc.dat имеет две таблицы, т.е. имеет два разных формата записи. Это простой пример. Иногда встречаются бинарные файлы с высокой энтропией, переход которых из одной таблицы в другую можно определить только по исчезновению / появлению некоторого байтового паттерна. Тем не менее, они перестают быть проблемой для наведенного глаза.

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

На следующем шаге мы должны определить размер отдельной записи. Для этого нужно найти узор и вычислить разницу между двумя соседними узорами. Взгляните на пример. Давайте поищем в начале каталога и подумаем, какая информация повторяется снова и снова. Я говорю о значении информации, а не о точно повторяющихся байтах. Повторяющаяся информация - это названия транспортных средств, строки, начинающиеся с «! CAT», и строки, начинающиеся с «VL». Это три разных шаблона. Возьмите один из них - образец названия автомобиля - и вычислите разницу между первым и вторым названиями автомобиля.

Разница составляет 0x800–0x0 = 0x800. Таким образом, размер записи каталога составляет 0x800 байт. Это важный метод RE для файлового формата, и его следует освоить (впрочем, как и другие). В качестве дополнения нет необходимости брать первые две или последние две записи для вычисления разницы между шаблонами. Вы не знаете, с какого байта начинаются записи, когда собираетесь определить размер записей, находящихся в середине файла, но все же вычислите дельту без проблем.

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

meta:
  id: catalogue
  file-extension: dat
  endian: le
  encoding: ascii
seq:
  - id: vehicle
    type: vehicle
    repeat: eos
types:
  vehicle:
    seq:
      - id: unk_1
        size: 0x800

Визуализатор Kaitai Struct с именем ksv отображает это следующим образом.

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

Разберем каждое поле. Первые 0x64 байта от начала записи до последнего символа пробела называются vehicle_name_1, потому что это имя автомобиля. Следующие два байта (0x0026), вероятно, будут отдельным полем, но его значение в настоящее время неизвестно, поэтому мы назвали его unk_1. Как определить, является ли это отдельным полем, или эти два байта принадлежат предыдущей строке или могут быть даже следующей строке? Нет никакого серебряного ответа, мы должны сделать предположения и использовать аналогию: если все строки заканчиваются символом пробела (0x20) и без NULL-байта, то первая строка вряд ли является исключением. Кроме того, эти два байта могут представлять собой поле длины для второй строки, но, во-первых, его длина равна 0x64 (от «Aerostar» до «VL38!»), А, во-вторых, это снова похоже на исключение, что вызывает сомнения. Проверяя предположение на других записях (помните, мы сейчас говорим о первом), мы убеждаемся, что оно действительно для них.

За полем unk_1 следует транспортное средство_name_2, поскольку его содержимое является копией транспортного средства_name_1. Следующие поля - это строки vl_code_1, cat_code и vl_code_2, названные по первым буквам их значений. Первые два поля имеют размер 0x16 байт. vl_code_2 может иметь размер 0x1A2 байтов до следующей строки «MX», но давайте не будем торопиться и установим его размер на 0x16.

Дело в том, что поле vl_code_2 во многих записях имеет то же значение, что и vl_code_1, и было бы справедливо предположить, что их длина такая же. С другой стороны, байты от смещения vl_code_2 + 0x16 до строки «MX» равны 0x20 (пробелы) во всех записях, поэтому они могут быть включены в vl_code_2, но для ясности мы разделим их в поле empty_1.

Следующее предполагаемое поле от строки «MX» до строки «A» имеет размер 0xD2, но давайте не будем снова торопиться и смотреть его значение в других записях.

Оказывается, это массив строк по 0xA байтов каждая, а не одна строка. Разделите 0xD2 на 0xA и получите 0x15 - количество записей в массиве. Их значение пока не ясно, хотя некоторые из них выглядят как коды стран (США, CA) или штатов (CA, MX), поэтому назовем поле unk_2.

Далее следует строка «A», размер которой можно определить аналогично, как до последнего байта 0x20. Проделав то же самое, что и для предыдущего поля, мы обнаружим, что это также массив из 0x9 записей длиной 0xC каждая, назовем его unk_3.

Смысл остальных байтов менее ясен, поскольку они не являются строками.

Первое, что мы видим, это два слова (или двойные слова) в разных местах, где первое значение - 0x7C2, а второе - 0x7CB. Со временем вы начнете замечать такие числа, потому что они выглядят как числа года, когда вы переводите их в десятичную форму: 0x7C2 = 1986, 0x7CB = 1995. Помните, что vehicle_name_1 = «Aerostar A 1986–1995», чтобы убедиться, что эти числа начинаются производственный год и год окончания производства, поэтому мы называем их year_from и year_to.

Байты между year_from и year_to обнуляются во всех записях, поэтому мы разделяем их на поле zero_1 размером 0x28. Такое же количество обнуленных байтов находится после year_to, давайте также разделим их на zero_2. Назначение остальных байтов в настоящее время неизвестно, поэтому мы называем файл unk_4.

И последнее. Внимательно пройдя через несколько записей и отметив поля unk_1 и vl_code_1, vl_code_2 и cat_code, мы можем увидеть, что числовое значение unk_1 присутствует в этих полях в символьной форме. Можно было догадаться, что unk_1 - это идентификатор транспортного средства, поэтому мы переименовали его в vehicle_id. В итоге формат файла теперь следующий.

meta:
  id: catalogue
  file-extension: dat
  endian: le
  encoding: ascii
seq:
  - id: vehicle
    type: vehicle
    size: 0x800
    repeat: eos
types:
  vehicle:
    seq:
      - id: vehicle_name_1
        type: str
        size: 0x64
      - id: vehicle_id
        type: u2le
      - id: vehicle_name_2
        type: str
        size: 0x64
      - id: vl_code_1
        type: str
        size: 0x16
      - id: cat_code
        type: str
        size: 0x16
      - id: vl_code_2
        type: str
        size: 0x16
      - id: empty_1
        type: str
        size: 0x18C
      - id: unk_2
        type: str
        size: 0xA
        repeat: expr
        repeat-expr: 0x15
      - id: unk_3
        type: str
        size: 0xC
        repeat: expr
        repeat-expr: 9
      - id: year_from
        type: u2le
      - id: zero_1
        size: 0x28
      - id: year_to
        type: u2le
      - id: zero_2
        size: 0x28
      - id: unk_4
        size-eos: true

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

[2.7] Найдите и опишите формат файла, представляющий точку входа в базу данных.

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

Перекрестные ссылки исследования

Мы можем продолжить обратный инжиниринг несколькими способами:

  1. искать нужную информацию по известной;
  2. поискать нужную информацию и понять, как она взаимосвязана с известной.

Первый способ - взять значения полей, таких как vehicle_id, vl_code_1, vl_code_2, cat_code, и искать их в других файлах. Если что-то будет найдено, это может сигнализировать о возможной взаимосвязи между каталогом и файлом. Смысл второго способа заключается в поиске по ключевым словам, использовании ключевых слов для поиска файла, содержащего требуемую информацию, для его реинжиниринга и выявления перекрестных ссылок между файлом и каталогом. Вы можете рассматривать эти два пути как векторы, направленные друг к другу. Это два способа достижения одной и той же цели.

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

После выбора автомобиля появляется список разделов («10 КОЛЕС И СООТВЕТСТВУЮЩИЕ ДЕТАЛИ», «20 ТОРМОЗНАЯ СИСТЕМА» и т. Д.). При выборе одного из них появляется второй список секций («10 КОЛЕС И КОЛЕСНЫЕ КОЛЕСА КОЛЕСА 1980–1980 [P10853]» и т. Д.). После выбора записи отображается список деталей с гиперссылками, названными в виде номеров деталей, по которым можно щелкнуть, чтобы открыть нижнее меню, содержащее список деталей. В настоящее время наша цель - «дотянуться» до списков деталей.

Как видите, автомобили и детали связаны через два списка разделов, которые мы будем называть деревом деталей. Дерево деталей имеет два уровня: «Альфа-индекс - основной» и «Альфа-индекс - второстепенный», которые для простоты будут называться первым и вторым уровнями. Основываясь на опыте изучения других баз данных транспортных средств, я делаю предположение, что дерево деталей и сами детали находятся либо в разных структурах данных, либо в разных файлах. Это означает, что мы должны исправить цель: мы переходим от транспортных средств к первому уровню дерева, затем ко второму уровню и, наконец, к частям.

Как достичь этой цели? Если вы выполните поиск по именам первого уровня («10 КОЛЕС И СВЯЗАННЫЕ ЧАСТИ»), вы получите только одно совпадение в файле FEULex.dat, содержащем все строки локализованной программы. Затем мы могли бы перейти с этого момента, взяв смещение к строке и снова выполнив поиск четырех байтов смещения в файлах с прямым порядком байтов - это эффективный метод, но он не работает в нашем случае, потому что на FEULex.dat ссылаются смещения, а не смещения. с начала файла.

Пришло время применить другой подход, который настолько эффективен, насколько прост. Он основан на подходе [2.1], который утверждает, что имеет смысл рассматривать программу как СУБД. Поскольку это СУБД, он обращается к БД, т.е. читает из файлов. Поскольку СУБД должна быть эффективной, она не считывает все файлы в память при запуске. Таким образом, доступ к файлам происходит, когда пользователь выполняет соответствующие запросы к программе, нажимая на определенные меню, кнопки и так далее. Следовательно, мы можем определить, к каким файлам обращается программа в момент действия пользователя, то есть выбрать подмножество файлов, содержащих требуемые данные, из набора всех файлов.

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

Итак, мы собираемся выяснить, какой файл представляет собой первый уровень дерева частей. Давайте запустим ProcMon, установим два фильтра: «Имя процесса - ntvdm.exe» и «Операция - ReadFile» и остановим монитор. Запустите программу, выберите автомобиль, снова запустите мониторинг с помощью ProcMon и нажмите кнопку загрузки данных автомобиля.

Бинго! При чтении первого уровня дерева частей программа ссылается только на несколько файлов: MCData.idx, MCData.dat, XGROUPS.idx, FEULex.dat, LexInd.idx. Последние два относятся к локализованным строкам, как было сказано ранее. XGROUPS.idx выглядит неинтересно и не содержит никаких намеков на деревья частей. Первые два - это единственное, что мы ищем.

[2.8] Отслеживайте доступ к файлам, когда программа собирается загружать интересующие вас данные.

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

Name: MCData.idx
Size: 5,5 MB
Format: binary
Number of tables: 4 (2 headers + 2 data tables)
Purpose: binds a vehicle (vehicle_id) to the first part tree level (ai_major_id), the first level to the second level (ai_minor_id), the second level to a part list (part_list_offset) in MCData.dat and to part diagrams (image_offset) in MCImage.dat and MCImage2.dat
Name: MCData.dat
Size: 1,5 GB
Format: encrypted text
Purpose: consists of a part list at part_list_offset for each tree

Я должен упомянуть вторую производную от подхода [2.1]. Мониторинг с использованием ProcMon показывает еще две вещи, которые помогут вам лучше понять внутреннее устройство файлов:

  1. смещение блока и размер блока, считываемые программой одновременно;
  2. порядок чтения блока.

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

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

Здесь мы видим множество «хаотичных» чтений из LexInd.idx, за которыми следует одно «точное» чтение из FEULex.dat. Если мы выясним, почему программа выполняет эти чтения (действие пользователя), и сравним последнее чтение из LexInd.idx с другими чтениями, то станет ясно, что это бинарный поиск. В этом случае программа ищет идентификатор строки в LexInd.idx, после чего считывает смещение строки и считывает строку из FEULex.dat. Понимание алгоритма позволило мне быстрее исследовать эти два файла.

[2.9] Используйте сведения о смещениях блоков, размерах блоков и порядках чтения блоков, собранные во время мониторинга, для исследования файлов.

Результаты

Пришло время подвести итоги наших выводов. Мы выяснили, какие файлы содержат структуры транспортных средств, дерево деталей и детали, как они структурированы и каковы перекрестные ссылки между ними. Давайте визуализируем это.

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

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

(К русской версии статьи есть два приложения, это архив картинок, использованных в этой части, и распаковщик NED 2.30.)

использованная литература

Список основных подходов

Первый подход к обратному проектированию базы данных

  • [2.1] Рассматривайте программу, работающую с БД, как СУБД.

Первичный анализ

  • [2.2] Выполните первичный анализ данных и кода, с которыми вы работаете. Найдите в Интернете максимум информации по ключевым словам, которые вы нашли во время экзамена.
  • [2.3] Оцените программные модули на предмет потенциальной возможности повторного использования кода. Используйте этот подход периодически по мере продвижения в обратном проектировании базы данных, поскольку обычно не всегда ясно, что можно использовать повторно, а что нет.
  • [2.4] Сложность повторного использования кода прямо пропорциональна количеству черных ящиков и обратно пропорциональна количеству выполняемых ими действий.

Исследование точки входа

  • [2.5] Выберите точку входа в базу данных. Чем ближе он к исходной точке входа, тем меньше структур вам придется исследовать заранее. В идеале, если они совпадают.
  • [2.6] Выясните, какие файлы представляют собой точку входа в базу данных.
  • [2.7] Найдите и опишите формат файла, представляющий точку входа в базу данных.

Перекрестные ссылки исследования

  • [2.8] Отслеживайте доступ к файлам, когда программа собирается загружать интересующие вас данные.
  • [2.9] Используйте сведения о смещениях блоков, размерах блоков и порядках чтения блоков, собранные во время мониторинга, для исследования файлов.

Отказ от ответственности

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