№36.1. Об упаковщиках в последний раз

#Бредисловие

Сорванные башни

(часть 4)

Когда я верю в чудо, зная, что его нет, я понимаю, как думает детство.
(с) мечтатель

#Бредисловие от WASM GROUP

<00E>: Я тут письмо получил... Только оно в ящик не пролазит :))
<00F>: И что пишут?
<00E>: Да все об одном и том же.
<00F>: Что об одном и том же? в одном письме? Ето как, по несколько раз
повторяют что-ли? :0
<00E>: Ну типа того :) Вот, "Уважаемые HI-TECH" ... ага... вот... "неужели вы
не можете выпускать рассылку без приколов?"
<00F>: мда... можем. Но не хочем :)))
<00E>: не перебивай! Вот дальше: "и почему у вас на форуме какие-то закорючки?
Пусть админ подправит, а то ни в одной кодировке не читается. Парнография
какая-то!"...
<00F>: Это не порнография,
а старорусское народное творчество - ЯТЬ
называется.
<00А>: Какой-то безграмотный тип! ЯТЬ - это суть русской письменности.
SERV: PING, PING user <FFFF> aka <-1>
<-1>: А помоему давно нужно решить этот вопрос об ятях!!! В самом деле,
неудобно и странно...
<00A>: только через труп. Не дам! Ни за что! :))
<00E>: Можно и чере труп, вот я только что муху убил.. :((
Удивительное животное, повернул её голову на 180 градусов....
<00A>: и вообще, подумаешь, ять... никто и не говорил, что все будет просто.
Читать они не могут ... некоторые вон и на ассемблере пишут, а тут ять
прочитать не могут... Будете возмущаться - напишу на японском!
<-1>: нет-нет, яти вполне нормальные буквы, всех устраивает! :))
<00F>: 00E, а зачем на 180?
<00E>: Я хотел, чтобы она вверх ногами летела :(
<00A>: Птичку жалко :((
<00E>: Не хнычь, в следующий раз, я крылья переставлю!
Ничего, трудно в ученьи легко в бою. Скоро перейдём на животных покрупнее.
<00А>: А что там еще? >:E
<00E>: Ну много чего, я ж говорю, все тоже самое повторяется... а, вот в конце
"Вчера слышал в троллейбусе, что сайт WASM.RU будет на первом месте по
посещаемости с чем вас и поздравляю.
С уважением, Артурик из Эстонии"
<00F>: Ну так это хорошо? :)
<00E>: Замечательный парень этот Артурик.
Вот думаю, поеду к нему, подарю БОЛЬШОЙ СЮРПРИЗ
(он ж тут как раз адрес обратный оставил на конверте)
Ведь старался человек, раскрутил наш сайт аж на 10 место!
Ура, Артурику!!! Просто ангелочек!!!
Я слышал у него даже крылышки на спине растут.
<00A>: А что за СЮРПРЫЗ?
<00E>: ЭЭЭ секрет. Говорю же большой. :))
<00F>: Так, пока вы тут письма читали, я вступление написал. :)

Гарри Поттер и девайс маглов

...Проходя мимо надписи на левом крыле Хогвартса "Поттер+Грейнджер=...", Гарри выругался и махнул палочкой. Стена раскрошилась, башня дрогнула. Надпись осталась - она висела в воздухе, поддерживаемая чьим-то заклинанием. На стенах все равно ничего написать было невозможно - рассерженная декан Гриффиндорфа, шепча странные заклинания "Annes-ke! Kutyn-ges-a-Chaleem!", наложила на все стены заклятье Восковой Доски, и после этого никакие надписи на стенах не держались. Даже Гермиона, владевшая всеми иностранными языками в совершенстве, перевести эту фразу не смогла.

До экзаменов осталась три дня. Солнце ярко светило, птицы пели. А у Гермионы как раз был День Рожденья. Правда, она, похоже, о нем забыла, закопавшись в учебники..., но Гарри собирался напомнить.

Ну какая еще может быть сессия - летом, да еще в 16 лет! "Нет, хорошую погоду в учебный день придумали точно садисты!"-подумал Гарри, потом посмотрел на висящую в воздухе надпись, пробормотал "Козлы!" и добавил одно из словечек профессора Дамблдора. Из палочки вырвался ослепительный луч, ударивший в левую башню Хогвартса. Башня задрожала и начала медленно рассыпаться. "Ну, теперь симметрично!" - подумал Поттер. Правая башня развалилась на прошлой неделе, когда группа слизеринцев, по словам Гермионы, попытались "узнать стиль моей одежды". Гермиона тогда тоже добавила к заклинанию одно из словечек Дамблдора. Шестерка слизеринцев до сих пор лежала в отдельной палате. По слухам, они превратились в что-то похожее на одноглазых циклопов с вертикальным ртом и заросли густой шерстью. Гермиона, как непосредственный очевидец, могла рассказать больше, но при воспоминании об этом ее разбирал такой истерический хохот, что друзья решили ни о чем больше не расспрашивать. А башня развалилась - так же, как и эта. "Ну все,"- подумал Гарри, - "если кто-нибудь видел, тысяча штрафных нам обеспечена!". И поспешно, но бесшумно, скрылся в направлении оранжереи...

...Посреди оранжереи угрюмо стоял Хагрид. На вытоптанной земле торчали чудом сохранившиеся несколько веточек. Со вздохом он оборвал их и критически посмотрел на получившийся пучок.
- Привет, Хагрид! - раздался за спиной голос Гарри. - А что... больше ничего нет?
- Да, придется идти в Дикий Лес, - вздохнул великан, - циклопы, они... ну как это сказать... ну им надо много, килограммов по 10 травы каждому!
- Слушай, Хагрид, а тогда не отдашь этот букетик мне? - осторожно осведомился Гарри.
- Да, конечно, бери, о чем разговор! А... для чего тебе? - и он протянул Гарри последние цветы из оранжереи.
- Ну,... для Гермионы... - Гарри слегка покраснел.
- А что с ней? - забеспокоился Хагрид, - надеюсь, ничего серьезного? Может, помочь чем?
- Нет, нет, все в порядке, - поспешил успокоить его Гарри, - мы справимся сами!...
Хагрид еще раз вздохнул и начал собираться за целебными травами в Дикий Лес. Здесь до следующего года ничего ожидать не приходилось.

...Последние консультации Снегга разнообразием не блистали. Он просто давал дополнительные ссылки на статьи, и прочитать все это можно было в любое свободное время. Но совсем не появляться на консультации было опасно. Вопросы по программированию, задаваемые волшебниками, похоже, ставили Снегга в тупик. Каждый вопрос, на который, по мнению Гарри, он не мог ответить сам, Снегг вставлял в билеты или переадресовывал другим, не упуская случая накинуть штрафных баллов. Вот и сейчас он предложил просмотреть статью "Об упаковщиках в последний раз". Гарри делал вид, что что-то читает, и просто пролистывал текст то вверх, то вниз. Мысли были далеко - сегодня вечером Гермиона согласилась прогуляться с ним по опушке леса! Да, после 9 выходить не разрешалось, но Гарри помнил несколько выходов из Хогвартса - ведущих как раз к Бешеной Иве и лесу. Впрочем, Гарри увидел несколько строчек посвящённых защите, и тут представив себя великим крякером, увлёкся за чтением новой статьи...

VOLODYA/HI-TECH NEOx/UINC.RU

Об упаковщиках в последний раз

Часть первая - теоретическая

Сложное – то, что делается мгновенно,
невозможное – то, что требует лишь
немногим большего времени

Рецензент:
Dr.Golova/UINC.RU

Главный корректор:
Edmond/HI-TECH

1. Лирическое отступление

Решено было разбить эту статью на две-три части. Первая часть собственно теоретическая. Она дает абсолютно необходимый минимум теории, который позволит успешно распаковывать файлы. В статье также описывается формат XML, который затем переводится в CSS-HTML при помощи XSLT-процессора. Таким образом, наряду со знанием структуры PE-файла вы, по прочтении статьи, будете иметь самые базовые представления о XML/CSS/XSLT, Perl/Java и других страшных технологиях. Мы достаточно неплохо осознем, что тема уже достаточно замусолена, поэтому нудного пересказа PE-документации здесь не будет. Скорее, вам стоит воспринимать эту статью как некий коллектор, попытку обобщения и анализа всей доступной информации о пакерах, PE-файлах, антиотладке и т.п. А поскольку есть возможность перевода статей с испанского и французского языков (помимо стандартного английского), то должно получится что-то неплохое. Вторая часть будет чисто практической. Мы рассмотрим UPX, Aspack, Asprotect, Crunch, Armadillo и вскользь упомянем о других, менее популярных пакерах. Все опыты, приведенные в первой части проводились на calc.exe – стандартном калькуляторе из поставки Windows 2000. Вторая часть тоже будет проходить вместе с calc.exe. Реальные случаи будете рассматривать сами. Последнее, но немаловажное замечание – ВСЕ, что здесь обсуждается, валидно ТОЛЬКО для Windows 2k/XP/2003 (последняя – с натяжкой!). 9x НЕ рассматривается как анахронизм. Замечание важно, т.к. 9x и NT по-разному трактуют оперативную память, загружают библиотеки, работают с процессами, обрабатывают исключения и т.п.

Для того чтобы понимать, что здесь написано, введем пару допущений. Допущение номер раз: вы знаете ассемблер; №2: вы знаете, что такое исполняемый файл, т.е. файл загружается с диска в оперативную память с помощью механизмов ОС, при этом ОС определенным образом может при загрузке корректировать информацию внутри файла, если это необходимо; №3: вы слышали о языке программирования Perl; №4 – вы имеете очень приличное представление о базовых типах языка С – структурах, массивах и т.п., последнее – пятое – вы имеете хоть и очень смутное, но все же какое-то представление о том, что такое PE-формат исполняемого файла.

2. Основные концепции

Теперь, наконец, давайте рассмотрим формат PE-файла. Скажем сразу, что в наши намерения не входило давать здесь пересказ технической документации MS. Файл со спецификациями PE/COFF при-ложен, на wheaty.net есть статьи Питрека на эту тему. Полагаем, в русской части Интернета сравнительно просто найти саму книгу Питрека. Из русской литературы можно предложить Румянцева, где неплохо описаны поля, и даже есть исходники, хотя их качество нам не нравится, и статью о загрузчике PE-файлов на RSDN.ru – там все расписано очень неплохо, есть и исходники, но они на Delphi, а этот язык мы не знаем и не любим. Однако алгоритм, по справедливому замечанию автора, везде одинаков.

Да, чуть не забыли. Для хорошо знающих английский, есть абсолютно великолепная вещь, совершенно свободно распространяемая - http://www.iecc.com/linker/ - слейте ее целиком, и обязательно прочтите, просто изумительно! Книга будет продублирована и на WASM.RU, ибо вполне стоит того! Практическим примером к этой книге может служить техническая документация с сайта http://www.cs.virginia.edu/~lcc-win32/ - Якоб Навия очень неплохо расписывает многие тонкости по созданию компилятора и линкера. Еще дополнительно прилагается англоязычное описание PE-формата (версия 1.7) от Lumnificer, где подробно расписываются все поля, и даже объясняется, как создавать PE-файл ручками, т.е. расписывается работа примитивного линкера!

Так же, посоветуем изучить исходный код программы Wine – эмулятора Windows для Linux. Wine умеет загружать pe-файлы под Linux, причем код pe_image.c написан очень развернуто. А там еще мно-о-го примеров есть… Сайт - http://www.winehq.com.

Теперь очень быстренько пробежимся по списку литературы, описывающего работу NTDLL.dll – Windows loader'а.

Нашими маяками в этом бушующем море станут статьи Питрека.

А также, основанная на этом статья Russell Osterlund с сайта http://www.smidgeonsoft.com, название статьи – "Windows 2000 Loader: What Happens Inside Windows 2000: Solving the Mysteries of the Loader" – лежит на MSDN. Лучше пока еще ничего не написано. Примеры для статьи скачивайте только с MS, родной сайт лучше не трогать. Причем после слива можно смело удалить все dsp/dsw/ncb/suo/sln файлы, оставив только vcproj. Причина в том, что все файлы проектов повреждены, во всяком случае, VS.NET 2003 их открыть не смогла. Другое дело vcproj, который, по сути, является простым xml-файлом. Подредактируйте их немного (неправильно прописаны пути Output Directory и т.п.), и держите в уме, что проект Forwarder требует lib-файл проекта Forwarded, поэтому второй должен собираться раньше.

Во всей остальной части статьи мы будем жестко придерживаться терминов, принятых Microsoft. Все определения структур взяты из winnt.h. Эта мера кажется очень разумной, т.к. на данный момент существует дикая неразбериха в терминах - существует целая куча слов, для которых нет четких определений, например, секция импорта, таблица импорта, IAT и т.п. Каждый писатель под этими словами понимает что-то свое, в результате чего имеем полный беспредел.

Теперь взгляните на рисунок ниже:

{картинка поскипана}

Мы предполагаем, что вы оторветесь от чтения этой статьи и проведете несколько минут с этим рисунком и официальной документацией от MS, держа в уме, что PE файл, по сути, просто совокупность секций, которые описываются в заголовке. Единственное что, стоит объяснить несколько нюансов.

2.1 RVA/VA и иже с ними

RVA переводится как Relative Virtual Address - относительный виртуальный адрес. Его относительность заключается в том, что он отсчитывается от адреса загрузки, который может быть, а может и не быть, равен ImageBase.

Обязательно учтите, что RVA имеет РАЗНЫЙ смысл для бинарных файлов (exe, dll, sys) и объектных файлов (obj, lib). Вторые мы здесь не рассматриваем, а для первых – файл загружается в память и RVA какого-либо элемента вычисляется так:

    RVA = VA – адрес загрузки,

где VA (Virtual Address) – виртуальный адрес элемента в памяти, а адрес загрузки берется из поля Op-tionalHeader.ImageBase, в том случае, если он равен ImageBase, либо вычисляется лоадером.

Обратите внимание на то, что элементарные просмотрщики файла, например, QVIEW/WinHEX и т.п. отображают абсолютные offset-ы, HIEW способен переключаться (Alt+F1) между показом VA и внутрифайловых смещений (local/global mode), а IDA вообще частично эмулирует работу ntdll.dll – Windows-loader'a, поэтому показывает только VA (если вы не укажете иное, например, загрузив файл как "binary").

Давайте рассмотрим пример для практического усвоения материала. Точка входа calc.exe (также часто называемая EP – entry point) выглядит так:

    .01012420: 55 8B ;VA

А в виде абсолютных внутрифайловых смещений – вот так:

    00011A20: 55 8B 
    ;обратите внимание на отсутствие точки!
    ;HIEW требует точку, если хотите VA!

Давайте попытаемся разобраться, какой же механизм пересчета VA во внутрифайловые смещения.

Алгоритм примерно выглядит так:

1) По VA рассчитать RVA

    VA = RVA + ImageBase

2) Для RVA найти секцию, в которой мы находимся (о секциях мы поговорим чуть ниже).

3) Пересчитать RVA во внутрифайловый offset по формуле:

    offset = RVA - IMAGE_SECTION_HEADER.VirtualAddress + 
       + IMAGE_SECTION_HEADER.PointerToRawData

Теперь давайте посчитаем ручками. В качестве примера возьмем адрес точки входа calc.exe - 0x01012420.

Сразу смотрим в HIEW на значение ImageBase -

     ¦ Image base 01000000 ¦ Subsystem GUI ¦

        VA = RVA + ImageBase;
       01012420 = RVA + 1000000;
       RVA = 12420; 
       //что можно сразу увидеть из OptionalHeader.EntryPoint

Теперь определяемся, где же у нас лежит этот RVA. Ага, RVA лежит в секции .text. Обратите вни-мание на то, что лежит он ближе к концу секции:

Number Name VirtSize RVA PhysSize Offset Flag
1 .text 000124EE 00001000 00012600 00000600 60000020

Теперь VA во внутрифайловое смещение:

    offset = 12420 - 1000 + 600 = 11A20;

Заметим, что процесс этот уже давным-давно автоматизирован. Есть такие утилиты как LordPE и PE Tools, а в них есть такая вещь как FLC (File Location Calculator), который по данному VA отобразит RVA и внутрифайловый offset.

Имея на руках некоторые API-функции - GetModuleHandle, ImageRvaToVa и ImageRvaToSection, несложно написать программу, которая быстренько переведет любой VA во внутрифайловый оффсет. Например, аналог функции от MS ImageRvaToSection может выглядеть где-то так:

      
     /***********************
     * Return value: Возвращается структура IMAGE_SECTION_HEADER, для данного RVA
     * Parameters: Первый – это структура IMAGE_FILE_HEADER
     * Parameters: Второй – это RVA для которого нужно найти секцию
     ************************/

    PIMAGE_SECTION_HEADER RvaToSection(PIMAGE_FILE_HEADER pFH, DWORD dwRVA)
    {
      UINT                  i;
      IMAGE_SECTION_HEADER  *pSH;

      pSH = (PIMAGE_SECTION_HEADER)((DWORD)(pFH + 1)
           + pFH->SizeOfOptionalHeader);
      for (i = 0; i < pFH->NumberOfSections; i++)
      {
       if (dwRVA >= pSH->VirtualAddress
        && dwRVA < pSH->VirtualAddress 
          + pSH->Misc.VirtualSize)
        return pSH;
       ++pSH;
      }
      return NULL;
    }
      

Примеры функций RtlImageRvaToVa /RtlImageRvaToSection /RtlImageDirectoryEntryToData есть в файле loader.c из wine.

Например, есть у вас есть dll, загруженная по адресу, отличному от ImageBase. Вы нашли защиту в Soft-Ice, и хотите пропатчить это место HIEW'вом - тут-то и пригодится умение.

2.2 О секциях PE-файла

PE-файлы были спроектированы для работы в ОС со страничной адресацией памяти. Известно, что Windows делит память на страницы различного размера, поэтому и PE-файл разбит на секции. Это очень важно понимать, поскольку секции PE-файла в памяти и на диске - это вовсе не одно и то же! В том случае, если сами данные в секции имеют общий объем менее размера кластера - секция на диске выравнивается (т.е., дополняется нулями, int3 - CCh или nop – 90h) под размер кластера (не путать с сек-тором – кластеры состоят из многих секторов диска!), который обычно равен 512 байтам (или другое значение – поле OptionalHeader.FileAlignment).

Когда файл загружается, то секция попадает в страницу памяти большего размера – 4 кб (или дру-гое значение - OptionalHeader.SectionAlignment), и выравнивается вновь, если это необходимо, уже только нулями. Обратите вниманиме, мы говорим "секция" – т.е., секция .text, CODE, .data, DATA, .aspack или что-либо еще. Каждая секция будет проецироваться на одну или несколько страниц памяти.

Не путайте понятие "Секция" (section) и "Директория" (directory). Директории (импорта, экспорта, ресурсов, исключений или чего-то еще) находятся ВНУТРИ секций. Заметьте, секция определяет разбиение PE-файла на части, но секции глубоко все равно, какой тип данных будет находится внутри нее. Для того, чтобы охарактеризовать тип, у нас есть директории! Директории характеризуют данные, содержащиеся в PE, по их типу и функциональности. Таким образом, можно говорить, что секция является физическим разбиением PE файла на состовляющие, а директория - логическим.

Иногда размер секции может занимать много кластеров, и отображаться Windows на несколько страниц памяти. Общее количество секций должно в точности соответствовать полю FileHeader.NumberOfSections. Теоретически, с этим шутить не стоит, так как шаг в сторону – стреляю. Такой файл не будет опознан ntdll.dll, поэтому на поле NumberOfSections можно смело(?) опираться. Однако и здесь нам уже радость испортили! Некоторые крипторы, например, telock, заменяют это значение в ПАМЯТИ, например, на 0xFFA4. Угадайте, что случится, когда вы попытаетесь запустить дамп с такого файла? А что будет, если утилита, просматривающая содержимое полей секций в цикле, будет ориентироваться на такую переменную как на счетчик цикла, нечто вроде такого:

    for (i=0; i<= FileHeader.NumberOfSections; i++)
    //где FileHeader.NumberOfSections == 0xFFA4
    {
    //Здесь обрабатываем содержимое секции
    //А теперь представим, что секции закончились. О-о-о-о-й!
    //да, попробуйте такой файл посмотреть HIEW. Забавная реакция!
    }

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

LordPE, HIEW на этот трюк покупаются! Все версии IDA - тоже! PE Tools – нет! Прекрасная защита от сброса дампа на диск (имеется в виду, дамп сбросить можно, а вот заставить его заработать потом.... тоже можно, но чуть посложнее)! Хотя, с другой стороны, такой файл не будет загружен самой Windows. Цитирую Евгения Сусликова: "не должно быть хиеву умнее виндов". Что ж, вполне справедливо.

Для каждой секции в заголовке PE-файла указан ее размер на диске и в памяти. Структура выглядит так:

    #define IMAGE_SIZEOF_SHORT_NAME 8
    typedef struct _IMAGE_SECTION_HEADER {
    //имя не более 8 символов
        BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; 
        union 
     {
                DWORD   PhysicalAddress;
                DWORD   VirtualSize; 
    //помеченные красным значения характеризуют секцию в памяти
    //VirtualSize – общий размер секции в памяти. 
    //если это число БОЛЬШЕ размера секции на диске
    // – секция дополняется нулями
     } Misc;
        DWORD   VirtualAddress;
    //RVA первого байта секции в памяти
    //синий шрифт – данные о секции на диске
        DWORD   SizeOfRawData;
    //Размер ИНИЦИАЛИЗИРОВАННЫХ данных на диске
    //Ой, какое интересное поле! Поговорим о нем чуть ниже. 
    //Вкупе с VirtualSize им часто пользуются протекторы
        DWORD   PointerToRawData;
    //очень полезное поле – offset от начала файла до первого байта секции
        DWORD   Characteristics;
    //характеристики секции – это поле тоже очень любят менять,
    // поговорим ниже и о нем 
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Особый интерес в этой структуре вызывают два поля – VirtualAddress и SizeOfRawData. Все остальные тоже очень интересны, но эти два широко используются протекторами для сокрытия от наших глаз того, что должно быть скрыто. К примеру, вам, вероятно, часто доводилось видеть нечто вроде такого в IDA:

    .data1:005B6FFD                 db    0 ;  
     .data1:005B6FFE                 db    0 ;  
     .data1:005B6FFF                 db    0 ;  
     .data1:005B7000                 db    ? ;
     .data1:005B7001                 db    ? ;
     .data1:005B7002                 db    ? ;
     .data1:005B7003                 db    ? ;

в то время как самый обычный hex-редактор выдавал вполне осмысленные цифры. Проблема и ее решение достаточно просты. Обратите пристальное внимание на поля VirtualSize и SizeOfRawData той секции, где есть такой подарочек. Значение SizeOfRawData должно быть меньше значения поля VirtualSize:

Number Name VirtSize RVA PhysSize Offset Flag
1 .text 00195C5C 00001000 00000000 00000000 60000020
2 .data 00008DD4 00197000 00000000 00000000 C0000040
3 .text1 00010000 001A0000 0000F000 00001000 60000020
4 .data1 00020000 001B0000 00007000 00010000 C0000040
5 .pdata 000B0000 001D0000 000B0000 00017000 C0000040
6 .rsrc 00001000 00280000 00001000 000C7000 40000040

Именно поэтому и появляются знаки вопроса – IDA ориентируется только на raw-байты (хотя, в случае секции кода с этим можно поспорить). Чтобы исправить положение, надо просто приравнять значение SizeOfRawData значению VirtualSize. И, вуаля:

    .data1:005B7000 aPdata000_0     db 'PDATA000'
     .data1:005B7008                 db    2 ;  
     .data1:005B7009                 db    1 ;

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

Возможен и еще, например, такого рода трюк. Положим, есть файл, у которого ручками создана несуществующая директория отладки (Debug Directory):

    #define IMAGE_DIRECTORY_ENTRY_DEBUG 6

Структура директории выглядит так:

    typedef struct _IMAGE_DEBUG_DIRECTORY {
        DWORD   Characteristics;
        DWORD   TimeDateStamp;
        WORD    MajorVersion;
        WORD    MinorVersion;
        DWORD   Type;
        DWORD   SizeOfData;
        DWORD   AddressOfRawData;
        DWORD   PointerToRawData;
    } IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;

Положим, мы задали заведомо огромные значения полей SizeOfData, AddressOfRawData и PointerToRawData. Лоадера эти поля абсолютно не интересуют (ему важна лишь валидность параметров в IMAGE_DATA_DIRECTORY). Прямо сказано, что эти поля касаются только отладчика (или дизассемблера). Так вот, IDA на таком файле (до версии 4.3 включительно) слетит наглухо! Такого рода трюк (© Dr. Golova) был применен в программе, которую рассматривал Sten в своей статье – "Исследование подаруночка" на reversing.net, если кто помнит.

Популярны также трюки с директориями импорта-экспорта. Некоторые поля (OriginalFirstThunk, например) забивают 0xFFFFFFFF или чем-нибудь не менее гадким - RVA OriginalFirstThunk должно, согласно действиям лоадера (Win2k SP4), укладываться в диапазон

    cmp     ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders]
    jb      loc_77F9373F ;если меньше SizeOfHeaders
    cmp     ecx, [eax+IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage]
    jnb     loc_77F9373F ;и больше либо равно SizeOfImage

если это не так – файл загружен не будет. В этих случаях IDA говорит "Can't find a translation for virtual address…", и … раньше слетала, а теперь продолжает работу. HIEW в своей работе на winnt.h не опирается вообще, он был создан раньше. Валидность адресов директории импорта/экспорта проверяется валидностью VA. Мы поговорим о подобных приемах немного позднее.

Флаги секции – тоже вещь небезынтересная! Здесь мы не будем углубляться в детали. Скажем лишь, что флаги секции могут преобразовываться лоадером в атрибуты страниц и сегментов, биты CR-регистров и т.д. Это достаточно сложная тема, требующая неплохого понимания принципов работы защищенного режима. Мы бы порекомендовали Рендалла Хайда "Art of Assembly" и Михаила Гука. Заодно можно почитать Фроловых, а также ознакомится со статьями Broken Sword на WASM.RU. После этого можно смело утверждать, что вы будете является одним из очень твердых специалистов по защищенному режиму процессоров х86 :). Такое знание не будет бесполезным. Например, понимание принципов защиты страниц виртуальной памяти помогает практически однозначно идентифицировать Aspack. Сбросьте флаг разрешения записи у секции кода в программе, предположительно запакованной Aspack, и взгляните на результаты. Почему такое происходит – мы поясним во второй части. Обязательно обратите внимание - это поле НИКОГДА не должно быть равным нулю. Как это можно использовать - см. ниже.

Сейчас уместным было бы посоветовать на какое-то время отвлечься от статьи, и попробовать поэкспериментировать с секциями файла. Да, мы сделаем это немного позднее, во второй и третьей частях, когда будем чистить файл от мусора, оставленного крипторами, однако, то, что сделано собственными руками, едва ли когда-нибудь забудется. Например, попробуйте поставить поле VirtualAddress какой-нибудь секции в ноль (как, например, это делает линкер от Watcom). Также учтите, что количество страниц в памяти не обязательно будет соответствовать количеству секций в PE-файле. Во-первых, размер, во-вторых – сама Windows просто не загрузит страницу памяти до тех пор, пока она не нужна. Только если страница нужна, тогда она будет подгружена.

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

    // after some windoze debugging I found that the name of the sections
    // DOES matter :( .rsrc is used by oleaut32.dll (TYPELIBS)
    // and because of this lame dll, the resource stuff must be the
    // first in the 3rd section - the author of this dll seems to be
    // too idiot to use the data directories... M$ suxx 4 ever!
    // ... even worse: exploder.exe in NiceTry also depends on this to
    // locate version info

И действительно. В файле oleaut32.dll можно найти следующие строки, сохранившиеся там со времен 95-го по 2k и выше:

     .77A078C7: 7825               js         .077A078EE  -----v (2)
     .77A078C9: 8D4580             lea         eax,[ebp][-80]
    .77A078CC: 687CB7A377      push        077A3B77C  -----v (3)
     .77A078D1: 50                 push        eax
     .77A078D2: FF15F8229B77       call        lstrcmpiA ;KERNEL32.dll

     .77A3B770:  44 00 49 00-52 00 00 00-2A 00 00 00-2E 72 73 72  D I R   *   .rsr
    .77A3B780:  63 00 00 00-00 00 00 00-74 79 70 65-6C 69 62 00  c       typelib  
      

И из-за откровенно неумного, недальновидного, …, не будем перечислять, поведения программиста, кракеры получили большой подарок, а авторы пакеров – подарок поменьше. Спасибо, Microsoft! И абсолютно без всякой иронии! Так что, если автор хочет, чтобы его файл имел красивую иконку, то он должен предоставить ресурсы файла в наглядном виде. Конечно, не все так просто, секция ресурсов то-же безжалостно калечится, однако, вероятность того, что название секции будет сохранено наряду с иконкой, очень велика. Что до остального.… Разберемся с этим чуточку попозже.

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

В заключение, рассмотрим алгоритм подсчета количества секций PE-файла. Как уже упоминалось, поле FileHeader.NumberOfSections не может служить достаточно надежным проводником. Тогда что? Пока мы используем этот алгоритм (к слову, тоже ненадежный), но, возможно, после выхода статьи, он уже не будет работать в силу вполне определенных причин. Тогда нам с вами останется последний верный и надежный способ – считать значение из файла диске. Многие утилиты это позволяют. Итак, производим подсчет секций, и смотрим на поля PointerToRelocations, PointerToLinenumbers, NumberOfRelocations, NumberOfLinenumbers. Если все эти поля равны нулю, а поле Characteristics - НЕТ, значит - это секция.

Алгоритм прост:

    /***********************
     * Return value: настоящее значение NumberOfSections
     * Parameters: pMem - возвращаемое значение
     * от CreateFileMapping, либо от GetModuleHandle
     ************************/

    WORD GetRealNumberOfSections(PVOID pMem)
    {
     PIMAGE_DOS_HEADER       pDosh;
        PIMAGE_NT_HEADERS       pNTh;
    PIMAGE_SECTION_HEADER   pSh;

        WORD  iRealNumOfSect = 0;

     // Устанавливаем SEH
          __try
    {
     // Считывает IMAGE_DOS_HEADER
      pDosh = (PIMAGE_DOS_HEADER)pMem;
      // Считываем IMAGE_NT_HEADERS 
      pNTh = (PIMAGE_NT_HEADERS)((DWORD)pDosh + pDosh->e_lfanew);
      // Получаем указатель на первую секцию
      pSh = IMAGE_FIRST_SECTION32(pNTh);

      // Считываем секции по очереди
      for(word i = 0; i < (pNTh->FileHeader.NumberOfSections); i++)
      {
       if(!pSh->PointerToRelocations && 
        !pSh->PointerToLinenumbers &&
        !pSh->NumberOfRelocations && 
        !pSh->NumberOfLinenumbers &&
        pSh->Characteristics)
        // Увеличиваем счётчик секций
        iRealNumOfSect++;
       else
        return iRealNumOfSect;
       ++pSh; // Переходим к следующей секции
      }
      return iRealNumOfSect;
     }
     __except(EXCEPTION_EXECUTE_HANDLER)
     {
      // Ошибка “Access Violation!”
      return 0;
     }
    }  
      

Уже упоминалось, что линкер Watcom устанавливает значение поля VirualSize в нуль. Тогда возникает вопрос - откуда брать VirualSize? Рекомендуется использовать значение поля SizeOfRawData, выровненное по SectionAlignment. Ниже указан макрос на C++ для выравнивания секции по SectionAlignment:

    #define RALIGN(dwToAlign, dwAlignOn) ((dwToAlign % dwAlignOn == 0) ?
     dwToAlign : dwToAlign - (dwToAlign % dwAlignOn) + dwAlignOn)

Используется так: VirtualSize = RALIGN(VirtualSize, SectionAlignment);

Eще немного об ImageBase. Понятия «директория кода» не существует. Есть только секция кода, которая может называться, как душе угодно. Начало секции вычисляется из IMAGE_OPTIONAL_HEADER.BaseOfCode. В случае, если значение поля AddressOfEntryPoint не укладывается в диапазон,

    BaseOfCode<=AddressOfEntryPoint<=SizeOfCode

то это внимательному человеку может говорить о многом. Во-первых, файл, вероятно, запакован (почему так – см. часть вторую). Во-вторых, теперь вам придется сушить мозги с его распаковкой. Некоторые утилиты, например, OllyDbg, это дело подмечают, о чем вежливо предупреждают. Мало ли что?

Теперь перейдем к директории импорта.

(продолжение следует...)

© 2014-2017 Сергей Воробьев

0.02