Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Распаковка: от самого простого к чуть более сложному

Обсудить статью на форуме

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

Автор: MozgC [TSRh]

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

ЦЕЛИ :
Калькулятор упакованный UPX'ом (30 Кб)
Блокнот упакованный ASPack'ом (33 Кб)
PCMedik упакованный PECompact'ом (839 Кб)
CrackMe зашифрованный ExeStealth'ом (369 Кб)

Инструменты :

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

PE Tools (191 Кб)
LordPE (534 Кб)
Import Reconstructor (284 Кб)
Hex Workshop (2.6 Мб)

Сами упаковщики (качать по желанию):
UPX (314 Кб)
ASPack (294 Кб)
PE Compact (283 Кб)
ExeStealth (16 Кб)

Начинаем!

И еще раз всем привет! Вот и решил я написать свою четвертую по счету статью. Как вы уже поняли из названия, в этой статье я планирую научить вас распаковке простейших упаковщиков (или по другому пакеров (packers), кто как их обзывает). Что меня подтолкнуло к этому? А подтолкнуло то, что меня просто поражает сколько начинающих крэкеров до сих не могут распаковать такие упаковщики как upx, aspack и т.д. без помощи автоматических распаковщиков. Что уж говорить про PE Compact и ExeStealth (который является скорее криптером, чем пакером, т.е. не уменьшает размер упакованного файла, а просто шифрует его, затрудняя взлом) которые таких крэкеров ставят просто в полнейший тупик, ведь распаковщиков для последних версий PE Compact и ExeStealth я пока не встречал. А между тем, распаковать их - это дело 1-5 минут. И я не шучу, говорю на полном серьезе. Надеюсь после прочтения данной статьи вы согласитесь со мной. Значит план действий таков:

 1) Теория.
 2) Учимся распаковывать UPX.
 3) Потом ASPack.
 4) После этого гроза всех новичков - PE Compact.
 5) И напоследок, редко встречающийся ExeStealth.
 

Теория

Что же такое упаковщики, зачем их используют и каков их принцип работы? Ответы на эти вопросы я сейчас постараюсь вам дать. Извините, если говорю лишнее, и вы это уже знаете, но многие не знают и это и им это надо объяснить.
Значит упаковщики - это программы, которые главным образом предназначены для сжатия исполняемых файлов (здесь и далее под файлами будут подразумеваться исполняемые файлы exe и dll) таким образом, чтобы файл работал как ни в чем не бывало, запускался как обычно, но при этом занимал меньше места на диске.
Однако уменьшение размера файла - это не единственная задача упаковщика. Иногда задачей упаковщика является еще защита от взлома. Конечно защитой это можно назвать просто с огромнейшей натяжкой (про протекторы чуть позже), однако факт есть факт =). Как же упаковка мешает взлому? Ну давайте посмотрим. Когда мы загружаем неупакованный исполняемый файл в дизассемблер, чтобы его поисследовать =), что мы там видим? Правильно, видим мы там собственно сам код программы, ее структуру, данные и т.д. После упаковки мы этого уже не увидим! Почему? Проведем аналогию с архиваторами. Был обычный .TXT, например, файл. Мы его запаковали ZIP'ом. Если мы попытаемся посмотреть его содержимое мы увидим все тот же текст? Нет, мы увидим "мусор", файл упакован и чтобы с ним работать нужно его разархивировать. Так и в нашем случае при попытке дизассемблирования мы увидим "мусор" вместо осмысленного кода. Также можете догадаться, что и пропатчить упакованный файл (In-Line патчи это отдельный разговор) будет невозможно. Допустим мы в отладчике увидели какой переход надо менять, запомнили адрес, узнали смещение этого адреса в файле. Лезем в файл, чтобы пропатчить его и... ..и видим что по нужному нам адресу, не переход, который мы хотели пропатчить, а "мусор". Конечно! Файл ведь упакован!
Для чего используются упаковщики мы поняли. Вам наверно интересно, КАК они работают? Само собой, мы не будем вдаваться в подробности, это не совсем просто, да и не нужно пока это вам. Раз вы читаете эту статью, значит пока не нужно =). Общий принцип я, конечно, расскажу. Значит так, мы знаем, что выполнение основного кода программы начинается с так называемой точки входа (Entry Point). Т.е. после того, как файл загружен в память управление передается на адрес Entry Point и программа начинает свою работу. Что делает упаковщик. Упаковщик запоминает точку входа, потом просто сжимает содержимое файла по одному из алгоритмов упаковки (обычно сжимается секция кода и данных), после чего дописывает свой код (свое тело) после (либо до) сжатого кода программы и изменяет точку входа на себя, т.е. на начало тела распаковщика. Теперь при запуске файла управление будет получать не основной код программы, тем более что он уже упакован и в таком виде не может быть выполнен, а код упаковщика (точнее сказать распаковщика). Код распаковщика, как вы уже поняли находящийся теперь внутри файла, получает управление первым и распаковывает упакованные секции кода/данных в памяти! На диске файл остается упакованным, неизменным, но в памяти он распаковывается, и соответсвенно может быть выполнен. После того как код и данные программы распакованы, код распаковщика восстанавливает таблицу импорта (об этом чуть позже) и передает управление освновному коду программы, т.е. на бывшую точку входа, которая в упакованных программах называется оригинальной точкой входа (Original Entry Point). Программа даже не замечает "подставы" =) и начинает выполняться так, как если бы она выполнялась будучи незапакованной.
С понятием Original Entry Point (запомните: сокращенно OEP) вы будете встречаться постоянно, поэтому я отдельно дам определение. OEP - это адрес, с которого бы начинала выполняться программа, если бы не была упакована. Еще, пока не забыл, на всякий случай, уточню, что код распаковщика, помещенный в программу, занимает мало места и общий размер программы засчет упаковки уменьшается. Вот такой вот краткий принцип работы упаковшиков. Как вы понимаете нас в нашем деле упакованные файлы не устраивают, портят они нам малину =), поэтому, как нетрудно догадаться, такие файлы нужно сначала распаковать, а потом уже спокойно делать с ними что угодно (ну мы то знаем, что мы с ними будем делать). Итак, приступим..

И опять план =)

Я понимаю, что сейчас многие из вас просто не представляют как же осуществляется распаковка таких файлов. Поэтому для начала я решил выделить основные этапы распаковки программ, которых мы потом будем придерживаться. Вот они:
 1) Нахождение оригинальной точки входа.
 2) Остановка перед переходом к оригинальной точке входа и зацикливание программы.
 3) Снятие дампа программы.
 4) Восстановление импорта и изменение точки входа на оригинальную.
 
Теперь чуть подробнее. Для начала неплохо бы узнать каким упаковщиком сжата программа. Этот пункт я не указал, потому что на самом деле он не обязательный. Вы научитесь распаковывать программы в независимости от того, каким упаковщиком они упакованы. Потому что общий алгоритм от этого почти не меняется. А в последствии вы научитесь узнавать упаковщики "на глаз". Пока же, чтобы узнать, чем упакована программа, воспользуемся программкой PEid - как мне кажется лучшей программой по определению того, чем упакована программа или на каком языке программирования она написана.
После того как вы узнали, чем упакована наша жертва, необходимо найти ее оригинальную точку входа. Если в одном из редакторов PE-файлов (типа PE Tools, LordPE, ProcDump) посмотреть точку входа в запакованную программу, то можно будет увидеть (поначалу вы это не поймете, но это так), что она указывает на код распаковщика. Нам же надо вернуть все на родину, в том числе и точку входа поставить равной оригинальной точке входа, чтобы выполнение программы начиналось не с кода распаковщика, а с кода исходной программы. Но для начала эту оригинальную точку входа нам нужно найти. После того, как мы ее найдем, нам необходимо будет остановиться на том месте, где код упаковщика/распаковщика передает управление уже распакованной в памяти программе. Тут нет ничего страшного и непонятного. Просто после того, как упаковщик отработал, он делает банальный jmp eax, где eax = OEP, т.е. выполнение программы продолжится с ее исходного настоящего начала. На самом деле переход на OEP может быть осуществлен не только с помощью jmp eax, но об этом позже.
Что-то отвлеклись. Значит нам нужно найти этот переход, передающий управление основной программе. Теперь поподробнее. После того, как этот переход найден, нам нужно зациклить программу, т.е. поставить ее выполнение в бесконечный цикл на одном месте. Это делается для того, чтобы остановить ее выполнение перед самым началом (перед OEP). Зачем? - Чтобы остановить выполнение программы и сделать дамп в тот момент, когда программа находится в своем исходном состоянии. Если сдампить программу, просто запустив ее, то, возможно, секции данных и кода будут уже изменены и отличаться от исходных. Это может повлиять на работоспособность дампа (распакованной программы).
Извиняюсь за то, что сразу не объяснил что такое "дамп" и "сдампить". Слово dump (дамп) может быть и существительным и глаголом. В первом случае, дамп означает - область (часть) памяти или же файл, сохраненный на диск из памяти. Во втором случае, сдампить, снять дамп - значит сохранить нужную область памяти (обычно занимаемую программой) на жесткий диск. При распаковке дампить распакованную в памяти программу нужно всегда. Это нужно для того, чтобы получить распакованную программу. Ведь когда запакованная программа запускается, то она распаковывается в памяти, и в этот момент распакованный код программы нужно сохранить на жесткий диск. Не пугайтесь. Я понимаю, что некоторым это кажется чем-то сложным и невероятным. Но это теория. Практика все прояснит, вы увидите, что все это легко, и к концу статьи (ну и после небольших тренировок) на распаковку ЛЮБОГО из приведенных здесь упаковщиков вам потребуется МЕНЕЕ 5 МИНУТ (Я ради интереса распаковывал меньше чем за минуту =).
Что у нас там дальше. Пункт 4 - восстановление таблицы импорта и изменение точки входа на оригинальную. Уухх... Объяснить будет сложно. Слушаем внимательно, и если не понятно, то запоминаем =) Значит таблица импорта хранит информацию о функциях, используемых программой при ее работе. Это всевозможные Windows API функции, без которых работа программы попросту невозможна. Исходно таблица импорта хранит в себе адреса, по которым в файле находятся имена импортируемых функций, т.е. функций используемых при работе программы. При запуске программы эти адреса (это все в памяти происходит) перезаписываются прямыми адресами имортируемых функций. Чувствуете разницу? Например сначала в таблице импорта лежал адрес строки "CreateFileA", а после запуска программы там уже будет лежать адрес функции CreateFileA.
Справедливый вопрос: "А зачем что-то восстанавливать?". Дело в том, что если в таком состоянии (когда в таблице импорта уже записаны адреса функций поверх адресов имен этих функций) сдампить и потом запустить программу, то она будет работать только при условии, что операционная система (ОС) той же версии и того же build'a что и та, на которой и дампили саму программу. Это потому, что те ячейки, в которых изначально лежат адреса имен функций, используемых для получения уже прямых адресов функций в любой версии операционной системы, уже заполнены адресами этих функций в той системе, в которой дампили программу. В этом случае информация об адресах имен функций уже не может быть восстановлена (адреса имен функций в таблице импорта уже перезаписаны просто адресами функций) и при запуске такой программы будут использоваться уже записанные прямые адреса функций. Но что будет, если мы такую программу запустим на другом компьютере, или даже на этом же компьютере, но в другой версии ОС? Адреса системных функций там будут уже другими !!! И, соотвественно, адреса, записаные в таблицу импорта будут ошибочны, что приведет к неработоспособности программы. Итак, наша задача - восстановить таблицу импорта, т.е. получить таблицу, в которой бы были записаны адреса имен функций (исходное состояние). С такой таблицей импорта программа будет работать всегда и везде. Это и подразумевается под выражением "восстановить импорт". На практике это НА ПОРЯДОК легче, чем в теории.
Теперь о грустном. Если вы ничего не поняли про таблицу импорта и зачем ее восстанавливать - не расстраивайтесь. Просто помните, что таблицу импорта после распаковки нужно восстанавливать. И с набором опыта вы поймете зачем и почему! Тем более, что проблем из-за этого у вас пока не возникнет. В этой статье процедура восстановления импорта будет описана. И вы сможете всегда ее повторить самостоятельно, даже если так и не поняли зачем это нужно. В этом случае просто запомните, что это нужно =).
После того, как снят дамп распакованной программы и импорт восстановлен, единственное, что остается, это присвоить точке входа значение оригинальной точки входа, чтобы выполнение начиналось с исходного кода программы, как ни в чем не бывало. После этого распаковку можно считать на 100% оконченной!

От теории к практике: UPX

Я решил не заморачиваться и сжать простой Windows-калькулятор (от Win98) UPX'ом. Взять уже упакованную версию можно тут. Естественно, его можно элементарно распаковать самим же UPX'ом с параметром "-d", т.е. "upx.exe -d calc.exe", но это нам не подходит, нам нужно учиться распаковывать самим, без использования автоматических распаковщиков. Если запустить этот calc.exe, то вы увидите, что он запустился как ни в чем не бывало, естественно, так и должно быть. Однако занимает он 30Кб (в незапакованном виде занимал 90+ Кб). Если натравить на него PEid, то мы увидим там надпись "UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo", что подтверждает, что файл упакован UPX'ом.
Что же, приступим к распаковке. Как я уже говорил, для начала надо найти OEP. Поиск и попадание на OEP проще всего сделать с помощью установки брейкпоинта "bpm esp-4", где вместо esp подставляется значение, которому равен регистр esp при старте программы, т.е. при нахождении на Entry Point. Такой брейкпоинт сработает (обычно не в первый раз, а например во второй) прямо перед переходом на OEP. На установке такого брейкпоинта основана распаковка большинства пакеров и протекторов.
Резонный вопрос "А почему именно на esp-4? Откуда это взяли, и почему он сработает перед переходом на OEP?". Будет сложно, но я попытаюсь объяснить. Значит для нахождения OEP используем тот факт, что при старте программы в Windows, указатель на верхушку стека (регистр esp) практически всегда один и тот же (имеет одно и то же значение). Это не обязательно, но это стандарт. Например, в Windows 2K/XP, esp при старте программы ПОЧТИ ВСЕГДА равен 12FFC4h. Хотя, конечно, если изменить esp вручную, ради интереса, находясь на EP, то ничего страшного не произойдет. Однако, повторюсь, такой стандарт сохраняется, и мы его используем для нахождения OEP. При запуске программы в общем случае esp равен этому стандартному значению. Так как упаковщик во время своей работы следит за стеком (т.е. сколько поместил в стек, столько потом и достал из стека), то, перед переходом на OEP, esp должен быть опять равен этому "стандартному значению". Практически во всех упаковщиках, когда они восстанавливают стек перед переходом на OEP, считывается значение в стеке по адресу esp-4 (имеется ввиду что esp = "стандартному значению"). Следующий элемент стека находится уже по адресу esp, и упаковщик его трогать не должен, т.к. не он ложил этот элемент в стек. Т.е. используя тот факт, что последним будет считываться значение по адресу esp-4, можно поставить брейкпоинт на тот момент, когда это будет происходить (bpm esp-4), и, в один из моментов срабатывания такого брейкпоинта (он может сработать несколько раз) мы окажемся перед переходом на OEP.
Если вам трудно это понять, не расстраивайтесь. Это действительно сложно объяснять, но я старался как мог. Даже если вы не поняли почему ставить брейкпоинт именно "bpm esp-4", то просто запомните это и делайте так для нахождения OEP. В будущем вы обязательно разберетесь почему так.
Итак, нам нужно поставить брейкпоинт "bpm esp-4" находясь на Entry Point (EP) упакованной программы. Как прерваться на EP? Я обычно это делаю так:
Запускаю PE Tools. В меню Tools выбираю Break & Enter и выбираю нужную программу. После этого появится сообщение о том, что необходимо поставить брейкпоинт "bpint 3" (брейкпоинт на вызов третьего прерывания) и, после его срабатывания, написать "e eip 0xYY" (YY - это какой-то байт, который был заменен на вызов третьего прерывания. Причем не обязательно добавлять 0x, можно писать просто "e eip YY").
Хватит теории, а то я и в этой главе что-то одну теорию пишу. Запускаем PE Tools ! В меню Tools жмешь Break & Enter и выбираем наш упакованный файл calc.exe. Следуя появившемуся предупреждению, ставим в софтайсе брейкпоинт "bpint 3" (потом F5), кликаем ОК и тут же всплывает отладчик. Там пишем "e eip 60". Вообще объясню, что когда мы используем "Break & Enter", по адресу начала кода программы (EP) ставится байт CCh (пишется поверх исходного байта). Байт CCh - это код команды int3, т.е. вызова третьего прерывания. А так как мы поставили брейкпоинт "bpint 3", т.е. брейкпоинт на вызов третьего прерывания, то отладчик остановит программу в тот момент когда она выполнит команду int3 (байт CCh). Само собой в этот момент мы будем находится на Entry Point программы, после чего замененный байт надо вернуть на родину (он ведь был заменен на байт CCh). В нашем случае это байт 60h. Так вот еще раз повторюсь, что когда сработает брейкпоинт, то восстанавливаем исходный байт (иначе программа не будет выполняться дальше), стираем все брейкпоинты (bc *) и устанавливаем заветный брейкпоинт "bpm esp-4" (вместо esp можно ничего не подставлять, ведь вместо esp по идее надо подставлять значение esp при старте программы, но мы то ведь и находимся на этом самом старте) и жмем F5. Когда сработает брейкпоинт мы окажемся вот в таком месте:
 :01018658 FF96D48B0100            call dword ptr [esi+00018BD4]
 :0101865E 61                      popad
 :0101865F E97C93FFFF              jmp 010119E0 <--- Мы будем здесь
 
Команда popad возвращает всем регистрам значения, сохраненные в стеке. Так часто бывает когда распаковщик сделал свое дело и после этого должен вернуть значения всех регистров в их первоначальное состояние. Кстати вначале когда мы восстанавливали первый байт - 60h - это байт команды pushad - сохранить значения всех регистров в стек. Как видите код распаковщика сначала сохраняет значения регистров, чтобы ничего не испортить, а в конце своей работы восстанавливает их. В момент восстановления и срабатывает брейкпоинт "bpm esp-4". Вообще, часто начало кода распаковщика начинается с pushad и заканчивается командой popad, после чего почти всегда идет переход на OEP. Далее команда jmp 010119E0 - это и есть прыжок на OEP, т.е. передача управления уже распакованной в памяти программе. Здесь адрес 010119E0 - это OEP, начало исходной программы. Запишите его! Если мы сейчас выполним этот прыжок, то мы попадем вот сюда:
 001B:010119E0  55		PUSH      EBP      <-- Попадем сюда, это OEP
 001B:010119E1  8BEC		MOV       EBP,ESP
 001B:010119E3  6AFF		PUSH      FF
 001B:010119E5  6870150001	PUSH      01001570
 001B:010119EA  68601D0101	PUSH      01011D60
 001B:010119EF  64A100000000	MOV       EAX,FS:[00000000]
 001B:010119F5  50		PUSH      EAX
 001B:010119F6  64892500000000	MOV       FS:[00000000],ESP
 001B:010119FD  83C498		ADD       ESP,-68
 001B:01011A00  53		PUSH      EBX
 001B:01011A01  56		PUSH      ESI
 001B:01011A02  57		PUSH      EDI
 001B:01011A03  8965E8		MOV       [EBP-18],ESP
 001B:01011A06  C745FC00000000	MOV       DWORD PTR [EBP-04],00000000
 001B:01011A0D  6A02		PUSH      02
 
Я вам подскажу, что это стандартное начало программ написанных на Visual C++. Вам это знать не обязательно, просто это лишний раз доказывает, что мы действительно нашли начало программы - ее оригиинальную точку входа.
Теперь нам надо зациклить программу перед переходом на OEP, выше я объяснял зачем это надо. Делаем все то же самое, пока не окажемся на jmp 010119E0. Теперь в софтайсе пишем "a" (начать ввод ассемблерных команд), после чего "jmp eip", и два раза Enter (первый раз, чтобы подтвердить запись команды jmp eip, второй раз чтобы выйти из режима ввода команд). Теперь вместо jmp 010119E0 у нас будем jmp eip, т.е. постоянный прыжок на себя же - цикл на месте. Жмем F5 для выхода из SoftIce.
Программа зациклена и нам ее теперь нужно сдампить. Дампить уже будем не с помощью PE Tools, а с помощью LordPE, т.к. последняя версия PE Tools у меня и многих других дампит некорректно (не знаю почему и влом разбираться, проще просто использовать LordPE). Для начала надо настроить LordPE, чтобы он дампил нормально. Давайте сравним опции: жмем Options и внутри панели Task Viewer у вас должна стоять ТОЛЬКО галочка напротив Full dump: fix Header. Теперь закрываем окно опций и в списке запущенных процессов кликаем правой кнопкой по calc.exe, и выбираем dump full... Указываем имя файла, например dumped.exe и сохраняем дамп на диск (просто Ок жмем). Т.к. зацикленная программа нам уже больше не нужна, она только висит в памяти в бесконечном цикле и ест ресурсы процессора, то надо ее закрыть - все в том же LordPE кликаем еще раз правой кнопкой по calc.exe в списке процессов и выбираем burn process. Программу мы сдампили! Осталось немного!
А осталось нам восстановить импорт. Зачем это нужно делать я писал выше. Запускаем УПАКОВАННЫЙ calc.exe и после этого запускаем Import Recontructor (далее ImpRec). Вверху в списке процессов выбираем наш calc.exe. Внизу слева в поле OEP нам нужно ввести RVA OEP.
Сделаем небольшое отступление. RVA - это сокращение от Relative Virtual Address, т.е. относительный виртуальный адрес. "Относительно чего?" - спросите вы - относительно Image Base. Например, если какая-либо строка хранится в памяти по адресу 450000h, и Image Base = 400000h, то RVA этой строки равен 450000h - 400000h = 50000h. Image Base - это адрес в памяти, начиная с которого программа загружена в память. Чтобы вам было понятнее, приведу пример. Вы наверное все знаете, что в заголовке .exe файла (например) сначала идет сигнатура MZ (4D 5A). Так вот после запуска программы по адресу Image Base в памяти окажется именно MZ. Однако необязательно, что дальше программа в памяти после загрузки является точной копией программы на диске. Это происходит из-за различных смещений секций программы. Но это не так важно, я лишь хотел чтобы вы знали эти термины.
Как я написал выше, нам нужно найти RVA OEP. Для начала нам нужно найти Image Base (почти всегда он равен 400000h, но в этот раз это не так). Чтобы узнать Image Base запустите LordPE, нажмите PE Editor и выберите calc.exe. В открывшемся окне в поле Image Base вы увидите значение 1000000h. ImageBase мы нашли, теперь надо найти RVA OEP. RVA OEP = VA OEP - ImageBase. VA OEP - это Virtual Address OEP, т.е. виртуальный адрес OEP, его мы видели в отладчике. И его мы запомнили: он равен 10119E0h. Считаем: RVA OEP = 10119E0h - 1000000h = 119E0h. Вот это значение (119E0) и впишем в ImpRec'e в поле OEP.
Теперь нажмем IAT AutoSearch для автоматического поиска адреса таблицы импорта и сразу же увидим сообщение, что скорее всего адрес найден. После этого в поле RVA должно будет выставиться значение 1000h и в поле Size - 1E8h. Если так, то все правильно. Теперь жмем Get Imports и видим что в списке "Imported Functions Found" появилось 6 строк с именами импортируемых DLL. Напротив всех должно быть написано YES. Если у вас все так же, как и у меня, то идем дальше. А идти никуда и не надо! Осталось нажать 1 кнопочку! =) Жмем Fix Dump и выбираем наш дамп - dumped.exe (или как вы его там назвали). В той же директории, что и dumped.exe должен будет появиться файл с добавленным знаком "_" - dumped_.exe - это дамп с уже восстановленной таблицей импорта.
Вот и все! Если запустить dumped_.exe, то вы тут же увидите перед собой знакомый калькулятор! Мы только что распаковали UPX! "Но как же так?" - спросите вы - "Ведь мы же не изменили точку входа на оригинальную!?". Дело в том, что за нас это сделал ImpRec, помните мы писали OEP в ImpRec'e? Так вот после восстановления импорта нашему дампу ImpRec заодно выставил ему указанную точку входа, которая равна OEP. Теперь программа начинает выполняться не с кода распаковщика, а сразу же с распакованного кода самой исходной программы. Все! Upx побежден!

Распаковываем ASPack

Тут я тоже решил не заморачиваться и сжать обычный Notepad, тоже из Win98. Качаем упакованный файл здесь. Чем отличается ASPack от UPX? Если говорить со стороны того, кто его распаковывает, то ничем. Все также, тот же брейкпоинт "bpm esp-4", то же зацикливание перед ОЕП, так же дампим, так же восстанавливаем импорт. Зачем я тогда про него пишу? Ну чтобы не было вопросов лишних, чтобы больше потренироваться распаковывать. Согласитесь, лучше распаковать 4 разных упаковщика, чем один UPX. Пользы в первом случае будет больше. Да и совесть мне не позволила бы пропустить такой частый упаковщик как ASPack. Пока я тут рассуждал, вы уже должны были скачать упакованный блокнотик. Размер программы 33КБ, в распакованном виде был 56Кб. Кстати, ради интереса скажу, что UPX все-таки обычно сжимает чуть получше чем ASPack. Но нам это без разницы, давайте начинать распаковывать.
Проверим что файл действительно упакован ASPack'ом - натравим на него PEid. Действительно, видим надпись "ASPack 2.12 -> Alexey Solodovnikov". Я вас и на этот раз не обманул =)
Вспоминаем, как мы распаковывали UPX. Нам нужно прерваться на точке входа программы и поставить брейкпоинт "bpm esp-4". Зачем это делать я уже говорил в этой статье. Кто читает не сначала - читайте сначала =). В общем, запускаем PE Tools, в меню Tools жмем Break & Enter и выбираем наш упакованный файл "notepad.exe". Видим знакомую табличку, что надо в софтайсе поставить брейкпоинт "bpint 3", и когда он сработает восстановить первый байт командой "e eip 0x60". Делаем как нам говорят: ставим брейкпоинт "bpint 3", после чего кликаем Ok по табличке. Брейкпоинт тут же срабатывает, и мы восстанавливаем первый байт командой "e eip 60" в софтайсе. Теперь удаляем старый брейкпоинт: "bc *" и ставим новый: "bpm esp-4". Почему так я уже объяснял. Кстати, вы видите, что и код распаковщика ASPack'a тоже начинается с команды pushad. Логично предположить, что заканчиваться он будет противоположной командой - popad. Что ж, увидим. А сейчас, после того, как брейкпоинт "bpm esp-4" установлен, жмем F5 и прерываемся вот тут:
 001B:0040E3AF  61                  POPAD
 001B:0040E3B0  7508                JNZ       0040E3BA     <-- Мы находимся здесь
 001B:0040E3B2  B801000000          MOV       EAX,00000001
 001B:0040E3B7  C20C00              RET       000C
  ==> 0040E3BA  68CC104000          PUSH      004010CC
 001B:0040E3BF  C3                  RET
 
Видим знакомый popad - своего рода признак скорого перехода на OEP для начинающих, но не видим ожидаемого jmp. Я вам подскажу, что это действительно переход на OEP. Теперь давайте разберемся поподробнее. Итак мы находимся на "jnz 0040E3BA" и, как видим, это прыжок сработает, т.е. следующим шагом мы окажемся на адресе 40E3BAh. Там командой "push 4010CCh" в стек, как ни странно, кладется число 4010CCh =) Последующая команда ret, как вы должны знать, (если не знаете, то может рано вам еще распаковывыать?) делает переход на адрес, который лежит в стеке по адресу esp, а там сейчас находится число 4010СС. Значит команда ret сделает переход на адрес 4010СС - а это своеобразный прыжок на OEP! Вообще, ничего своеобразного тут нет, просто вам может быть непривычным увидеть такое исполнение перехода, т.е. когда в стек сначала кладется адрес перехода, а потом выполняется команда ret. Однако в будущем вы будете видеть такое очень и очень часто. Давайте дойдем до этого ret и выполним его. Действительно произойдет прыжок, и мы окажемся здесь:
 001B:004010CC  55                  PUSH      EBP     <-- Мы здесь
 001B:004010CD  8BEC                MOV       EBP,ESP
 001B:004010CF  83EC44              SUB       ESP,44
 001B:004010D2  56                  PUSH      ESI
 001B:004010D3  FF15E0634000        CALL      [KERNEL32!GetCommandLineA]
 
Уверю вас, что это действительно начало исходной программы и поэтому OEP = 4010CC. Запишите этот адрес!
OEP мы нашли, теперь надо зациклить программу и снять дамп. Делаем все то же самое, чтобы попасть на 40E3BO, где стоит "jnz 40E3BA" (Помните мы попали сюда когда сработал брейкпоин "bpm esp-4"). Однако теперь не идем дальше, а зацикливаем программу. У вас может возникнуть вопрос "А почему именно здесь?". Вообще обычно зацикливают программу и снимают дамп немного не доходя до перехода на ОЕП либо на самом OEP. Плюс-минус 5 команд ничего не изменят =) Значит когда вы находитесь на адресе 40E3B0, вводим в софтайсе "a" и затем "jmp eip", после чего жмем два раза Enter. Выше я уже писал, что это делается для того, чтобы зациклить программу. Теперь вместо команды jnz 40E3BA будет стоять команда jmp eip и программа будет висеть в вечном цикле, давая нам возможность ее сдампить. Этим и воспользуемся: выходим из софтайса и запускаем LordPE. Там в списке процессов выбираем notepad.exe, кликаем по нему правой кнопкой, выбираем full dump... и сохраняем файл на диск с именем "dumped.exe". Теперь зацикленная программа нам больше не нужна, поэтому мы ее закрываем, еще раз кликая правой кнопкой по notepad.exe в списке процессов и выбирая burn process. Вот и все, как вы помните, осталось восстановить импорт.
Итак, чтобы восстановить импорт, запускаем наш упакованный notepad.exe, после него запускаем ImpRec и в списке выбираем notepad.exe. Теперь нам опять нужно ввести RVA OEP в поле OEP =) Что ж, запускаем LordPE, жмем PE Editor, выбираем файл notepad.exe и в появившемся окне в поле Image Base видим значение 400000. Итак, Image Base = 400000h и OEP = 4010CCh, отсюда следует, что RVA OEP = 4010CCh - 400000h = 10CCh. Вот значение 10CC и вводим в поле OEP в ImpRec'e. Как и в прошлый раз жмем IAT AutoSearch и опять видим табличку с заголовком Found Something! Поле RVA теперь должно иметь значение 62DC и поле Size - 244. У вас должно быть то же самое. Жмем Get Imports и видим, что таблица импорта найдена и готова к восстановлению: напротив каждой строки с названием dll написано YES. Жмем Fix Dump, выбираем наш dumped.exe и... И видим появившийся файл "dumped_.exe". Запускаем его и любуемся знакомым блокнотиком. Мы только что распаковали ASPack! Аналогично предыдущему случаю, ImpRec сам восстановил оригинальную точку входа и избавил нас от лишнех действий. Трудно? Надеюсь, что нет! Поверьте мне, через какое-то время вы будете с усмешкой смотреть на эту статью и вспоминать вашу первую долгую распаковку =). Ну а мы, тем временем, идем дальше. Впереди - PE Compact!

PE Compact? Не проблема!

На этот раз я решил не халявить и захотел подобрать такую программу, упакованную PE Compact, распаковка которой прошла бы не совсем по стандарту. Моментально мне на ум пришла программа "PCMedik", которую я распаковываю и "немного подправляю" с каждой новой ее версией =) Взять последнюю, на момент написания статьи, версию PCMedik можно здесь.
Итак, файл PCMedik.exe упакован пакером PE Compact. Что представляет из себя этот упаковщик? Да в принципе ничего особенного. Хотя его можно похвалить за относительно гибкую настройку (опции можно менять прямо в программе перед сжатием) и отсутствие автоматических распаковщиков для последних версий PE Compact. Да и мне он почему-то больше нравится чем UPX и ASPack, хотя все равно обычно проигрывает UPX'у в степени сжатия. Что ж, пора разобраться и с ним!
Чисто для галочки, натравим PEid на файл PCMedik.exe и увидим такую строку: "PECompact 1.68 - 1.84 -> Jeremy Collake". Насмотревшись на эту строку, можно начинать распаковку. Почему я советую смотреть, чем упакована программа с помощью PEid? Не знаю... Просто когда я только учился распаковывать эти пакеры, то мне почему-то было психологически легче, когда перед началом распаковки я узнавал, чем упакована программа. Так что, думаю, возможно и вам это, хотя бы чисто психологически, поможет. Так уж устроен человек, что легче работать тогда, когда он знает, с чем имеет дело.
Вы уж извините, но одно и то же повторять 3-ий раз я не буду. Если вам надо каждый раз все объяснять заново, то не стоит заниматься тем, чем мы щас занимаемся. В общем ничего сверхъестественного тут нет. Прерываемся на EP (если остались такие, кто еще не знает, как это сделать, то смотрите выше, в главе про распаковку UPX или ASPack), восстанавливаем первый байт командой "e eip EB" (как видите на этот раз это уже байт EB). Чуть ниже видим знакомые команды pushfd (сохранить в стеке регистр флагов) и pushad (сохранить в стек все стандартные регистры). Ставим, как всегда, брейкпоинт "bpm esp-4" и жмем F5. Первый раз прерываемся на команде pushad, тремя строчками ниже Entry Point =) - это явно не то, что мы ищем, просто брейкпоинт сработал при обращении к стеку по адресу esp-4. Жмем F5 для продолжения и прерываемся тут:
 001B:004F854E  61                  POPAD
 001B:004F854F  9D                  POPFD
 001B:004F8550  50                  PUSH      EAX      <-- Мы окажемся тут
 001B:004F8551  682C2C4800          PUSH      00482C2C
 001B:004F8556  C20400              RET       0004
 
Как обычно перед переходом на OEP видим команду popad со своим напарником popfd. Дальше в стек ложится eax (не важно для чего) и далее знакамая нам по ASPack'у конструкция push - ret. Push 482C2C сохраняет в стек адрес перехода, и ret переходит по сохраненному в стеке адресу. Делаем вывод, что OEP = 484C2C. Традиционно, посмотрим, что там по адресу 482C2C:
 001B:00482C2C  55                  PUSH      EBP          <-- OEP
 001B:00482C2D  8BEC                MOV       EBP,ESP
 001B:00482C2F  B909000000          MOV       ECX,00000009
 001B:00482C34  6A00                PUSH      00
 001B:00482C36  6A00                PUSH      00
 001B:00482C38  49                  DEC       ECX
 001B:00482C39  75F9                JNZ       00482C34
 001B:00482C3B  53                  PUSH      EBX
 001B:00482C3C  56                  PUSH      ESI
 001B:00482C3D  57                  PUSH      EDI
 001B:00482C3E  B88C284800          MOV       EAX,0048288C
 001B:00482C43  E89433F8FF          CALL      00405FDC
 
Не то, что бы стандартное начало для Delphi-программы, но, тем не менее, это оно, начало программы =) Итак, запомнили OEP = 482C2C, теперь нужно зациклить программу перед переходом на OEP и сдампить её.
Говорю кратко. С самого начала добираемся до адреса 4F8550 (это там где push eax, после второго срабатывания брейкпоинта на esp-4), зацикливаем там программу командами в софтайсе "a", "jmp eip", Enter. Жмем F5 для продолжения, открываем LordPE, там находим в списке процессов PCMedik.exe, делаем ему full dump, сохраняем в dumped.exe и закрываем зацикленную программу с помощью "burn process". Ну вот и все, осталось восстановить импорт.
Теперь вы поймете, что я имел в виду в начале этой главы, когда говорил, что распаковка пройдет не совсем по стандарту. Не пугайтесь =) Чтобы восстановить импорт, запускаем наш PCMedik.exe, следом за ним запускаем ImpRec, и опять нужно ввести RVA OEP в поле OEP. С помощью LordPE находим, что ImageBase = 400000h. OEP = 482C2Ch, поэтому RVA OEP = 482C2Ch - 400000h = 82C2Ch. В поле OEP в ImpRec'e вводим 82C2C, жмем IAT Autosearch и видим радостную табличку с заголовком Found Something! Жмем Get Imports! Однако, что такое? В списке импортируемых dll и использующихся из них функций, мы видим всего одну строчку, напротив которой написано NO. Это значит, что что-то не то. Видим, что размер таблицы адресов импорта (поле Size) на этот раз всего 1C, но такой маленькой таблица импорта быть не может. Обычно размер таблицы адресов импорта составляет от 200h до 1000h. Нажав по плюсику слева от этой строки, в конце которой написано "NO", мы видим, что не определено ни одной функции. А обычно в раскрывающемся списке каждая строка содержит в себе название какой-либо функции. Мы окончательно убеждаемся, что адрес таблицы импорта и ее размер определены импреком неверно! Ну не беда, будем искать таблицу импорта сами! Я обычно в подобных случаях нахожу адрес таблицы импорта одним из трех способов. В процессе написания статьи я посоветовался с несколькими новичками, и они помогли мне выбрать на их взгляд более простой способ. Его я вам сейчас и опишу.
Итак, чтобы найти адрес начала таблицы импорта, давайте сначала порассуждаем. Мы знаем, что таблица импорта находится в программе, и при запуске программы она заполняется адресами используемых функций. Если сдампить запущенную программу, то и таблица импорта сохранится на диск внутри сдампленного файла. Теперь следите за мыслью ВНИМАТЕЛЬНО, обдумывая каждое мое предложение. В таблице импорта находятся адреса импортируемых функций, так? Есть функции, которые используются в каждой программе, так? Адрес одной из таких часто используемых функций тоже окажется в таблице импорта, правильно? В нашем дампе (dumped.exe) где-то должна быть таблица импорта, заполненная адресами используемых функций, так? А значит и адрес одной из часто используемых функций будет в этой таблице импорта, т.е. внутри нашего дампа, ведь так? Что если мы возьмем и посмотрим адрес одной из часто используемых Windows API функций и попробуем найти этот адрес в дампе!? Ведь если мы его найдем, то мы сможем узнать примерное начало таблицы импорта, ведь этот адрес должен лежать внутри этой таблицы! Сейчас все рассмотрим на примере. Тут нет ничего сложного.
Значит так, сперва нам надо выбрать какую-нибудь часто встречающуюся API функцию. Функция GetModuleHandleA встречается в 99%+ программ для Windows. Давайте узнаем адрес этой функции в системе. ВНИМАНИЕ, этот адрес может быть у всех разным. Для того, чтобы узнать адрес функции GetModuleHandleA в софтайсе наберем команду "exp GetModuleHandleA" и в ответ увидим следующее:
KERNEL32
 001B:77E79F93 GetModuleHandleA
 
Это говорит о том, что адрес функции GetModuleHandleA - 77E79F93. У вас этот адрес может быть другим (У меня Windows XP Professional, без сервис-паков). Теперь нам нужно найти этот адрес в дампе (dumped.exe). Воспользуемся для этого одним из Hex-редакторов. Я использовал HexWorkshop, чего и вам советую. Открываем файл в hex-редакторе и делаем поиск шестнадцатеричного значения (в HexWorkshop'e жмем F3 и выбираем Type -> Hex Values). Внимание! В строке поиска вы НЕ должны прямо вписывать адрес функции GetModuleHandleA! Этот адрес нужно вписывать в обратном порядке по байту. Т.е. я буду вписывать 939FE777. Если у вас адрес этой функции равен например 78123456, то вы в строке поиска пишите 56341278. В общем написали и жмем ОК для начала поиска. Первое совпадение у меня нашлось в файле по адресу 867D8 от начала файла, второе по адресу 86858, третье - 86940, четвертое - E961С, пятое EB310. У вас адреса могут не совпадать. Как же узнать, какой из этих адресов функции действительно находится внутри таблицы импорта, а какие к таблице импорта не относятся? Давайте внимательно посмотрим на первый найденный адрес по смещению 867D8 от начала файла. Вот как выглядит это место в шестнадцатеричном редакторе:
 0086760: 52950800 40780800 00000000 00000000
 0086770: 00000000 00000000 00000000 7532F577
 0086780: 00E3F777 1FE2F777 0899E777 349EE777
 0086790: 0A98E777 459AE777 8198E777 1A75E777
 00867A0: FC02E877 86C4E777 C47CE777 C578E777
 00867B0: EF77E777 44F0E777 2499E777 CE7CE777
 00867C0: 7246E777 EF3BE777 B805E877 217FE777
 00867D0: 7A17E677 FDA5E777[939FE777]99A0E777
 00867E0: 3C51E777 38C9E777 1806E877 9E5DE777
 00867F0: AA8EE777 B55CE777 493CE777 37ACE777
 
Не буду вас мучать, скажу, что это есть начало таблицы адресов импорта. Все эти числа (в моем примере начинающиеся с 7532F577 и заканчивающиеся 37ACE777) - это адреса функций, записанные в обратном порядке. (Большинство Windows API функций в Windows XP начинаются с адреса 77хххххх, и в таблице импорта их адреса находятся вместе, последовательно друг за другом. Поэтому я и сделал вывод, что именно первый найденный адрес функции GetModuleHandleA лежит внутри таблицы импорта). Первый адрес функции записан по адресу 8677С (выше идут нули) - это и есть начало таблицы импорта. На всякий случай, если не верите, можете прокрутить вверх - ничего похожего на записанные адреса функций вы там не увидите. Запомним адрес 8677С - это смещение от начала файла, а нам нужно узнать, какой адрес в памяти будет ему соотвествовать.
Запускаем LordPE, жмем PE Editor, затем выбираем наш dumped.exe и в появившемся окне жмем FLC, кликаем по кнопке offset и вводим напротив число 8677C, после чего жмем DO. В поле напротив кнопки RVA видим адрес 8717C - это и будет RVA начала таблицы импорта в памяти.
Для восстановления импорта запускаем упакованный PCMedik.exe, и вслед за ним ImpRec. Как всегда в ImpRec'е выбираем PCMedik.exe, в поле OEP вводим 82C2C. В поле RVA мы бы должны были написать 8717C - найденное нами (пока еще только предполагаемое) начало таблицы импорта, но я обычно ввожу несколько ранний адрес, на тот случай, что таблица импорта начинается все-таки раньше и я мог пропустить ее начало. Поэтому округлим в меньшую сторону и введем в поле RVA значение 87000 и в поле Size - 1000. Почему 1000? Да потому что мы не знаем точного размера таблицы импорта, но обычно он не превышает 1000h. Жмем Get Imports (а не Iat Autosearch, т.к. мы уже убеждались что автопоиск нам ничего не даст, поэтомы мы и нашли нужные значения сами и теперь сразу нужно жать Get Imports) и ... И видим много строк, заканчивающихся на NO, что свидетельствует о плохом результате. А ну ка давайте прокрутим вниз! В середине списка мы увидим целую кучу строк с именами dll, в конце которых написано YES! "Косячные" строки с NO на конце присутствуют из-за того, что мы неточно ввели начало таблицы импорта и ее размер, но ImpRec выделил нам нужные строки словом YES на конце, остальные же мы можем удалить! Выделяем все строки с NO на конце, тыкаем правой кнопкой и в меню выбираем Delete Thunk(s). Должны остаться только строки с YES на конце. Теперь, когда мы получили правильную таблицу импорта, ее осталось вживить в файл - жмем Fix Dump! Указываем наш dumped.exe и радуемся удачной распаковке PE Compact! Eсли это вам показалось трудным - не расстраивайтесь, все-таки это ваша первая распаковка. Буквально еще немного практики и на PE Compact у вас будет уходить не больше 5 минут! Но хватит о PE Compact, нас ждет распаковка ExeStealth'а !

Последний рубеж: ExeStealth

ExeStealth... Что же тут сказать.. с чего начать.. Начну с того, что новичкам будет трудно. Подготовленным - не очень. Вам придется познакомиться с понятием SEH, с моим не очень убедительным способом нахождения OEP и кнопочкой AutoTrace в ImpRec'e =) Приступим?
К сожалению, я не нашел ничего, что бы уже было упаковано ExeStealth'ом, и поэтому пришлось упаковывать свою программку. Калькулятор уже паковал, блокнот тоже. Для разнообразия, решив упаковать Delphi-программу, я за минуту написал "мегакрэкми" и упаковал его ExeStealth'ом. Качем его здесь, а пока читаем дальше.
Что представляет из себя ExeStealth? В отличие от предыдущих упаковщиков, ExeStealth не уменьшает размер программы, а оставляет его практически прежним. ExeStealth криптует (шифрует) программу и поэтому является криптером. Алгоритм работы у него примерно такой же как и упаковщика, только вместо сжатия программы он реализует набор действий, затрудняющих распаковку, а соотвественно дизассемблирование и патч программы. ExeStealth является относительно слабым криптером, в том смысле, что распаковать программу, защищенную ExeStealth'ом, нетрудно, и защиты от него немного. Разве что от новичков. Но что мы хотели от криптера? Криптер не призван дать абсолютную защиту программе, он лишь призван сделать ее чуть больше чем у обычного упаковщика. Криптеры используются, когда автор программы не хочет, чтобы его программу пропатчили, дизассемблировали или чтобы строки в его программе не лежали открытым текстом в exe файле. Как я уже сказал, такая защита прокатит только против новичков. Сделать же нормальную защиту для программы - это удел протектора. Протектор строится по такому же принципу, что и упаковщики/криптеры, однако полностью направлен на защиту программы от взлома и поэтому делает все, что можно, чтобы его было трудно распаковать и потом взломать программу. О протекторах можно говорить долго и много, но не будем отвлекаться, начнем разбираться с ExeStealth.
С чего же начать... Даже не знаю... Давайте чтоли прервемся на Entry Point и поставим брейкпоинт "bpm esp-4" =) Сделали? Когда брейкпоинт сработает первый раз мы должны будем оказаться тут:
 001B:00461060  EB00                JMP       00461062
 001B:00461062  60                  PUSHAD
 001B:00461063  EB00                JMP       00461065     <-- Оказались здесь
  ==> 00461065  E800000000          CALL      0046106A
 001B:0046106A  5D                  POP       EBP
 001B:0046106B  81EDD3264000        SUB       EBP,004026D3
 
Как видите далеко мы не упрыгали: брейкпоинт сработал спустя две команды, т.е. двумя строчками ниже Entry Point, и это явно не переход на OEP =) Так что жмем дальше F5 и прерываемся тут:
 001B:00461474  895C241C            MOV       [ESP+1C],EBX
 001B:00461478  8BBD852E4000        MOV       EDI,[EBP+00402E85] <-- Здесь
 001B:0046147E  037F3C              ADD       EDI,[EDI+3C]
 001B:00461481  8B9FC0000000        MOV       EBX,[EDI+000000C0]
 001B:00461487  83FB00              CMP       EBX,00
 001B:0046148A  740F                JZ        0046149B
 
Это тоже не переход OEP. Почему? Потому что нет никаких явных переходов или нашей любимой команды popad =) Когда вы будете поопытнее, вы будете находить прыжки на OEP даже если они выглядят нестандартно, а пока слушайте меня и ориентируйтесь на команды popad, jmp, push - ret, а так же смотрите на то место, куда они ведут. Если оно похоже на начало программы, то значит это действительно переход на OEP. Как узнать начало программы? Стандартное начало программ написанных на Visual C++ я уже показал в главе про UPX. В этой главе вы увидите стандартное начало программ написанных на Delphi. Опять отвлеклись, идем дальше, жмем F5 и прерываемся в следующем месте:
 001B:004617A1  61                  POPAD          <-- Знакомо?
 001B:004617A2  50                  PUSH      EAX  <-- Оказываемся тут
 001B:004617A3  33C0                XOR       EAX,EAX
 001B:004617A5  64FF30              PUSH      DWORD PTR FS:[EAX]
 001B:004617A8  648920              MOV       FS:[EAX],ESP
 001B:004617AB  EB01                JMP       004617AE     <-- "Левый" прыжок
 001B:004617AD  8700                XCHG      EAX,[EAX]
 001B:004617AF  0000                ADD       [EAX],AL
 
Уххх, сейчас будет трудно. Сначала я скажу вам, что это место скорого перехода на OEP. Чуть позже вы увидите почему, а пока можете ориентироваться по команде popad. Выделю следующие команды:
 001B:004617A2  50                  PUSH      EAX     <-- Установка обработчика ИС
 001B:004617A3  33C0                XOR       EAX,EAX
 001B:004617A5  64FF30              PUSH      DWORD PTR FS:[EAX]
 001B:004617A8  648920              MOV       FS:[EAX],ESP
 
Поясню, ИС - это сокращение от "исключительная ситауция", и приведенный здесь код - это установка обработчика исключительной ситуации. Сейчас попробую объяснить что это такое.
При работе с исключительными ситуациями и их обработкой постоянно используется термин SEH. SEH - это сокращение от Structured Exception Handling, т.е. структурированная обработка исключительных ситуаций. Примерами исключительных ситуаций (далее - ИС) могут быть деление на ноль, попытка доступа к невыделенной области памяти, попытка записи в системную область памяти, недопустимые арифметические операции (например попытка вычисления логарифма от нуля) и т.д. Обработчик операционной системы (далее - ОС) получает управление при возникновении ИС и решает, что дальше делать, после чего либо завершает программу, либо выполняет необходимые действия и передает управление обратно программе, либо (если видит что перед возникновением ИС в программе был установлен обработчик ИС) передает управление обработчику ИС в программе. Как понятно из того, что я только что написал, программа может сама указать свой обработчик ИС, однако этот обработчик будет получать управление уже из обработчика исключительной ситуации ОС. Если еще не поняли, то обработчик исключительной ситцуации - это какой-то код в программе или ядре ОС, который при возникновении исключительной ситуации делает необходимые действия и решает, как дальше работать программе, например делает прыжок на какой-то адрес в программе для продолжения ее работы. Как это мешает отладке? Программа, которая хочет помешать своей отладке устанавливает свой собственный обработчик ИС, после чего специально создает исключительную ситуацию. Если мы будем проходить по участку кода, где будет создаваться исключительная ситуация, то в этот момент попадем в обработчик ИС операционной системы. Переход на обработчик ИС, который был установлен из программы, произойдет в одном из call'ов внутри обработчика ИС ОС =) А если мы будем идти в отладчике, нажимая F10 (т.е. не входя в вызовы процедур и функций), то проскочим переход в обработчик, установленный программой, и возврат в программу, т.е. так сказать потеряемся =) В этом и трудность.
Уши не повяли? Если повяли, то слушаем то же самое только в упрощенном варианте =) Когда программа не хочет, чтобы ее дальнейшие действия (в нашем случае переход на OEP, т.е. фактически нахождение адреса OEP) проследили, то программа (точнее автор программы) делает специальную ошибку, например заведомо делит число на ноль. Но перед этим указывается участок в программе, который при возникновении этой ошибки будет выполнен. В этом участке обычно просто делается переход на дальнейший код программы, но неопытный крэкер, напоровшись на специально сделанную ошибку, на этот переход уже не попадет, отладчик его засунет в дебри операционной системы, и таким образом крэкер не сможет проследить, что дальше делается в программе!
Значит так, приведенные выше 4 строки - это установка обработчика исключительной ситуации. Так программа указывает адрес обработчика (т.е. с какого адреса потом начнется выполнение программы, если ниже возникнет исключительная ситуация). После команды MOV FS:[EAX],ESP идет защищенный код, в случае возникновения ошибки в котором, обработчик получит управление. Смотрим, что у нас ниже. А ниже у нас JMP 004617AE, который я обозвал "левым" прыжком. Смотрите, куда направлен этот переход: на адрес 4617AE, а по тому адресу находятся байты 00 00 (эти байты соотвествуют ассемблерной команде add [eax], al). Попытка выполнения этой команды приведет к ошибке, т.к. перед этим регистр eax обнуляется, и получится попытка записи в память по адресу 0. Т.е. возникает исключительная ситуация. Если вы дальше будете жать F10, то вас унесет в обработчик ИС операционной системы, и обратно вы уже не вернетесь. Таким образом программа хотела защититься от того, чтобы вы дальше проследили ее действия и нашли переход на OEP.
Что же делать? Дело в том, что при установке обработчика ИС, в стек кладется адрес этого обработчика. Обычно он кладется в стек перед командой PUSH DWORD PTR FS:[EAX]. В нашем случае это PUSH EAX. Значит, если поставить брейкпоинт на адрес EAX и нажать F5, то когда обработчик ИС получит управление, мы прервемся в нем и посмотрим, что он делает. Поэтому, когда будете находиться на PUSH EAX в указанном выше месте, то удаляйте все брейкпоинты, ставьте новый "bpx eax" и жмите F5 для продолжения выполнения программы. Не пройдет и секунды, как вы окажетесь в самом начале обработчика ИС:
 001B:00461751  55                  PUSH      EBP      <-- Начало обработчика ИС
 001B:00461752  8BEC                MOV       EBP,ESP
 001B:00461754  57                  PUSH      EDI
 001B:00461755  8B4510              MOV       EAX,[EBP+10]
 001B:00461758  8BB8C4000000        MOV       EDI,[EAX+000000C4]
 001B:0046175E  FF37                PUSH      DWORD PTR [EDI]
 001B:00461760  33FF                XOR       EDI,EDI
 001B:00461762  648F07              POP       DWORD PTR FS:[EDI]
 001B:00461765  8380C400000008      ADD       DWORD PTR [EAX+000000C4],08
 001B:0046176C  8BB8A4000000        MOV       EDI,[EAX+000000A4]
 001B:00461772  C1C707              ROL       EDI,07   <-- Внимательно
 001B:00461775  89B8B8000000        MOV       [EAX+000000B8],EDI
 001B:0046177B  B800000000          MOV       EAX,00000000
 001B:00461780  5F                  POP       EDI
 001B:00461781  C9                  LEAVE
 001B:00461782  C3                  RET
 
Мало понятного? Не пугайтесь! Главное, что тут задается адрес OEP. Думаете я конкретно разбирался во всем этом? Честно скажу, что нет. Я просто заметил одну вещь. Помню, давно еще, когда я первый раз распаковывал ExeStealth, для простоты нахождения ОЕП и распаековки, я запаковал свою же программу этим ExeStealth'ом. Естественно OEP своей же программы я знал. Я тогда понимал, что логически в этом обработчике ИС должен был бы указываться OEP, чтобы передать управление уже расшифрованной программе, ведь распаковка уже закончена, и для чего еще нужен был этот обработчик мне на ум не приходило. Поэтому я был уверен, что тут где-то должен был промелькнуть адрес OEP! Я внимательно начал идти по этому обработчику и...! И после выполнения команды ROL EDI, 07 я увидел в регистре EDI значение OEP !!! Что делает эта команда вы должны знать, или посмотрите в справочнике. Поставив брейкпоинт на значение регистра edi в этот момент, и нажав F5, я тут же прервался на OEP моей программы, что и следовало ожидать. В дальнейшем я проверил это еще на нескольких программах, и всегда распаковка была точно такой же, и в регистре EDI, после команды ROL EDI, 07, появлялся адрес OEP.
Так что, просто запомните это, разбираться и объяснять вам почему это так у меня нет возможности, да и вы наврятли поймете, так что не буду забивать вам мозги - просто запомните это.
Давайте опять доберемся до этого обработчика, и когда прервемся в его начале по нашему брейкпоинту, пойдем "пешком" до команды ROL EDI, 07. Выполним ее и запишем значение регистра EDI - 4504D4 - это и есть OEP. Удалим все старые брейкпоинты и поставим брейкпоинт на найденный OEP - "bpx 4504D4", после чего нажмем F5 и прервемся тут:
 001B:004504D4  55                  PUSH      EBP     <-- OEP, Мы здесь
 001B:004504D5  8BEC                MOV       EBP,ESP
 001B:004504D7  83C4F0              ADD       ESP,-10
 001B:004504DA  B864034500          MOV       EAX,00450364
 001B:004504DF  E89460FBFF          CALL      00406578
 001B:004504E4  A1201E4500          MOV       EAX,[00451E20]
 001B:004504E9  8B00                MOV       EAX,[EAX]
 001B:004504EB  E888E6FFFF          CALL      0044EB78
 001B:004504F0  8B0DF41E4500        MOV       ECX,[00451EF4]
 001B:004504F6  A1201E4500          MOV       EAX,[00451E20]
 001B:004504FB  8B00                MOV       EAX,[EAX]
 001B:004504FD  8B1564004500        MOV       EDX,[00450064]
 001B:00450503  E888E6FFFF          CALL      0044EB90
 001B:00450508  A1201E4500          MOV       EAX,[00451E20]
 001B:0045050D  8B00                MOV       EAX,[EAX]
 001B:0045050F  E8FCE6FFFF          CALL      0044EC10
 001B:00450514  E8033CFBFF          CALL      0040411C
 
Это освновная ветвь Delphi-программы. Первые 4 команды представляют из себя стандартное начало Delphi-программ, запомните их - в будущем вам будет легче находить начало программ, написанных на Delphi, ну а начало VC++ программ я уже приводил выше.
Ладно, OEP нашли, удостоверились, что это действительно OEP =) Теперь надо сдампить программу и восстановить импорт. Я привык дампить до перехода на OEP, а не находясь на первом адресе программы. Поэтому давайте сдампим в тот момент, когда будет устанавливаться обработчик ИС (последнее срабатывание "bpm esp-4") - я проверил, все будет работать, если сдампить в этот момент.
Значит добираемся до вот этого уже знакомого нам места:
 001B:004617A2  50                  PUSH      EAX     <-- Тут зацикливаем программу
 001B:004617A3  33C0                XOR       EAX,EAX
 001B:004617A5  64FF30              PUSH      DWORD PTR FS:[EAX]
 001B:004617A8  648920              MOV       FS:[EAX],ESP
 
После этого зацикливаем программу, вводя в софтайсе "а", "jmp eip", Enter. Жмем F5 и... СТОП!
Дело в том, что в ExeStealth присутствует защита от сдампливания программы! LordPE сдампливает только первые 4Кб программы, а ProcDump запарывается при попытке снять дамп. Четко тут работает только PE Tools, которые мы перед этим забраковали и отдали предпочтение LordPE. Надеюсь, что в ближайшем будущем автор PE Tools исправит эту ошибку. Но в данном случае только PE Tools нам и поможет. Однако дампить надо немного изменив настройки. Пока у нас программа висит в памяти зацикленная, запускаем PE Tools и выбираем в меню Options -> Set Options. В отделе Task Viewer поставьте только две галочки: Full dump: paste header from disk и Full dump: fix header. Теперь жмете правой кнопкой по нашему зацикленному процессу crackme.exe и выбираете Dump Full... Сохраните дамп как "dumped.exe". После чего выделяем в списке наш crackme.exe и жмем Del для завершения процесса: он нам больше не нужен. Программу мы сдампили, осталось восстановить импорт.
Для восстановления импорта запускаем еще раз crackme.exe и, как всегда, вслед за ним ImpRec. В списке процессов выбираем crackme.exe (Как мне надоело повторять это по 100 раз, но боюсь что среди вас все еще присутствуют "счастливчики", которые забывают или не понимают что делать). Вам уже не должно составлять труда найти RVA OEP, поэтому в поле OEP вводим 504D4 и жмем IAT AutoSearch. Видим табличку Found Something. (Я посмотрел в словарике, это переводится как "Найдено что-то" =). Жмем Get Imports и... И видим кучу строк с NO на конце, что говорит о том, что не все (в нашем случае вообще ни одной) функции адреса которых записаны в таблице импорта распознались.
Что же делать? Придется мне вам объяснять еще одну штуку. Дело в том, что в некоторых криптерах и почти во всех протекторах используются такие "штуки" как переходники. В общем слушайте. Вместо того, чтобы в таблице импорта были записаны адреса настоящих функций, используемых в программе, криптер/протектор записывает туда адреса, по которым находятся переходники. Смотрите, например в исходном состоянии в таблице импорта, когда программа запущена, записан адрес функции GetModuleHandleA, например это адрес 77E79F93. Криптер/протектор же записывает туда адрес например 143188, а по адресу 143188 находится команда jmp 77E79F93 (прыжок на GetModuleHandleA). Таким образом криптер/протектор делает переходники для таблицы импорта, чтобы затруднить восстановление импорта. Затруднить это может только новичка, и я вам сейчас докажу почему.
Если раскроете одну из веток в списке ImpRec'а, то увидите там набор строк типа RVA: 00053104 ptr:00143188. Это значит, что в таблице импорта по адресу 453104 находится число 143188, т.е. адрес переходника. Нажмите по этой строке правой кнопкой и выберите Disassemble/Hex View. Вы увидите что-то типа такого:
 00143188    jmp 77F53275    // = ntdll.dll/01EA/RtlDeleteCriticalSection
 0014318D    jmp 77F7E300    // = ntdll.dll/02A3/RtlLeaveCriticalSection
 00143192    jmp 77F7E21F    // = ntdll.dll/020F/RtlEnterCriticalSection
 00143197    jmp 77E79908    // = kernel32.dll/0203/InitializeCriticalSection
 0014319C    jmp 77E79E34    // = kernel32.dll/0359/VirtualFree
 
Это переходники на API функции Windows и другие функции, используемые в программе. Нам надо сделать, чтобы вместо адресов переходников в таблице импорта были записаны прямые адреса используемых функций. Как это сделать? К счастью, ImpRec это конечно же предусмотрел. Если нажать кнопку Auto Trace в ImpRec'e, то он попытается автоматически определить адреса используемых функций, на которые указывают переходники и составить нормальную таблицу импорта, без участия переходников криптера. Жмем Auto Trace и через 5 секунд видим, что таблица импорта восстановлена без проблем: напротив всех строк написано YES.
Вот практически и все. Жмем Fix Dump, выбираем dumped.exe, запускаем dumped_.exe и видим, что программа запустилась без проблем, т.е. мы ее распаковали. С ExeStealth покончено, последний рубеж пересечен.

Пора закгругляться...

Возможно, распаковка, в частности PE Compact и ExeStealth, показалась вам сложной, возможно вы ожидали, что будет легче, возможно наоборот. В любом случае, я старался объяснить все по максимуму, разжевать все и не упустить ни единой мелочи. Я надеюсь, что у меня получилось, и что распаковка не вызвала у вас много вопросов. Если вы все-таки чего-то не поняли, то возможно дело не в статье, а в вас? Может рано еще браться за распаковку? Опыта с 3-5 крэкми недостаточно. Но я буду надеяться на лучшее, буду надеяться, что моя статья не показалась вам китайской грамотой, и благодаря ей вы научились распаковывать эти простые упаковщики и криптеры. Как всегда, мне хотелось бы знать ваше мнение. Поэтому если вам не трудно, чирканите мне пару строчек и сообщите, удалась ли статья или наоборот только запудрила вам мозги. Мой email по-прежнему mozgc @ mozgc.com (пробелы в адресе email убрать).
Жду ваших отзывов.

На будущее. Для общего развития.

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

1. Иногда после распаковки программы в Windows 2K/XP, она не будет работать в Win 9x/Me и будет писать ошибку, связанную с функцией RestoreLastError. Дело в том, что в Win 2K/XP есть две функции - RestoreLastError и SetLastError, идентичные по своему содержанию (точнее имеют разные имена но даже одинаковый адрес в системе). Однако ImpRec, при восстановлении импорта, выбирает функцию RestoreLastError. В Win 9x/Me нет функции RestoreLastError, есть только функция SetLastError. Поэтому, если возникает такая ошибка, то нужно либо перевосстановить импорт, и в ImpRec'e найти в списке и заменить функцию RestoreLastError на SetLastError; либо просто в .exe файле найти строку RestoreLastError, заменить ее на SetLastError и конец строки забить байтом 00h в хекс-редакторе.
2. Как я писал в статье, можно дампить и после перехода на OEP (в статье мы всегда дампили до этого перехода), но в этом случае небходимо будет подправить дамп после его сохранения на диск. Дело в том, что когда мы зацикливаем программу, то в памяти поверх текущей команды мы записываем команду "jmp eip". Соотвественно, когда мы потом сдампим программу, то в дампе на этом месте будут байты EB FE (код jmp eip), но ведь исходно там было что-то другое, относящееся к программе. Поэтому необходимо будет найти это место и в хекс-редакторе заменить байты EB FE на исходные. Когда мы дампим до перехода на OEP, то ничего восстанавливать не надо, т.к. команда jmp eip записывается поверх какой-то команды в коде распаковщика. А о коде распаковщика мы можем не волноваться. После распаковки он выполняться уже никогда не будет.
3. В самом начале главы про распаковку UPX, я сказал, что в незапакованном виде файл занимал 90+ Кб. Однако вы могли заметить, что после нашей распаковки файл стал занимать 100+ Кб. Почему? Почему не исходные 90+ Кб? А потому что UPX при упаковке программы дописывал свой код, который потом распаковывал программу при запуске. Когда мы дампили программу, то естественно, что и код распаковщика тоже сохранился в дампе. В исходной незапакованной программе его не было. Поэтому размер нашей программы после упаковки UPX'ом и нашей распаковки стал чуть больше, чем размер исходной незапакованной программы.
4. В начале статьи я упомянул про inline патчи. Они патчат прямо упакованную программу! Это не всегда возможно, однаком в случае с простейшими упаковащиками типа UPX и ASPack это реально. Вкратце, принцип inline-патчей таков, что они изменяют переход от тела распаковщика к OEP на переход на какой-то адрес в теле файла (обычно это конец файла или конец какой-либо секции, где много нулей и где можно записать свой код). По этому адресу прописывается код, который при выполнении патчит программу в памяти, и после этого делает прыжок на OEP. Таким образом программу можно пропатчить не распаковывая, т.е. сделать крэк, который бы патчил прямо упакованную программу.

Приветы. Благодарности.

Передаю приветы ВСЕМ TSRh мемберам, а также следующим людям: MoonShiner, Hex, Dragon, Angel_aka_K$, -=ALEX=-, всем тем, кто тестировал статью во время ее написания и помогал сделать ее понятнее и лучше, а также V0land / TSRh за предоставленное место для хранения файлов, используемых в статье, на платном хостинге.

Всем счастливо! MozgC [TSRh] (c) 2003
TSRh TeaM Web Site
TSRh TeaM Forum
Cr@ckL@B Forum


Обсуждение статьи: Распаковка: от самого простого к чуть более сложному >>>


Комментарии к статье: Распаковка: от самого простого к чуть более сложному

MozgC 29.07.2004 00:23:21
Парни, у кого остались файлы, используемые в статье, в частности упакованные 4 программы, просьба обязательно связаться со мной по адресу MozgC @ xtin.org . Спасибо.
---
bigm 27.02.2005 17:42:20
Народ у кого остались файлы которые были упакованы, Пришлите приз! bigm87@rambler.ru
---
Nikolaj образца 1943 года 20.04.2005 20:54:25
Начинающий , но Все Равно СПАСИБО !!
С Уважением Nik !!
---
junk 15.07.2005 07:45:31
Прекрасная статья на 6 баллов!
Подолжай в том же духе.
Удачи всем
---
RUslan 28.08.2005 10:28:16
Статья супер.
---
RUslan 28.08.2005 10:31:20
При ручной распаковки я softice не пользуюсь, на мой взглят легче распаковывать при помощи OLLY.
---
buka 05.03.2006 18:58:48
MozgC 4-ever
---
moloch 10.06.2006 21:32:39
Ссылки на подопытные файлы не работают, а жалко...
---
Steam 24.08.2006 11:06:09
Статья - СУПЕР! ВСё написано ясно и понятно
---
Wal 25.08.2007 23:17:33
В очередной раз хочу поблагодарить автора статьи за интересный материал. Распаковывал калькулятор запакованный UPX v3.00 - все получилось, хоть и не сразу. Так глядишь и крякать можно научиться и ассемблер по ходу дела изучить :)) Для новичка это очень хорошо.


Еще хотелось бы внести несколько поправок учитывая сегодняшние реалии, и в особенности то, что калькулятор под 98-ой мастдай многим найти сложно. Я паковал калькулятор из XP, который был написан на Visual C 7.0. Насколько понял с этим были связаны некоторые несовпадения процесса описанного в статье и того, что происходил на моем компе.

Итак, запаковав calc.exe из Windows XP и проделав действия до команд eb eip 0x60 и bc * дальше не стоит торопиться вводить команду bpm esp-4, потому что она скорее всего не сработает. Перебором возможных вариантов наиболее подходящей оказалась команда bpm esp-6. Именно при ее использовании, и при условии что включено окошко команд (через команду wc) или отображается достаточное количество строк можно будет чуть выше места прерывания увидеть заветную ассемлерную команду popad.

Дальше опять, кода приводимого в статье:

:01018658 FF96D48B0100 call dword ptr [esi 00018BD4]
:0101865E 61 popad
:0101865F E97C93FFFF jmp 010119E0

не будет! Будет несколько другой код:

01020E7D: POPAD ; !!!!!!!!
01020E7E: LEA EAX, [ESP-80]
01020E82: PUSH 00
01020E84: CMP ESP, EAX
01020E86: jnz 01020E82
01020E88: SUB ESP, 80
01020E8B: JMP 01012475 ;!!!!!!!!!!!


Как видите происходит заполнение 0х80 - ти байт стека нулями и только потом безусловный переход. Адрес фигурирующий в качестве аргумента в команде безусловного перехода и есть тот самый OEP. Перейдя по нему опять же нельзя увидеть "стандартное" начало программ на VC . Видимо версия другая, поэтому и код будет другой. Начинается он с команд

push 70
push 010015Е0
call 010127C8

Итак OEP = 01012475.


Еще маленькое дополнение для избежания лишней траты нервов :))) Для ввода ассемблерной команда вводим "a" потом enter, затем команду, затем дважды enter. Может это лишнее, но я сам долго над этим копался. И наконец последнее из того что не уточнено в статье и что может сбить столку таких как я :)) Вызывать зацикливание программы нужно при достижении команды безусловного перехода на OEP, а не после этого перехода! То есть в моем случае вводить команду JMP EIP нужно когда мы перешли на команду JMP 01012475, а не после выполнения этой команды и перехода на push 70. В противном случае программа сдампится и LordPE адреса функций в ней настроит, но потом программа просто не запустится.


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


P.S.: Вообще после довольно геморной установки SoftICE на XP потом этот отладчик еще и часто систему подвешивает. Надо бы научиться юзать Olly.
---
Ovca 29.08.2008 19:52:11
Классная статья! Все разжеванно хорошо - самое оно для таких как я! Только про импорт не совсем понятно, у меня когда windowsXP пытается загрузить сдамплинную прогу с НЕвостановленным импортом то выдает ошибку, ему ведь нужен указатель на символьное имя ипмортируемой функции, а вместо етого там указатели на адрес функции в памямяти, или я чтото не так понимаю?
---
RU-REBEL 19.12.2008 00:59:07
Добавлю от себя - IMPREC не всегда корректно расставляют флаги доступа к секциям. Если вы например сняли UPX, а даже после фикса импорта вываливается в винду с сообщениями об ошибке, то дело может быть в том, что секция с данными (в случае с UPX она обычно называется UPX1) не помечена как writeble. Лезьте в LordPE и исправляйте
---
DJ_ALEX 01.02.2010 20:54:49
киньти файло со стати на alex555cs@yandex.ru
ссылки ..я то нерабочие
---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS