• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья ASM – Тёмная сторона РЕ-файла

Исполняемым файлом в системах класса Win является "Рortable-Еxecutable", или просто РЕ. Инженеры Microsoft потрудились на славу, чтобы предоставить нам спецификацию на него, однако некоторые "тузы в рукавах" они всё-таки припрятали. В данной статье рассматривается недокументированная часть РЕ-файла, ознакомившись с которой можно будет собирать информацию о процессе создания файлов компилятором. Антивирусные компании уже давно практикуют эту фишку своих сканерах, а для широкого круга общественности её так и не предали огласке.

darkside_pe.png

Под катом:

1. Общие сведения;
2. Структура РЕ-файла;
3. Заголовок Rich;
4. Практика – парсим хидеры;
5. Область применения знаний;
6. Заключение.



1. Общие сведения

Формат РЕ появился с выходом в свет WinNT-3.1. Защищённый режим и виртуальная память требовали определённой стратегии распределения ресурсов – эту задачу как-раз и возложили на сам исполняемый РЕ-файл. В поисках нирваны, инженерам пришлось координально изменить подход к загрузке образов в ОЗУ. Если раньше обстановка в системе была статична без каких-либо эксцессов, то теперь, получив в своё распоряжение неограниченный объём памяти, всякое приложение на радостях так и старалось всю её поиметь.

Дабы ограничить аппетиты, было решено внедрить в исполняемый файл небольшой заголовок, и связать его с загрузчиком образов. Теперь файл сам сообщает, сколько ему требуется памяти для производственных нужд, а система её предоставляет. Нужно отметить, что это было единственно-верным решением в сложившейся ситуации, иначе великие умы уже давно придумали-бы что-нибудь свежее. Но видимо слабо.. РЕ родился в 1993-году и дошёл до нас в своём первозданном виде! Если и применялись какие-то моды, то исключительно на стороне системного загрузчика, а не самого файла. Здесь нужно снять шляпу перед разработчиком формата Марком Збиковски, с инициалов "MZ" которого начинаются все исполняемые файлы. Новоиспечённый формат точили и шлифовали ещё пару лет, так-что первая его спецификация датируется 1995-годом, как дополнение к системе Win-95.

Однако позже, любопытными программистами в РЕ был обнаружен нигде не регламентируемый блок информации. Причём в некоторых файлах он присутствовал, а в некоторых нет, т.е. появлялся как призрак. Microsoft крепко держала оборону на этот счёт, и все вопросы тупо игнорировала. В конечном итоге получилось так, что подобным молчанием она выстрелила себе в ногу, поскольку "тёмные силы" не заставили себя ждать и раскурив весь план стали активно его применять в своих целях. Что примечательно, этот блок и по сей день остаётся в тени – системный протекторат посчитал оставить всё как-есть, и не включать его в спеку.

Как выяснилось спустя много лет, данный блок был включён в РЕ-файл по требованию штатных сотрудников Microsoft, которые занимались в закрытых кабинетах реверс-инженерингом уже скомпилированного, бинарного софта. Они часто сталкивались с дилемой, какой именно версией компилятора C++ был создан исследуемый файл, и сколько было исходных модулей с кодом (в больших проектах может быть более сотни отдельных файлов, которые связываются в цепочку посредством include).

Задачу возложили на двух инженеров – это Дэн Сполдинг и Ричард Шупак, которые работали в то время над компилятором Visual C++ и базой кода системных библиотек. Не заморачиваясь, инженеры тупо включили в линкер LINK.EXE опцию ведения лога о проделанной работе, с сохранением его в специально отведённый для этих целей заголовок РЕ-файла. Формат лога состоит всего из трёх полей: Product-ID в лице версии компилятора (VC, VB, MASM, Phx), номер его сборки Build, а так-же счётчика Counter скомпилированных исходных файлов, которые входили в состав проекта. Пример такого блока представлен ниже:


R_0.png


В документации на PE/COFF от Microsoft нет ни единого упоминания об этом блоке, а сразу после заглушки DOS указывается РЕ-Header. В качестве маркера окончания блока, Шупак вставил дворд своего имени "Rich", а с инициалов второго автора Дэна блок начинается, просто здесь они накрыты ксором и не видны. После расшифровки получим сигнатуру "DanS". Позже мы напишем приложение, которое покажет всю информацию из этого заголовка. В конечном итоге конкретно для данного случая (софт PEiD), результат будет таким где видно, что по всей вероятности проект собирался в 15-ой студии, которая комплектуется линкером версии(9). Особый интерес представляют и значения в столбце "Count" – это счётчик исходных файлов:

Scr_0.png


На данный момент, в базе "Product-ID" имеется 0x10E=270 опознанных объектов, которые я собрал в свой инклуд (см.скрепку в подвале статьи). Поскольку Microsoft их не документировала (хотя сама активно юзает), энтузиастам всё-же удалось раскопать эти коды в файле "msobj140-msvcrt.lib" студий начиная с 2015, и при помощи скрипта на питоне вытащить их от туда в приглядном виде. Более продвинутые мемберы обзавелись ими ещё раньше, вскрыв как консервную банку линкер LINK.EXE, с последующим поиском алиасов имён экспериментально. Так появился софт, который с точностью определяет среду разработки, типа тот-же PeiD, DiE и прочие.

С Rich-заголовком связан один нюанс. Поскольку мягкие так и не обнародовали его, то он присутствует только в исполняемых файлах, которые собирались в продуктах самой Microsoft, с использованием линкера LINK.EXE, а это как-правило: Visual Studio, Visual Basic и MASM. Если-же бинарь компилировали в Delphi, Pascal, Go и прочих, то хидер Rich искать в нём бесполезно – его там попросту нет. В палату кастрированных попадает и ассемблер FASM, т.к. у него свой линкер и компилятор.

Но судя по статистике, доля продуктов Microsoft на рынке двоичных файлов составляет порядка 80% от общего числа, так-что Rich в природе частое явление. Под эгидой РЕ ходят такие расширения как: *.exe (dll, sys, drv, mui, scr, efi, ocx, bpl, dpl, cpl, acm, ax), а значит вся/эта братия будет представляться нашим потенциальным клиентом.


2. Структура РЕ-файла

Перед тем как рассмотреть формат заголовка Rich, в общих чертах вспомним структуры официального РЕ-файла. Для этого напишем обычный "HelloWorld" и вскормим его любому HEX-редактору.


PE_0.png


Бинари всех исполняемых файлов Win должны начинаться с проприетарного заголовка DOS с сигнатурой "MZ" (привет Марк Збиковски). Создающие файл линкеры выделяют под него 0x80 = 128-байт, из которых системный загрузчик образов проверяет только два поля – это непосредственно сигнатура по смещению(00), и т.н. поле e_lfanew по смещению(3Сh) от начала, где прописывается указатель на РЕ-заголовок (красная стрелка). В данном случае там лежит значение 0x00000080, хотя это не константа и в других файлах офсет может быть иным.

Все остальные поля хидера DOS (включая стаб) давно атрофированы и никому не нужны, так-что мы можем смело забивать их нулями, или в целях уменьшения размера вообще сместить весь файл (начиная с РЕ-хидера) вверх, по смещению(08) от его начала, соблюдая выравнивание на 8-байтную границу. Только поле e_lfanew нужно будет оставить, скорректировав его соответственно к смещению(08). Если попросить гугл, то в сети можно найти доку от парней из BlackHat, которые извращаются с РЕ по-чёрному. Они приводят пруфы, что РЕ-заголовок можно переносить в любое место вверх/вниз, но только в пределах 4Gb, поскольку под e_lfanew выделяется 32-битный дворд.

Наиболее информативным для загрузчика образов является РЕ-заголовок, за которым сразу-же следует опциональный. Здесь уже указываются требования файла к системе: размер стека и памяти Heap, предпочтительный базовый адрес загрузки образа в память (в некоторых случаях ASLR его всё-равно меняет), точка-входа в программу для регистра EIP, выравнивание программных секций на диске и в памяти, их кол-во, адреса, и прочее. Как и в случае с заголовком DOS, большинство полей в Optional-Header так-же игнорируются современными загрузчиками, поскольку достались нам в наследство от Win95/98.

В контексте данной статьи, для нас интерес представляет поле "LinkerVersion" в опциональном заголовке. Оно имеет размер 2-байта, в первом из которых указывает версия-мажор линкера, а во-втором минор. К примеру я использую компилятор FASM ver.1.73, тогда в первом байте получаю(1), а во-втором(73). Отметим, что данное поле является документированным, поэтому свой "возраст" сбрасывают в него буквально все линкеры, а не только подопечные Microsoft – запомним его на будущее.

В спойлере ниже спрятана структура PE_HEADER, в которой неиспользуемые поля я оставил без комментов, хотя по именам всегда можно догадаться об их назначении. Для 64-битных версий исполняемых файлов структура идентичная, за исключением всех полей с адресами – их нужно расширить до 8-байт qword:


C-подобный:
;// DWORD-указатель на РЕ-заголовок,
;// лежит по смещению 3Ch от начала файла MZ-Header

struct PE_HEADER
  Signature          dd  0   ;// 50450000 = 'PE..'
  Machine            dw  0   ;// 0x014C = Intel386, 0x8664 = AMD64
  NumberOfSection    dw  0   ;// кол-во секций в файле
  TimeStamp          dd  0
  SymbolPointer      dd  0
  SymbolSize         dd  0
  OptHeaderSize      dw  0   ;// 00E0 = размер "OPTIONAL HEADER"
  Flags              dw  0
;//--- OPTIONAL HEADER -------------------
  Magic              dw  0   ;// Offset 18h = 010B
  LinkerVersion      dw  0   ;//<-------------------- Наш клиент!
  SizeOfCode         dd  0
  SizeOfInitData     dd  0
  SizeOfUnInitData   dd  0
  EntryPointRVA      dd  0   ;// точка-входа
  CodeBaseRVA        dd  0   ;//
  DataBaseRVA        dd  0   ;//
  ImageBase          dd  0   ;// база в памяти
  SectionAlign       dd  0   ;// выравнивание секций в памяти
  FileAlign          dd  0   ;// выравнивание секций на диске
  OsVersion          dd  0
  ImageVersion       dd  0
  SubSysVersion      dd  0   ;// 4
  Reserved           dd  0
  ImageSize          dd  0   ;//
  HeaderSize         dd  0   ;//
  FileChecksum       dd  0
  SubSystem          dw  0   ;//
  DLLflags           dw  0   ;//
  StackReserve       dd  0
  StackCommit        dd  0   ;// требуемый размер стека
  HeapReserve        dd  0   ;// требуемая память хип
  HeapCommit         dd  0
  LoaderFlag         dd  0
  NumberDataDir      dd  0     ;// кол-во элементов в каталоге
;//--- DATA DIRECTORY --------------------
  ExportRVA          dd  0,0   ;// первый дворд = адрес в памяти, второй = размер данных
  ImportRVA          dd  0,0   ;//
  ResourceRVA        dd  0,0   ;//
  ExceptionRVA       dd  0,0
  SecurityRVA        dd  0,0
  FixUpRVA           dd  0,0
  DebugRVA           dd  0,0
  ArchitectureRVA    dd  0,0
  GlobalRVA          dd  0,0
  TlsRVA             dd  0,0
  LoadConfigRVA      dd  0,0
  BoundImportRVA     dd  0,0
  IatRVA             dd  0,0
  DelayImportRVA     dd  0,0
  COMdescriptor      dd  0,0
  Reserved1          dd  0,0
ends

В опциональном заголовке прописались ещё два интересных мембера. Первый под кличкой "SectionAlignment" сообщает системному диспетчеру-памяти размер выравнивания секций. По умолчанию все линкеры определяют его как 4Кб страницы, и лучше оставить это поле без изменений. А вот второй "FileAlignment" определяет уже размер секций образа файла на диске. Экспериментальным путём удалось определить, что диапазон этого поля варьируется от 16-байт до 64К, а в дефолте все пихают туда 512. Уменьшив это выравнивание в 2 или 4-раза, можно сжать дисковый файл, чтобы он занимал на харде меньше места. В практической части мы ещё вернемся к нему.


3. Заголовок "Rich"

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

1. Трансляция. На данном этапе, препроцессор компилятора вычисляет адреса всех меток и переходов в исходнике. Часто бывает так, что адреса и переменные напрямую зависят друг от друга. То-есть пока не вычислим первый адрес, не получим второй. Поэтому трансляция повторяется циклически как-минимум два раза. Такие трансляторы называют двух/трёх-проходной. Результат вычислений сохраняется в специальной таблице. Когда определились с адресами, переходим к сл.этапу..

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

3. Линковка (компоновка). Это последний этап создания исполняемого файла. Процедура занимается тем, что собирает в единое целое все объектные файлы проекта, связывая их друг-с-другом, и помещая в один флакон EXE/DLL/SYS. Этим как-раз и озадачен линкер среды разработки. Результатом данной операции является готовый к исполнению файл.

Суть внедрения в РЕ-файл заголовка "Rich" заключается в том, чтобы линкер запоминал в нём каждый свой шаг. Такой алгоритм способствует не только последующему реверсу софта, но и даёт возможность контролировать целостность файлов. Например если к нам попадёт подозрительный системный файл, можно будет вычислить, каким именно инструментарием он был создан. Кстати в своё время, хакерская группировка Lazarus именно на этом, когда подсунула созданный в кулуарах файл, выдавая его за системный. Правда своего они всё-же добились, но оставили за собой след.

Таким образом, Rich-заголовок динамически расширяется в зависимости от того, сколько логов сбросил в него линкер. Каждый лог имеет размер 8-байт и хранит три составляющие – это уже упомянутые: Product-ID(2), Build(2) и Counter(4). В скобках здесь я указал длину поля в байтах.

Маркером начала заголовка служит 4-байтная строка "DanS", а маркером окончания "Rich". Теперь весь полученный блок шифруется обычным XOR, а ключом для ксора является контрольная сумма блока + содержимое MZ-заголовка (при этом поле e_lfanew в MZ сбрасывается в нуль). Другими словами, чтобы получить ключ-шифрования, линкер складывает все дворды начиная от самого начала файла по смещению(0), и вплоть до маркера окончания "Rich". Полученная сумма сохраняется сразу-же за сигнатурой "Rich". Как результат получаем такую картину:


R_xor.png


Значит чтобы расшифровать Rich-заголовок, мы находим маркер-окончания "Rich", сразу после которого будет лежать ключ для операции XOR. В данном случае ключ имеет значение 0x4BB35FE4. Теперь ставим обратный шаг инструкцией ассемблера STD (что подразумевает Set Direction, вкл.обратное направление) и начиная с маркера "Rich" приступаем к декрипту. После расшифровки каждого дворда, нужно проверять полученные значения на маркер-начала "DanS". То-есть расшифровывать нужно от конца к началу блока. Результат этого процесса представлен в нижнем окне скрина, который снят с отладчика.

Как видим, после сигнатуры "DanS" ровным строем до конца строки, идут три дворда нулей – это выравнивание и присутствует оно всегда. Поскольку 0 xor Key на выходе даёт Key, то ключ для расшифровки можно брать не с конца, а с любого из двордов 2,3,4 от начала блока. Если посмотреть на верхнее окно и вправду, там лежит этот-же ключ.

Непосредственно 8-байтные логи линкера начинаются со-второй строки дампа (см.нижнее окно). В данном случае, первый дворд лога(0) имеет значение 0x00964FBD, где 0x0096 будет олицетворять поле "Product-ID", а 0x4FBD это "Build=20413". Следующий дворд 0x00000003 заканчивает лог(0), и знаменует счётчик операций для данного продукта. Аналогичным образом вытаскиваем и остальные 8-байтные логи, общее число которых в данном случае 10.

Ладно.., логи мы научились добывать. Но как привести их в более дружелюбный читабельный вид?
Оказалось, что гугл знаком с этой проблемой и на запрос любезно предоставил мне файл ниже, где питон постарался и сформировал из файла библиотеки "msobj140-msvcrt.lib" лист такого содержания. Остаётся лишь оформить эти строки в виде таблицы, и можно выводить на консоль привязанные к кодам объекты. Так-же мне попалась , с подробным описанием соответствий. В общем есть из чего выбирать:


Python:
#!/usr/bin/env python3

int_names = {
      0x0000: 'prodidUnknown',
    0x0001: 'prodidImport0',
    0x0002: 'prodidLinker510',
    0x0003: 'prodidCvtomf510',
    0x0004: 'prodidLinker600',
    0x0005: 'prodidCvtomf600',
    0x0006: 'prodidCvtres500',
    0x0007: 'prodidUtc11_Basic',
    0x0008: 'prodidUtc11_C',
    0x0009: 'prodidUtc12_Basic',
    0x000a: 'prodidUtc12_C',
    0x000b: 'prodidUtc12_CPP',
    0x000c: 'prodidAliasObj60',
    0x000d: 'prodidVisualBasic60',
    0x000e: 'prodidMasm613',
    0x000f: 'prodidMasm710',
    0x0010: 'prodidLinker511',
    0x0011: 'prodidCvtomf511',
    0x0012: 'prodidMasm614',
    0x0013: 'prodidLinker512',
    0x0014: 'prodidCvtomf512',
    0x0015: 'prodidUtc12_C_Std',
    0x0016: 'prodidUtc12_CPP_Std',
    0x0017: 'prodidUtc12_C_Book',
    0x0018: 'prodidUtc12_CPP_Book',
    0x0019: 'prodidImplib700',
    0x001a: 'prodidCvtomf700',
    0x001b: 'prodidUtc13_Basic',
    0x001c: 'prodidUtc13_C',
    0x001d: 'prodidUtc13_CPP',
    0x001e: 'prodidLinker610',
    0x001f: 'prodidCvtomf610',
    0x0020: 'prodidLinker601',
    0x0021: 'prodidCvtomf601',
    0x0022: 'prodidUtc12_1_Basic',
    0x0023: 'prodidUtc12_1_C',
    0x0024: 'prodidUtc12_1_CPP',
    0x0025: 'prodidLinker620',
    0x0026: 'prodidCvtomf620',
    0x0027: 'prodidAliasObj70',
    0x0028: 'prodidLinker621',
    0x0029: 'prodidCvtomf621',
    0x002a: 'prodidMasm615',
    0x002b: 'prodidUtc13_LTCG_C',
    0x002c: 'prodidUtc13_LTCG_CPP',
    0x002d: 'prodidMasm620',
    0x002e: 'prodidILAsm100',
    0x002f: 'prodidUtc12_2_Basic',
    0x0030: 'prodidUtc12_2_C',
    0x0031: 'prodidUtc12_2_CPP',
    0x0032: 'prodidUtc12_2_C_Std',
    0x0033: 'prodidUtc12_2_CPP_Std',
    0x0034: 'prodidUtc12_2_C_Book',
    0x0035: 'prodidUtc12_2_CPP_Book',
    0x0036: 'prodidImplib622',
    0x0037: 'prodidCvtomf622',
    0x0038: 'prodidCvtres501',
    0x0039: 'prodidUtc13_C_Std',
    0x003a: 'prodidUtc13_CPP_Std',
    0x003b: 'prodidCvtpgd1300',
    0x003c: 'prodidLinker622',
    0x003d: 'prodidLinker700',
    0x003e: 'prodidExport622',
    0x003f: 'prodidExport700',
    0x0040: 'prodidMasm700',
    0x0041: 'prodidUtc13_POGO_I_C',
    0x0042: 'prodidUtc13_POGO_I_CPP',
    0x0043: 'prodidUtc13_POGO_O_C',
    0x0044: 'prodidUtc13_POGO_O_CPP',
    0x0045: 'prodidCvtres700',
    0x0046: 'prodidCvtres710p',
    0x0047: 'prodidLinker710p',
    0x0048: 'prodidCvtomf710p',
    0x0049: 'prodidExport710p',
    0x004a: 'prodidImplib710p',
    0x004b: 'prodidMasm710p',
    0x004c: 'prodidUtc1310p_C',
    0x004d: 'prodidUtc1310p_CPP',
    0x004e: 'prodidUtc1310p_C_Std',
    0x004f: 'prodidUtc1310p_CPP_Std',
    0x0050: 'prodidUtc1310p_LTCG_C',
    0x0051: 'prodidUtc1310p_LTCG_CPP',
    0x0052: 'prodidUtc1310p_POGO_I_C',
    0x0053: 'prodidUtc1310p_POGO_I_CPP',
    0x0054: 'prodidUtc1310p_POGO_O_C',
    0x0055: 'prodidUtc1310p_POGO_O_CPP',
    0x0056: 'prodidLinker624',
    0x0057: 'prodidCvtomf624',
    0x0058: 'prodidExport624',
    0x0059: 'prodidImplib624',
    0x005a: 'prodidLinker710',
    0x005b: 'prodidCvtomf710',
    0x005c: 'prodidExport710',
    0x005d: 'prodidImplib710',
    0x005e: 'prodidCvtres710',
    0x005f: 'prodidUtc1310_C',
    0x0060: 'prodidUtc1310_CPP',
    0x0061: 'prodidUtc1310_C_Std',
    0x0062: 'prodidUtc1310_CPP_Std',
    0x0063: 'prodidUtc1310_LTCG_C',
    0x0064: 'prodidUtc1310_LTCG_CPP',
    0x0065: 'prodidUtc1310_POGO_I_C',
    0x0066: 'prodidUtc1310_POGO_I_CPP',
    0x0067: 'prodidUtc1310_POGO_O_C',
    0x0068: 'prodidUtc1310_POGO_O_CPP',
    0x0069: 'prodidAliasObj710',
    0x006a: 'prodidAliasObj710p',
    0x006b: 'prodidCvtpgd1310',
    0x006c: 'prodidCvtpgd1310p',
    0x006d: 'prodidUtc1400_C',
    0x006e: 'prodidUtc1400_CPP',
    0x006f: 'prodidUtc1400_C_Std',
    0x0070: 'prodidUtc1400_CPP_Std',
    0x0071: 'prodidUtc1400_LTCG_C',
    0x0072: 'prodidUtc1400_LTCG_CPP',
    0x0073: 'prodidUtc1400_POGO_I_C',
    0x0074: 'prodidUtc1400_POGO_I_CPP',
    0x0075: 'prodidUtc1400_POGO_O_C',
    0x0076: 'prodidUtc1400_POGO_O_CPP',
    0x0077: 'prodidCvtpgd1400',
    0x0078: 'prodidLinker800',
    0x0079: 'prodidCvtomf800',
    0x007a: 'prodidExport800',
    0x007b: 'prodidImplib800',
    0x007c: 'prodidCvtres800',
    0x007d: 'prodidMasm800',
    0x007e: 'prodidAliasObj800',
    0x007f: 'prodidPhoenixPrerelease',
    0x0080: 'prodidUtc1400_CVTCIL_C',
    0x0081: 'prodidUtc1400_CVTCIL_CPP',
    0x0082: 'prodidUtc1400_LTCG_MSIL',
    0x0083: 'prodidUtc1500_C',
    0x0084: 'prodidUtc1500_CPP',
    0x0085: 'prodidUtc1500_C_Std',
    0x0086: 'prodidUtc1500_CPP_Std',
    0x0087: 'prodidUtc1500_CVTCIL_C',
    0x0088: 'prodidUtc1500_CVTCIL_CPP',
    0x0089: 'prodidUtc1500_LTCG_C',
    0x008a: 'prodidUtc1500_LTCG_CPP',
    0x008b: 'prodidUtc1500_LTCG_MSIL',
    0x008c: 'prodidUtc1500_POGO_I_C',
    0x008d: 'prodidUtc1500_POGO_I_CPP',
    0x008e: 'prodidUtc1500_POGO_O_C',
    0x008f: 'prodidUtc1500_POGO_O_CPP',
    0x0090: 'prodidCvtpgd1500',
    0x0091: 'prodidLinker900',
    0x0092: 'prodidExport900',
    0x0093: 'prodidImplib900',
    0x0094: 'prodidCvtres900',
    0x0095: 'prodidMasm900',
    0x0096: 'prodidAliasObj900',
    0x0097: 'prodidResource',
    0x0098: 'prodidAliasObj1000',
    0x0099: 'prodidCvtpgd1600',
    0x009a: 'prodidCvtres1000',
    0x009b: 'prodidExport1000',
    0x009c: 'prodidImplib1000',
    0x009d: 'prodidLinker1000',
    0x009e: 'prodidMasm1000',
    0x009f: 'prodidPhx1600_C',
    0x00a0: 'prodidPhx1600_CPP',
    0x00a1: 'prodidPhx1600_CVTCIL_C',
    0x00a2: 'prodidPhx1600_CVTCIL_CPP',
    0x00a3: 'prodidPhx1600_LTCG_C',
    0x00a4: 'prodidPhx1600_LTCG_CPP',
    0x00a5: 'prodidPhx1600_LTCG_MSIL',
    0x00a6: 'prodidPhx1600_POGO_I_C',
    0x00a7: 'prodidPhx1600_POGO_I_CPP',
    0x00a8: 'prodidPhx1600_POGO_O_C',
    0x00a9: 'prodidPhx1600_POGO_O_CPP',
    0x00aa: 'prodidUtc1600_C',
    0x00ab: 'prodidUtc1600_CPP',
    0x00ac: 'prodidUtc1600_CVTCIL_C',
    0x00ad: 'prodidUtc1600_CVTCIL_CPP',
    0x00ae: 'prodidUtc1600_LTCG_C',
    0x00af: 'prodidUtc1600_LTCG_CPP',
    0x00b0: 'prodidUtc1600_LTCG_MSIL',
    0x00b1: 'prodidUtc1600_POGO_I_C',
    0x00b2: 'prodidUtc1600_POGO_I_CPP',
    0x00b3: 'prodidUtc1600_POGO_O_C',
    0x00b4: 'prodidUtc1600_POGO_O_CPP',
    0x00b5: 'prodidAliasObj1010',
    0x00b6: 'prodidCvtpgd1610',
    0x00b7: 'prodidCvtres1010',
    0x00b8: 'prodidExport1010',
    0x00b9: 'prodidImplib1010',
    0x00ba: 'prodidLinker1010',
    0x00bb: 'prodidMasm1010',
    0x00bc: 'prodidUtc1610_C',
    0x00bd: 'prodidUtc1610_CPP',
    0x00be: 'prodidUtc1610_CVTCIL_C',
    0x00bf: 'prodidUtc1610_CVTCIL_CPP',
    0x00c0: 'prodidUtc1610_LTCG_C',
    0x00c1: 'prodidUtc1610_LTCG_CPP',
    0x00c2: 'prodidUtc1610_LTCG_MSIL',
    0x00c3: 'prodidUtc1610_POGO_I_C',
    0x00c4: 'prodidUtc1610_POGO_I_CPP',
    0x00c5: 'prodidUtc1610_POGO_O_C',
    0x00c6: 'prodidUtc1610_POGO_O_CPP',
    0x00c7: 'prodidAliasObj1100',
    0x00c8: 'prodidCvtpgd1700',
    0x00c9: 'prodidCvtres1100',
    0x00ca: 'prodidExport1100',
    0x00cb: 'prodidImplib1100',
    0x00cc: 'prodidLinker1100',
    0x00cd: 'prodidMasm1100',
    0x00ce: 'prodidUtc1700_C',
    0x00cf: 'prodidUtc1700_CPP',
    0x00d0: 'prodidUtc1700_CVTCIL_C',
    0x00d1: 'prodidUtc1700_CVTCIL_CPP',
    0x00d2: 'prodidUtc1700_LTCG_C',
    0x00d3: 'prodidUtc1700_LTCG_CPP',
    0x00d4: 'prodidUtc1700_LTCG_MSIL',
    0x00d5: 'prodidUtc1700_POGO_I_C',
    0x00d6: 'prodidUtc1700_POGO_I_CPP',
    0x00d7: 'prodidUtc1700_POGO_O_C',
    0x00d8: 'prodidUtc1700_POGO_O_CPP',
    0x00d9: 'prodidAliasObj1200',
    0x00da: 'prodidCvtpgd1800',
    0x00db: 'prodidCvtres1200',
    0x00dc: 'prodidExport1200',
    0x00dd: 'prodidImplib1200',
    0x00de: 'prodidLinker1200',
    0x00df: 'prodidMasm1200',
    0x00e0: 'prodidUtc1800_C',
    0x00e1: 'prodidUtc1800_CPP',
    0x00e2: 'prodidUtc1800_CVTCIL_C',
    0x00d3: 'prodidUtc1800_CVTCIL_CPP',
    0x00e4: 'prodidUtc1800_LTCG_C',
    0x00e5: 'prodidUtc1800_LTCG_CPP',
    0x00e6: 'prodidUtc1800_LTCG_MSIL',
    0x00e7: 'prodidUtc1800_POGO_I_C',
    0x00e8: 'prodidUtc1800_POGO_I_CPP',
    0x00e9: 'prodidUtc1800_POGO_O_C',
    0x00ea: 'prodidUtc1800_POGO_O_CPP',
    0x00eb: 'prodidAliasObj1210',
    0x00ec: 'prodidCvtpgd1810',
    0x00ed: 'prodidCvtres1210',
    0x00ee: 'prodidExport1210',
    0x00ef: 'prodidImplib1210',
    0x00f0: 'prodidLinker1210',
    0x00f1: 'prodidMasm1210',
    0x00f2: 'prodidUtc1810_C',
    0x00f3: 'prodidUtc1810_CPP',
    0x00f4: 'prodidUtc1810_CVTCIL_C',
    0x00f5: 'prodidUtc1810_CVTCIL_CPP',
    0x00f6: 'prodidUtc1810_LTCG_C',
    0x00f7: 'prodidUtc1810_LTCG_CPP',
    0x00f8: 'prodidUtc1810_LTCG_MSIL',
    0x00f9: 'prodidUtc1810_POGO_I_C',
    0x00fa: 'prodidUtc1810_POGO_I_CPP',
    0x00fb: 'prodidUtc1810_POGO_O_C',
    0x00fc: 'prodidUtc1810_POGO_O_CPP',
    0x00fd: 'prodidAliasObj1400',
    0x00fe: 'prodidCvtpgd1900',
    0x00ff: 'prodidCvtres1400',
    0x0100: 'prodidExport1400',
    0x0101: 'prodidImplib1400',
    0x0102: 'prodidLinker1400',
    0x0103: 'prodidMasm1400',
    0x0104: 'prodidUtc1900_C',
    0x0105: 'prodidUtc1900_CPP',
    0x0106: 'prodidUtc1900_CVTCIL_C',
    0x0107: 'prodidUtc1900_CVTCIL_CPP',
    0x0108: 'prodidUtc1900_LTCG_C',
    0x0109: 'prodidUtc1900_LTCG_CPP',
    0x010a: 'prodidUtc1900_LTCG_MSIL',
    0x010b: 'prodidUtc1900_POGO_I_C',
    0x010c: 'prodidUtc1900_POGO_I_CPP',
    0x010d: 'prodidUtc1900_POGO_O_C',
    0x010e: 'prodidUtc1900_POGO_O_CPP',
}

## These are somewhat guessed, don't rely on them 100%
def vs_version(i):
    if i > len(int_names) or i < 0:
        return '<unknown>', 'XX.XX'
    elif i in range(0x00fd, 0x010e + 1):
        return 'Visual Studio 2015', '14.00'
    elif i in range(0x00eb, 0x00fd):
        return 'Visual Studio 2013', '12.10'
    elif i in range(0x00d9, 0x00eb):
        return 'Visual Studio 2013', '12.00'
    elif i in range(0x00c7, 0x00d9):
        return 'Visual Studio 2012', '11.00'
    elif i in range(0x00b5, 0x00c7):
        return 'Visual Studio 2010', '10.10'
    elif i in range(0x0098, 0x00b5): ## TODO: Investigate on Phoenix
        return 'Visual Studio 2010', '10.00'
    elif i in range(0x0083, 0x0098): ## TODO: Investigate on Phoenix
        return 'Visual Studio 2008', '09.00'
    elif i in range(0x006d, 0x0083):
        return 'Visual Studio 2005', '08.00'
    elif i in range(0x005a, 0x006d):
        return 'Visual Studio 2003', '07.10'
    elif i == 1:
        return 'Visual Studio', '00.00'
    else:
        return '<unknown>', '00.00'


4. Практика – парсим хидеры

Под занавес немного попрактикуемся, чтобы заточить навыки.
В своём "Rich-parse" отечественного разлива, я соберу основную информацию из РЕ и опционального заголовка, после чего проверю бинарь на наличие в нём хидера Rich. Если таковой имеется, то взяв ключ расшифрую блок ксором, и выведу на консоль всю полученную информацию. Для этого буду преследовать следующий алгоритм:

1. Функция GetOpenFileName() из библиотеки comdlg32.dll предоставляет стандартное окно системы, для выбора файлов. Она имеет всего один аргумент – указатель на структуру "OPENFILENAME", в которой указывается маска с расширением требуемых файлов (в данном случае exe/sys/dll), а так-же линк на буфер, куда функция сбросит полный путь до выбранного файла.

2. Пакет из трёх функций _lopen/read/close() соответственно открыв читает указанный файл. Мне достаточно только первого килобайта, который сохраняю в свой буфер.

3. Далее идёт тест на присутствие Rich-Header. Получив указатель на РЕ-заголовок по смещению(3Ch) от начала файла, строковой инструкцией LODSD с обратным шагом, осуществляю поиск сигнатуры "Rich", продвигаясь назад от РЕ-заголовка. Между РЕ и Rich, в некоторых файлах встречаются нули, поэтому нужно учитывать их, протопав как-минимум шагов 10.

4. Если обнаруживаю Rich, то первым делом нужно расшифровать весь заголовок, после чего снимаю с него хэш MD5. Он нужен для идентификации файлов, о чём пойдёт речь чуть позже. Хэш получаю при помощи функций из библиотеки Bcrypt.dll, что-да-как уже обсуждалось в статье ASM-CNG.

5. Теперь имеем расшифрованный блок и можно приступать к декодирования данных Product-ID, Build и Counter. Как уже упоминалось, в скрепке лежит инклуд с привязанными к кодам строками. Поскольку коды назначаются последовательно от нуля и до 0x10E (всего 207 штук), то обычным умножением его на 4 можно получить смещение к соответствующей строке. То-есть создаём таблицу, в которой будут лежать последовательные указатели на строки. Далее, когда получим код "Product-ID" из заголовка Rich, он будет соответствовать порядковому номеру указателя в таблице. Юзаем..


C-подобный:
format   pe console
include 'win32ax.inc'
include 'equates\pe.inc'
include 'equates\bcrypt.inc'
entry    start
;//----------
.data
filter    db  '  exe, dll, sys, ocx',0        ;// что видно в окне GetOpenFileName()
          db  '*.exe;*.dll;*.sys;*.ocx',0,0   ;// маска с типами файлов

x32       db  'Intel 386 or later',0
x64       db  'AMD64 (K8+)',0

align 16
ofn          OPENFILENAME
;//<----- данные MD5
md5AlgHndl   dd  0
md5Hndl      dd  0
md5ObjLen    dd  0
pcbResult    dd  0
hashBuff     rb  16
hashObject   rb  256
;//-----------------
hFile        dd  0
startRich    dd  0
endRich      dd  0
key          dd  0
align 16
buff         rb  2048
;//----------
.code
start:  invoke  SetConsoleTitle,<'*** PE-Rich information ***',0>

;//---- Запрашиваем у юзера исполняемый файл
       cinvoke  printf,<10,' Select executable file...',0>
        mov     [ofn.lStructSize],sizeof.OPENFILENAME
        mov     [ofn.lpstrFilter],filter
        mov     [ofn.lpstrFile],buff        ;// сюда получу имя файла
        mov     [ofn.nMaxFile],512
        mov     [ofn.Flags], OFN_EXPLORER
        invoke  GetOpenFileName, ofn        ;// 1 = OK
        or      eax,eax
        jnz     @f
       cinvoke  printf,<10,' ERROR! File not found.',0>
        jmp     @exit

;//---- Показываем его имя, и читаем первые 1024-байта из него
@@:     invoke  CharToOem,buff,buff
       cinvoke  printf,<10,' File: %s',10,0>,buff
        invoke  OemToChar,buff,buff

        invoke  _lopen,buff,0
        push    eax
        invoke  _lread,eax,buff,1024
        pop     eax
        invoke  _lclose,eax

;//---- Показываем основную инфу из РЕ и опционального заголовка
        mov     eax,dword[buff+3ch]
       cinvoke  printf,<10,' Magic..............:  %.2s',\
                        10,' PE-Header offset...:  0x%X',0>,buff,eax
        mov     esi,buff
        add     esi,[esi+3ch]
        movzx   eax,[esi+PE_HEADER.Machine]
        movzx   ebx,[esi+PE_HEADER.NumberOfSection]
        movzx   ecx,[esi+PE_HEADER.LinkerVersion]
        movzx   edx,ch
        and     ecx,0xff
        mov     ebp,x32
        cmp     eax,0x14c
        je      @f
        mov     ebp,x64
@@:    cinvoke  printf,<10,' PE for machine.....:  0x%04X | %s',\
                        10,' Section count......:  %d',\
                        10,' Linker version.....:  %d.%02d',\
                        10,' Image base.........:  0x%08X',\
                        10,' EntryPointRVA......:  0x%08X',\
                        10,' Section alignment..:  %d',\
                        10,' File alignment.....:  %d',\
                        10,' Image size.........:  %d',10,0>,eax,ebp,ebx,ecx,edx,\
                        [esi+PE_HEADER.ImageBase],[esi+PE_HEADER.EntryPointRVA],\
                        [esi+PE_HEADER.SectionAlign],[esi+PE_HEADER.FileAlign],\
                        [esi+PE_HEADER.ImageSize]

;//---- Проверить наличие хидера Rich
;//---- между РЕ и Rich могут быть нули, их нужно учитывать
       cinvoke  printf,<10,' *** Rich Header....: ',0>

        mov     esi,buff
        add     esi,[esi+3ch]        ;// ESI = линк на РЕ-заголовок
        mov     ecx,10               ;// длина цикла
@@:     sub     esi,4                ;// двигаемся назад от РЕ
        cmp     dword[esi],'Rich'    ;// ищем сигнатуру..
        je      @ok                  ;// если нашли!
        dec     ecx
        jnz     @b

       cinvoke  printf,<' Not found.',0>
        jmp     @exit

@ok:    push    esi
        mov     eax,[esi+4]       ;// EAX = ключ шифрования
        mov     [key],eax         ;// запомнить его.
        add     esi,4             ;//
        mov     [endRich],esi     ;// указатель на хвост блока

       cinvoke  printf,<   ' Found!',\
                        10,' *** Rich XOR-key...:  %08X',0>,eax

;//---- Расшифруем Rich
        pop     esi
        sub     esi,4          ;// линк на первый дворд с конца
        mov     edi,esi        ;// ESI для чтения, EDI для перезаписи STOSD
        xor     ecx,ecx        ;// ECX=0, будет счётчиком логов
        std                    ;// ставим обратный шаг
@@:     inc     ecx            ;// считаем дворды..
        lodsd                  ;// первый пошёл!
        xor     eax,[key]      ;// расшифровать ключом
        stosd                  ;// положить на место
        cmp     eax,'DanS'     ;// это маркер начала?
        jne     @b             ;// нет, продолжить..

;//---- Расшифровали блок! Запомним данные
        cld                       ;// восстановить прямой шаг, Clear
        add     esi,4             ;// коррекция ESI после LODSD
        mov     [startRich],esi   ;// запомнить линк на голову
        sub     ecx,4             ;// счётчик - первая строка нам не нужна (там нули)
        shr     ecx,1             ;// счётчик/2, т.к. парсить будем по 8, а не 4-байта
        add     esi,16            ;// пропустить первую строку
        push    esi ecx           ;// про запас!

;//---- Вычисляем хэш MD5 Rich-заголовка
        invoke  BCryptOpenAlgorithmProvider,md5AlgHndl,BCRYPT_MD5_ALGORITHM,0,0
        invoke  BCryptGetProperty,[md5AlgHndl],BCRYPT_OBJECT_LENGTH,md5ObjLen,4,pcbResult,0
        invoke  BCryptCreateHash,[md5AlgHndl],md5Hndl,hashObject,[md5ObjLen],0,0,0

        mov     eax,[startRich]   ;// EAX = начало блока
        mov     ecx,[endRich]     ;//
        sub     ecx,eax           ;// ECX = длина в байтах

        invoke  BCryptHashData,[md5Hndl],eax,ecx,0
        invoke  BCryptFinishHash,[md5Hndl],hashBuff,16,0
        invoke  BCryptDestroyHash,[md5Hndl]

       cinvoke  printf,<10,' *** Rich MD5-hash..:  ',0>
        mov     esi,hashBuff
        mov     ecx,16
@@:     xor     eax,eax           ;// покажем полученный хэш!
        lodsb
        push    ecx
       cinvoke  printf,<'%02x',0>,eax
        pop     ecx
        loop    @b

;//---- Парсим расшифрованный Rich
       cinvoke  printf,<10,\
                        10,' Product-ID                      Build   Count',\
                        10,' -----------------------------   -----   -----',0>

        pop     ecx esi        ;// ESI = линк на первый лог, ECX = всего штук
@Rich:  lodsd                  ;// EAX = "Product-ID + Build"
        xchg    eax,ebx        ;// запомнить в EBX
        lodsd                  ;// EAX = счётчик операций "Counter"
        movzx   edx,bx         ;// EDX = Build
        shr     ebx,16         ;// EBX = Prod-ID
        mov     ebp,ebx        ;// EBP = EBX для поиска строки в таблице
        shl     ebx,2          ;// получаем смещение по номеру
        mov     edi,PidTable   ;// EDI = таблица строк "Product-ID"
        add     edi,ebx        ;// получаем линк
        mov     ebx,[edi]      ;// EBX = указатель на строку
        push    ecx esi
       cinvoke  printf,<10,' 0x%03X = %-22s  %-6d  %d',0>,ebp,ebx,edx,eax
        pop     esi ecx
        loop    @Rich          ;// промотать ECX-раз..

@exit: cinvoke  _getch     ;// клавиша
       cinvoke  exit,0     ;// выход!
;//----------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',comdlg32,'comdlg32.dll',\
         kernel32,'kernel32.dll',user32,'user32.dll',bcrypt,'bcrypt.dll'
include  'api\msvcrt.inc'
include  'api\kernel32.inc'
include  'api\user32.inc'
include  'api\comdlg32.inc'
include  'api\bcrypt.inc'

Result_0.png


Немного пояснений..
Вначале идёт инфа из опционального заголовка где видно, что файл для платформы х32 с кодом 14Сh, имеет 4 программные секции (можно было перечислить их имена), и слинковал его компоновщик версии(10), а вот какой именно не указывается, мол догадайся сам. Далее следует база и точка-входа, после которых выравнивание секций в памяти (4096-байт = одна страница вирт.памяти), и на диске = 512 байт. Размер страницы лучше оставить в дефолте, всё-равно это значение не влияет на размер файла, а лишь на его проекцию в ОЗУ. А вот второе значение 512 имеет смысл "подёргать за вымя". Чем меньше его порог, тем меньше получим пустых нулей в образе файла на диске.

Второй блок информирует о наличии хидера Rich, показывает значение ключа шифрования, и вычисленный хэш MD5 (всегда 16-байт). Этот хэш имеет важное значение, поскольку в некоторых случаях освобождает нас от парсинга всего блока. То-есть просто снимаем отпечаток и всё. Зачем это нужно мы обсудим позже.

Ну и в последнем блоке представлена информация о процессе создания исполняемого файла. Обратите внимание, что помимо студии(16), в файле натоптала и студия(15). Такой расклад мы можем наблюдать, когда программа использует чужие, уже готовые модули. Так-же видим импорт и компоновку символов с ресурсами в файлы *.obj. На заключительном этапе LINK.EXE версии(10) собирает всю эту кухню в единый файл отладчика KD.EXE (Kernel Debug).

Ради интереса я открывал много файлов и обнаружил интересные вещи. К примеру участник нашего форума @Mikl___ на сайте wasm.in выкладывал работы с графикой OpenGL на ассемблере MASM. Скрин одного из его файлов представлен ниже где видно, что товарищ наш использует хитрый приём для уменьшения размера файла. Компилируя батником, он видимо определяет ключи для переназначения выравнивания секций, поскольку оба значения Alignment равны всего 16-байт, хотя в дефолте 4096/512. Код для процессора х64, компиль с линкером версии(14):


MASM.png


А вот ещё один пример, скомпилированный на этот раз в среде VisualBasic.
Ясно, что автор этого кряка не трогал дефолтные настройки выравнивания секций. Тогда почему они равны и имеют значения 4096-байт? Это на порядок увеличивает размер исполняемого файла, и обычная форточка весит аж под 20К. Странно..


VB.png



5. Область применения знаний

В заключении попробуем ответить на вопрос: -"Какой вообще профит несёт в себе Rich, и что мы можем с него поиметь"? Ответ вполне очевиден. Если мы не занимаемся исследованием бинарей, защитой и реверсом софта, то естественно проку от Rich, как от козла молока. Чтобы по достоинству оценить всю мощь этого механизма, нужно мыслить иррационально, как это делают например сотрудники аверских контор. Тут и всплывают хэши заголовков Rich. Вот несколько наводок, а остальное сами..

1. Хэши позволяют без особых затрат классифицировать малварь. Правда для этого нужна база штамов, которую можно запросить у тех-же аверов, или со-временем собрать самому. Причём если файл один раз засветился (и после этого автор модифицировал его содержимое, чтобы обновить контрольную сумму), с вероятностью 99% он будет обнаружен вновь, парсингом заголовка Rich. Операции на уровне линкера – это самый нижний уровень, уйти от которого можно лишь координально сменив тактику, и все исходные файлы. Соответственно это будет уже совсем другая дичь, поймав которую за хвост мы вновь снимаем с неё хэш MD5, и помещаем его в свою базу.

2. Примечательным является то, что такие протекторы как Enigma, Themida, VMProtect и прочие, не трогают заголовок Rich. Это означает, что когда к нам попадёт накрытый протектором файл, потенциально мы можем обнаружить исходный, неупакованный пайлоад виря, путём поиска идентичных хэшей Rich из базы. Большинство этих средств защиты полностью меняют природу файла, щедро присыпая его слоем щебёнки. В обычных условиях, нам пришлось-бы весь этот пирог разгребать, а так достаточно потянуть за Rich, и не нужно пробираться в нёдра файла.

3. Недооценивать противника глупо. Нужно иметь в виду, что авторы вредоносов всегда идут на шаг впереди. Так, история знает случаи, когда точившие малварь ребята сажали аверские конторы на пятую точку, просто подменяя Rich-заголовок, выдернув его у легитимного файла, например calc или paint.exe. Совсем удалять Rich палевно, а подменить его часть или целиком уже няшно. Авер недоумевает – вроде файла в базе хэшей Rich нет, но ведёт он себя странно. Тогда приходится грузить сэмпл в вирт.машину и пытать его там. Более того, если мы знаем Rich какого-нибудь зверька, то подставив его в свой код, можно возложить всю вину на его автора, а самому остаться в тени. Именно так поступили Lazarus на олимпиаде в Пекине 2018-года. Да мало-ли чё можно придумать.. Тут главное иметь фантазию.


6. Заключение

Исследование бинарных файлов – невероятно увлекательное занятие, тем-более что в современных EXE есть-что искать. На первый взгляд вроде кажется, что ещё можно было добавить к теме РЕ-файлов, которая за столько лет изъезжена уже вдоль и поперёк. Однако вот наскребал-таки инфы на целую статью и то, принудительно удерживая себя. Как обычно, многое осталось за кадром, но смысл весь в том, чтобы просто показать направление и обрисовать возможности. Дальше, заинтересованный читатель должен продвигаться сам. Здесь вот Новый год подкрадывается, и если не встретимся, хочу пожелать всем удачи и попутного ветра. В скрепке лежит инклуд и исполняемый файл для тестов. До встречи, пока!
 

Вложения

  • PE_RICH.ZIP
    8,2 КБ · Просмотры: 307
Последнее редактирование модератором:

Qulan

Red Team
06.12.2020
171
512
BIT
365
Очень годный материал, благодарю.
Как подступиться к реверсу? Я учился переполнять буфер в течении месяца и с трудом понял что происходит. Но когда читаю статью на подобии вашей, становится страшно и очень блин интересно. Буду признателен за подсказку, с чего начать и как лучше начать. Я не стремлюсь стать малварь аналитиком, но хотел бы для себя обрести навыки реверса, как минимум чтоб разбираться в сплоитах. Спасибо!
 
  • Нравится
Реакции: Mogen и Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
298
1 322
BIT
173
Как подступиться к реверсу?
Чтобы заниматься обратным инженерингом (реверсом), для начала нужно выучить "прямой" инженеринг, т.е. язык программирования, а это как-правило машинный ассемблер. Это первое и основное требование. Во-вторых, поскольку всё действо происходит в памяти ОЗУ, нужно делать упор на изучение её организации - ячейки, как выделяется и освобождается, и прочее. Ну и дальше разнообразные хитрости программирования, которые старается использовать разработчик софта. Это придёт со-временем.

Ситуацию усугубляет то, что программы в основном пишутся на С++, причём без ключей оптимизации. В результате, обычный "HelloWorld" написанный на чистом ассме и плюсах, в отладчике будет выглядеть совсем иначе. В плюсах будет огромное кол-во шелухи и пару строчек полезного кода, найти которые не так просто. Здесь выручают точки-останова BreakPoint, которые необходимо освоить. Но и тут нужно знать, на что именно сбавить бряк, а значит по-любому нужно учить язык, чтобы понимать происходящее.

Более того, в современном мире ручной реверс уже не актуален - всё должна решать автоматика, которая экономит время и нервы. Только столкнувшись с упаковщиками и протекторами понимаешь, почему.
 

Ondrik8

prodigy
Green Team
08.11.2016
1 129
3 187
BIT
0
спасибо! лично для себя многое почерпнул)
 
Последнее редактирование:
  • Нравится
Реакции: Marylin

v1gman

Green Team
31.07.2020
495
523
BIT
23
в современном мире ручной реверс уже не актуален - всё должна решать автоматика, которая экономит время и нервы
Согласен, но отчасти. Если ты только изучаешь какую-то технологию, то стоит для начала без автоматизации потыкать. Возьмём пример с того же Burp Suite. Вообще, есть два вектора обучения: снизу-вверх и сверху-вниз.
Пример: Ты что-то тыкаешь в, предположим, nmap и оно работает, но ты не знаешь почему, и узнаешь об этом спустя некоторое время. Либо же ты изучаешь компьютерные сети (TCP/IP в данном случае) и потом уже с этим багажом знаний приступаешь к nmap'у.
 
  • Нравится
Реакции: Mogen

fuzzz

Red Team
03.02.2019
249
468
BIT
1
Статья хорошая, но тематики ИБ не увидел(((
 

rusrst

Green Team
04.10.2020
22
27
BIT
0
Ситуацию усугубляет то, что программы в основном пишутся на С++, причём без ключей оптимизации. В результате, написанный на чистом ассме и плюсах обычный "HelloWorld", в отладчике будет выглядеть совсем иначе. В плюсах будет огромное кол-во шелухи, и пару строчек полезного кода, найти которые не так просто. Здесь выручают точки-останова BreakPoint, которые необходимо освоить. Ну и тут, нужно знать, на что именно сбавить бряк, а значит по-любому нужно учить язык, чтобы понимать происходящее.
Статья круть!
Умели раньше майки делать вещи, что на десятки лет становились стандартом.

Что касается с/с++, в реально низких применениях там не только ключи использовать надо, но и расширения синтаксиса компиляторов. Да и вообще нужен опыт, опыт, опыт :)
 
  • Нравится
Реакции: Marylin
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!