Раз уж я закончил разработку библиотеки на C++ для работы с PE-файлами, грех не использовать ее в каком-то более-менее серьезном проекте. Поэтому я разработаю с ее помощью упаковщик, поясняя по шагам, что я делаю, а либа на C++ сильно упростит нам жизнь. Итак, с чего же начать разработку упаковщика? Наверное, с выбора какого-нибудь несложного бесплатного алгоритма сжатия. После непродолжительных поисков таковой был мной найден: LZO. Он поддерживает множество различных видов сжатия (можно считать, разновидностей), и LZO1Z999 - самая эффективная по степени сжатия из всех доступных. Это, конечно, не ZIP, но приближается к нему по эффективности: 550-килобайтный файл был сжат zip'ом с максимальной степенью сжатия в 174 килобайта, в то время как LZO сжал тот же файл до 185 килобайтов. Однако у LZO гораздо более быстрый распаковщик. Он также оказался базонезависимым, то есть, его можно разместить по любому виртуальному адресу, и он будет работать без всяких корректировок адресов. Размер распаковщика приятно удивил: Visual Studio с оптимизациями по размеру и отключением исключений и проверок буферов дала результат в 613 байтов кода! Этот алгоритм нам подойдет.
Я начну написание с самых простых упаковщика и распаковщика, постепенно усложняя их. Для начала просто напишем программку, которая загружает PE-файл с помощью моей библиотеки. Упаковщик будем делать для x86-файлов, т.е. за PE+ пока что браться не будем. Итак, сначала вам необходимо будет скачать и скомпилировать в Visual Studio 2008 или 2010 мою библиотеку для работы с Portable Executable. После того, как вы это сделаете, следует создать новый проект. Я назвал его simple_pe_packer и положил в ту же папку, где лежит библиотека:
Настройки компиляции проекта должны совпадать с настройками компиляции библиотеки, иначе не слинкуется:
Для Debug-конфигурации, соответственно, выставим Multi-threaded Debug (/MTd). Теперь необходимо добавить в солюшен проект библиотеки LZO, чтобы было, чем упаковывать данные. Я скачал с сайта автора библиотеку lzo-2.06, распаковал ее в папку с таким же именем в каталоге с моей библиотекой для работы с PE (см. самый первый скриншот), после чего добавил в солюшен simple_pe_packer проект lzo-2.06, добавив в него все *.c и *.h-файлы из каталога lzo-2.06. Не забываем снова выставить настройки компиляции, как на втором скриншоте. Установим у проекта simple_pe_packer зависимость от проекта lzo-2.06 (правой кнопкой мыши - Project Dependencies, если вы используете английскую студию, конечно). Далее, чтобы lzo-2.06 собралось, необходимо добавить include-директорию:
Эту директорию добавляем и в Release, и в Debug-конфигурацию, разумеется. Теперь вернемся к проекту simple_pe_packer. Здесь мы тоже добавим include-директорию:
Она указывает на место, где лежат заголовочные файлы моей библиотеки для работы с PE-файлами. Если вы разложили всё так же, как и я, то у вас пути совпадут с моими. Если нет, то смотрите, как всё лежит у вас.
Теперь мы полностью переходим к проекту simple_pe_packer. Добавляем к файлам исходных кодов новый файл main.cpp, в котором будет код нашего упаковщика. Для начала его код будет таким:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//Заголовки для работы с файлами и консолью #include <iostream> #include <fstream> //Заголовочный файл библиотеки для работы с PE-файлами #include <pe_32_64.h> //Заголовочный файл алгоритма LZO1Z999 #include "../../lzo-2.06/include/lzo/lzo1z.h" //Директивы для линкования с собранными библиотеками PE и LZO #ifndef _M_X64 #ifdef _DEBUG #pragma comment(lib, "../../Debug/pe_lib.lib") #pragma comment(lib, "../Debug/lzo-2.06.lib") #else #pragma comment(lib, "../../Release/pe_lib.lib") #pragma comment(lib, "../Release/lzo-2.06.lib") #endif #else #ifdef _DEBUG #pragma comment(lib, "../../x64/Debug/pe_lib.lib") #pragma comment(lib, "../x64/Debug/lzo-2.06.lib") #else #pragma comment(lib, "../../x64/Release/pe_lib.lib") #pragma comment(lib, "../x64/Release/lzo-2.06.lib") #endif #endif //Пока что пустая функция main int main(int argc, char* argv[]) { return 0; } |
Это программа, которая совершенно ничего не делает. Однако, следует ее скомпилировать, чтобы убедиться, что все пути настроены верно. Если компиляция прошла успешно, идем дальше. Далее я буду приводить только обновляемый код функции main или его части. Сделаем еще одно небольшое действие - откроем x86 PE-файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
int main(int argc, char* argv[]) { //Говорим пользователю, как использовать наш упаковщик //На текущем шаге никаких опций упаковки не будет, просто //необходимо будет запускать упаковщик, передав через командную строку //имя файла, который мы хотим упаковать if(argc != 2) { std::cout << "Usage: simple_pe_packer.exe PE_FILE" << std::endl; return 0; } //Открываем файл - его имя хранится в массиве argv по индексу 1 std::ifstream file(argv[1], std::ios::in | std::ios::binary); if(!file) { //Если открыть файл не удалось - сообщим и выйдем с ошибкой std::cout << "Cannot open " << argv[1] << std::endl; return -1; } try { //Пытаемся открыть файл как 32-битный PE-файл //Последние два аргумента false, потому что нам не нужны //"сырые" данные привязанных импортов файла и //"сырые" данные отладочной информации //При упаковке они не используются, поэтому не загружаем эти данные pe32 image(file, false, false); //Оповестим пользователя, что файл считан успешно std::cout << "File OK" << std::endl; } catch(const pe_exception& e) { //Если по какой-то причине открыть его не удалось //Выведем текст ошибки и выйдем std::cout << e.what() << std::endl; return -1; } return 0; } |
Осталось скомпилировать этот код и запустить его для проверки. Запустим полученный exe-файл будущего упаковщика в консоли, передав ему его же имя для теста:
Как видим, переданный PE-файл успешно открылся и считался. В следующем уроке мы перейдем непосредственно к простейшей упаковке и напишем на MASM32 (или на Си, я еще не решил) стаб распаковщика. А сейчас двинемся дальше. В начале статьи я написал, что алгоритм распаковки LZO1Z999 базонезависим и занимает всего 613 байтов. Как же получить бинарный вариант алгоритма? Давайте создадим новую конфигурацию проектов и назовем ее ReleaseDecompressor - она будет предназначена исключительно для того, чтобы собрать процедуру распаковщика. Делается это через меню Configuration Manager, в левом меню выбираем New..., вводим имя, выбираем Copy settings from: Release и ставим галку Create new project configurations:
Далее переходим к свойствам проекта lzo-2.06. Во вкладке Configuration Properties - General меняем тип исполняемого файла (Configuration type) на Application (.exe). Далее выделяем все .c-файлы в проекте, кроме lzo1z_d1.c (именно в нем содержится реализация нужного нам распаковщика), и заходим в их свойства. Исключаем их из сборки:
Должна получиться такая картина:
Теперь заходим в настройки файла lzo1z_d1.c - того, который мы оставили в сборке. На вкладке C/C++ - Optimization выбираем оптимизацию по размеру (Optimization - Minimize size (/O1)), далее выбираем Favor Size Or Speed - Favor Small code (/Os). Вернемся теперь снова к настройками проекта lzo-2.06, перейдем на вкладку C/C++ - Code Generation. Отключим C++-исключения (Enable C++ Exceptions - No), отключим проверку буферов (Buffer Security Check - No (/GS-)). Далее, на вкладке Linker - Manifest File отключим генерацию манифеста (Generate Manifest - No (/MANIFEST:NO)). На вкладке Linker - Debugging отключим генерирование отладочной информации (Generate Debug Info - No). На вкладке Linker - System можно выставить подсистему Windows (SubSystem - Windows), но это не играет особой роли. На вкладке Linker - Advanced ставим точку входа (Entry Point - lzo1z_decompress), чтобы никакие CRT к результирующему бинарнику не подключались. На этом все, теперь можно собрать проект lzo-2.06. В результате получим маленький (размером 1.5 кб) exe-файл. Открыв его в каком-нибудь PE-просмотрщике, например, в CFF Explorer'е, увидим, что у него нет ни одной директории, что не может не радовать. Нет импортов, нет релокаций (хотя мы их не отключали) - алгоритм полностью базонезависим! Можно увидеть, что виртуальный размер единственной секции с кодом у файла - 0x265 (или 613 байтов):
Уверен, поковырявшись с настройками сборки еще немного, можно уменьшить размер распаковщика еще на сотню байтов. Полученный бинарный код распаковщика мы будем потом использовать в своем алгоритме распаковки PE-файлов.
На этом все, до следующего шага!
Для желающих выкладываю готовый проект со всеми установленными настройками и необходимыми файлами (однако, библиотеку для работы с PE вам придется скачать и собрать самостоятельно, расположив проект библиотеки так, как описано в начале статьи): own-packer-step1
Попробовал перейти по ссылке:
http://kaimi.io//kaimi.io/wp-content/uploads/2012/09/22.png
... ))) прикольная видяха
)))
... В print варианте просмотра
Исправлено
Спасибо, вот это действительно интересно, т.к. в перспективе можно развивать и развивать, конечно потрясно было бы видеть степень сжатия, как у Upack, но пока и так нормально. Можно будет прикрутить пару антиотладочных фич, интересный ГУИ и поддержку командной строки.
Еще раз спасибо!
Очень интересно, жду продолжения.
Кстати да, DX, может поднимешь вопрос о повышении степени сжатия, или добавлять несколько алгоритмов сжатия (aPlib, LZMA,Zlib) и опцию --brute, чтобы автоматом прогонял по алгоритмам и использовал уже наилучший результат.
По поводу антиотладки - можно добавить пару простых хитростей, к примеру:
invoke CloseHandle,011h
invoke CloseHandle,012h
invoke CloseHandle,018h
invoke CloseHandle,019h
invoke CloseHandle,01Ah
Вызовет исключение при закрытии невалидного хендла из ринг0, если отладчик не читает обращения к кривым хендлам.
Или Апоксовский АнтиОлли:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
Alloc dd ?
OLDProtect dd 02040001h
msgTitle db "Execution status:",0h
msgText1 db "No debugger detected!",0h
msgText2 db "Debugger detected!",0h
.code
start:
; MASM32 antiOlly example
; coded by ap0x
; Reversing Labs: ap0x.headcoders.net
; The idea is simple. OllyDBG interprets PAGE_GUARD as a
; Memory break-point. If we set SEH and execute PAGE_GUARDed
; code exception will occure. If debugger is present it
; will execute MemBpx and continue executing code after it.
; If debugger is not present handleing will be forwarded to SEH.
; Setup SEH
ASSUME FS:NOTHING
PUSH offset @Check
PUSH FS:[0]
MOV FS:[0],ESP
; Allocate new space
PUSH PAGE_READWRITE
PUSH MEM_COMMIT
PUSH 10000h
PUSH 0
CALL VirtualAlloc
; Write RET there
MOV BYTE PTR[EAX],0C3h
MOV DWORD PTR[Alloc],EAX
; Place Memory break-point
PUSH offset OLDProtect
PUSH PAGE_EXECUTE_READ OR PAGE_GUARD
PUSH 00000010h
PUSH EAX
CALL VirtualProtect
; Execute
CALL [Alloc]
PUSH 30h
PUSH offset msgTitle
PUSH offset msgText2
PUSH 0
CALL MessageBox
PUSH 0
CALL ExitProcess
; SEH handler
@Check:
POP FS:[0]
ADD ESP,4
PUSH 40h
PUSH offset msgTitle
PUSH offset msgText1
PUSH 0
CALL MessageBox
PUSH 0
CALL ExitProcess
end start
Я сейчас не ставлю цель сделать идеальный алгоритм сжатия, я нашел достаточно хороший, который, по заверениям автора, работает в разы быстрее существующих аналогичных. А антиотладку, может, и добавлю потом какую-нибудь.
Согласен с тобой, на счет скорости и размера загрузчика - тут не поспоришь.
Это я так - на долгосрочные перспективы.
Было бы удобно - в этом цикле статей можно совместить инфу для новичков, любителей и профи. И статей за пять - развить полноценный достойный опенсорс пакер\прот.
А потом положить на этот опенсорс, т.к. смысл исключительно обучающий, а длительно заниматься развитием нет смысла по некоторым причинам.
Или на этом:
Code:
call GetTickCount
mov ds:dword_0_9AE104, eax
_label:
call GetTickCount
cmp eax,ds:dword_0_9AE104
jz _label
Функция возвращает 0 и получаем бесконечный цикл, старая ИдаСтелс падала.
КАИМИ:
А потом положить на этот опенсорс, т.к. смысл исключительно обучающий, а длительно заниматься развитием нет смысла по некоторым причинам.
Ну и что? Да, положить, не положить - а выложить на SF или на git. И все, кому надо - те продолжат. а так да - обучающий характер и получение кайфа от процесса созидания)
Если так, то причем тут антиотладка и алгоритмы сжатия? Статья ведь обучающая, будет описано, как происходит интеграция алгоритма в пакер. Хочется другой алгоритм? Берите сорс и добавляйте свои алгоритмы.
Хочется антиотладку? --//-- и добавляйте антиотладку.
Вот upx - пример правильно упаковщика, в частности, из-за того, что есть опция распаковки. Упаковщик же пишем, а не протектор.
Согласен. Но на счет алгоритмов сжатия я упомянул к тому, что всегда хотел понять - как добиваются ТАКОЙ степени сжатия, и почему UPX, являясь стандартом сжатия де-факто - не может догнать тот же упомянутый Upack или FishPP. Поэтому и задал вопрос, подумая, что DX тоже заинтересуется. Антиотладка - это ерунда, если нужно - прикрутить без проблемм, а вот со степенью сжатия - действительно не так все просто.
Автор upack - профессионал в области сжатия информации, да и то в его пакере LZMA использовался помнится плюс достигался небольшой выигрыш за счет хранения части данных в PE заголовке, а так берется IDA, берется upack, рипается алгоритм сжатия (если там что-то отличное от LZMA) и всё.
будет использован пе лоадер или в код пе файла будет встраиваться распаковщик и после распаковки джамп на распакованный код?
Второе
это вариант, но вы упомянули upx как эталон пе пакера, а в нем ведь есть лоадер, и скажу, оправданно
Упомянул я, а цикл статей пишет dx.
Можно вопрос?
Вот моя папка с проектом:
lzo-2.06
pe_lib main.cpp
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(55): error C2065: pe32: необъявленный идентификатор
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(55): error C2146: синтаксическая ошибка: отсутствие ";" перед идентификатором "image"
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(55): error C3861: image: идентификатор не найден
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(60): error C4430: отсутствует спецификатор типа - предполагается int. Примечание. C++ не поддерживает int по умолчанию
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(60): error C2143: синтаксическая ошибка: отсутствие "," перед "&"
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(64): error C2065: e: необъявленный идентификатор
2>c:\_temp\_kaimi\step01\simple_pe_packer\simple_pe_packer\main.cpp(64): error C2228: выражение слева от ".what" должно представлять класс, структуру или объединение
2> тип: 'unknown-type'
2>
2>СБОЙ построения.
Не подскажете где накосячил?
... а нет , сори не дочитал, нужно использовать библиотеку
с сорсами версии 0.1.11
Но теперь такая ошибка (((
2>LINK : fatal error LNK1104: не удается открыть файл "../../Debug/pe_lib.lib"
Проверь пути, какая проблема. Библиотеку тоже, кстати, нужно собрать, если ты этого еще не сделал.
Спасибо за ответ. А можно на форуме зарегистрироваться?
Чего-то письмо на ящик от Вас не приходит (((
Попробуй зайди под sheva740 с паролем который использовал при регистрации
>Попробуй зайди под sheva740 с паролем который использовал при регистрации
Не выходит. Можно Перерегистрироваться?
... и потом при регистрации никто пароль задавать не просит,
так что пароль я и не вводил , поэтому не знаю
... ok зарегистрировался как mbele
))
Здравствуйте, DX.
В ходе построения упаковщика (пустая ф-ция main) у меня возникла следующая ошибка:
2>------ Построение начато: проект: simple_pe_packer, Конфигурация: Debug Win32 ------
2>Построение начато 23.09.2013 17:01:48.
2>InitializeBuildStatus:
2> Обращение к "Debug\simple_pe_packer.unsuccessfulbuild".
2>ClCompile:
2> main.cpp
2>LIBCMTD.lib(wincrt0.obj) : error LNK2019: ссылка на неразрешенный внешний символ _WinMain@16 в функции ___tmainCRTStartup
2>C:\Users\user1\Documents\Visual Studio 2010\Projects\portable_executable_library\simple_pe_packer\simple_pe_packer\Debug\simple_pe_packer.exe : fatal error LNK1120: 1 неразрешенных внешних элементов
2>
2>СБОЙ построения.
В чём дело и как можно исправить? Жду вашего совета.
Впрочем, проблема наблюдается и при компиляции нового пустого проекта.
Прошу извинить за глупый вопрос, напутал с подсистемой.
Проблема в том, что надо было создавать консольный проект Win32, а не оконное приложение.
dx, с использованием библиотеки LZO проблема, ее лицензия - GNU GPL, а это значит, что использование ее в ПО с закрытым исходным кодом без разрешения автора нарушает лицензию.
Ты знаешь альтернативу этому алгоритму, сравнимую по скорости и степени сжатия?
Я не писал с ее использованием закрытого ПО, поэтому вопросов не возникало. Наверное, со схожей эффективностью можно заюзать zip/gzip (правда, там придется париться с переделкой кода распаковщика, чтобы он стал базонезависимым, скорее всего, если не найти такую готовую реализацию).
Добрый день не получается собрать библиотеку lzo-2.06
error LNK2005: __setargv already defined in dict.obj
error LNK2005: _lzo_wildargv already defined in dict.obj
error LNK2005: _main already defined in dict.obj
И так 40 ошибок!
Попробуй скачать готовый архив с проектами в конце статьи и собрать оттуда.
Спасибо большое!
Так собралось без ошибок!
Вроде по шагам все делал, error C1083: Не удается открыть файл включение: pe_lib/pe_bliss.h: No such file or directory
если не трудно, скиньте папочку с проектом который собирается
PE bliss - это библиотека для работы с PE-файлами. В статье написано, где ее взять и какой версии (https://code.google.com/archive/p/portable-executable-library/). Если цель не обучаться самостоятельно и не разбираться с проблемами, то проще взять проект или собранный упаковщик из последнего урока.
Добрый день! Возможно глупый вопрос будет, но я не нахожу в версии 1.0.0 хедера "pe_32_64.h", есть ли там аналог данного хедера? Или в данному случае годится только версия 0.1.11?
В первом шаге нужно использовать старую версию библиотеки, да. В каком-то из шагов я явно указал, когда перевел код на версию 1.0.0 (кажется, где-то в одном из последних).
Добрый день! Еще один вопрос, при компиляции lzo, а также unpacker (шаг 3) у меня появляется вторая секция .rdata, да и vitrual size .text больше. В чем может быть причина? Я сначала думал, что что-то упустил, но затем скачал ваш архив own-packer-step1, но там у меня такие же результаты. Использую MSVS 2015.
Хотя в принципе мне не мешают, но всё равно интересно.
Я компилировал всё в студии 2010, вероятно, причина в смене компилятора.