spo2_curs_Pyatkovskyx

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ
РОССИЙСКОЙ ФЕДЕРАЦИИ
ГОУ НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ
ИМ. Р.Е. АЛЕКСЕЕВА
ИНСТИТУТ РАДИОЭЛЕКТРОНИКИ И ИНФОРМАЦИОННЫХ
ТЕХНОЛОГИЙ
Кафедра «Вычислительные системы и технологии»
Дисциплина: «Системное программное обеспечение»
Пояснительная записка
к курсовой работе
Тема:
«Средства защиты программ методом шифрования»
Выполнил:
студент группы 10-В-1
Пятковский М.О.
Проверил:
Кочешков А. А.
Нижний Новгород
2012
Оглавление
1. Постановка задачи .....................................................................................................................3
2. Анализ задачи ............................................................................................................................4
2.1 Поиск функциональн близких аналогов..............................................................................10
3. Разработка алгоритмов............................................................................................................12
3.1 Алгоритм шифрации/дешифрации ......................................................................................12
3.2 Добавление новой секции. ....................................................................................................13
3.3 Расширение последней секции. ............................................................................................13
4. Описание программы ..............................................................................................................14
4.1 Файловой состав. ...................................................................................................................14
4.2 Блок-схема работы загрузчика: ............................................................................................15
4.3 Интерфейс пользователя. ......................................................................................................16
4.4 Возможности развития программы. ....................................................................................16
5. Тестирование программы .......................................................................................................17
6. Ограничения и системные требования ..................................................................................22
Заключение...................................................................................................................................23
Список использованной литературы .........................................................................................24
Приложение 1...............................................................................................................................25
Приложение 2...............................................................................................................................33
1. Постановка задачи
Разработать метод создания защищенных программ.
Программы хранятся в файлах в зашифрованном виде. При запуске на выполнение
программа запрашивает пароль, расшифровывает свой образ в памяти и выполняется.
2. Анализ задачи
.EXE (сокр. англ. executable — исполнимый) — расширение исполнимого файла,
применяемое в системах DOS, Windows, Symbian OS, OS/2 и в некоторых других. Кроме
объектного кода, может содержать различные метаданные (ресурсы, цифровая подпись).
Форматы .EXE:
MZ — 16-битный формат, основной формат файлов .EXE в DOS.
EXE-файлы для Windows и OS/2 используют другие форматы для основной части
программы, но всё равно начинаются с заглушки в формате MZ, которая при попытке
запустить файл в DOS выводит сообщение This program cannot be run in DOS mode. («Эту
программу невозможно запустить в режиме DOS»).
NE — 16-битный формат, использовался в Windows 3.x[2], OS/2 и MS-DOS.
LE — смешанный 16- и 32-битный формат, ранее использовался в OS/2 и Windows (VxD).
LX — 32-битный формат, используется в OS/2.
PE — 32- и 64-битный переносимый формат, используется в современных версиях
Windows начиная с Windows NT и Windows 95.
Общий вид PE-файла.
PE-файл в самом своем начале содержит программу для ОС DOS. Эта программа
называется stub и нужна для совместимости со старыми ОС. Если мы запускаем PE-файл
под ОС DOS или OS/2 она выводит на экран консоли текстовую строку, которая
информирует пользователя, что данная программа не совместима с данной версией ОС.
Программист при линковке может указать любую программу DOS, любого размера. После
этой DOS-программы идет структура, которая называется IMAGE_NT_HEADERS. Эта
структура определена так:
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
Первый элемент IMAGE_NT_HEADERS – сигнатура PE-файла. Для PE-файлов она
должна иметь значение IMAGE_NT_SIGNATURE. Далее идет структура, которая
называется файловым заголовком и определенная как IMAGE_FILE_HEADER. Файловый
заголовок содержит наиболее общие свойства для данного PE-файла. После файлового
заголовка идет опциональный заголовок - IMAGE_OPTIONAL_HEADER32. Он содержит
специфические параметры данного PE-файла. В конце опционального заголовка
содержится массив элементов DataDirectory. Он служит для доступа к некоторым
объектам, которые могут быть секциями (о секциях далее), а могут и не быть. В общем
случае эти объекты называются – директориями. После опционального заголовка
начинается таблица секций. В ней содержится информация о каждой секции. После
таблицы секций идут исходные данные для секций. В конец PE-файла можно записать
любую информацию и от этого функционирование программы не изменится (если там не
присутствует проверка контрольной суммы и т.п.).
Терминология применимая для файлов PE-формата
Секция – непрерывный набор страниц памяти с одинаковыми атрибутами. Бывают секции
кода, данных, ресурсов и т.д. Обычно данные делятся на секции, если предполагается, что
они будут использоваться одинаковым образом, т.е. например, только для чтения или
только для записи. Также, данные могут делиться на секции в зависимости от того, что
представляют из себя эти данные, например ресурсы или таблица импорта. В общем
случае может быть, например 12 секций с одинаковыми атрибутами, и используемые для
кода. Мы вправе сами создавать секции, указывая это компиляторам.
VA (Virtual Address) – виртуальный адрес. Адрес в адресном пространстве текущего
процесса.
RVA (Relative Virtual Address) – относительный виртуальный адрес. При загрузке PEфайла, ОС использует механизм файлового мэппинга (FileMapping). Т.е. она проецирует
данный exe, dll, sys или scr-файл по какому-то адресу в виртуальном адресном
пространстве. Адрес начала проекции называется базовым адресом в памяти данного exe,
dll, sys или scr-файла. А смещение относительно базового адреса называется –
относительным виртуальным адресом. Например, EXE-файл спроецирован по адресу
400000H. Тогда если PE-заголовок находится по адресу 4000E0H, то RVA PE-заголовка
будет E0. В PE-заголовке очень много параметров указываются через RVA. А если RVA
начала инструкций в файле есть 1000H, то виртуальный адрес будет равен 401000H,
учитывая, что база 400000H. Чтобы посчитать относительный виртуальный адрес по
данному виртуальному адресу используется следующая формула:
RVA = VA - IMAGE_OPTIONAL_HEADER.ImageBase
IAT – таблица адресов импорта. Массив двойных слов, содержащих RVA импортируемых
функций.
INT – таблица импортируемых имен. Массив двойных слов, каждое из которых является
RVA на ASCIIZ-строку с импортируемой функцией.
DOS-MZ заголовок
В начале файла располагается DOS-MZ заголовок. Он определен следующим образом:
typedef struct _IMAGE_DOS_HEADER {
WORD
e_magic;
WORD
e_cblp;
WORD
e_cp;
WORD
e_crlc;
WORD
e_cparhdr;
WORD
e_minalloc;
WORD
e_maxalloc;
WORD
e_ss;
//
//
//
//
//
//
//
//
//
DOS .EXE header
Magic number
Bytes on last page of file
Pages in file
Relocations
Size of header in paragraphs
Minimum extra paragraphs needed
Maximum extra paragraphs needed
Initial (relative) SS value
WORD
e_sp;
WORD
e_csum;
WORD
e_ip;
WORD
e_cs;
WORD
e_lfarlc;
WORD
e_ovno;
WORD
e_res[4];
WORD
e_oemid;
WORD
e_oeminfo;
WORD
e_res2[10];
LONG
e_lfanew;
} IMAGE_DOS_HEADER
//
//
//
//
//
//
//
//
//
//
//
Initial SP value
Checksum
Initial IP value
Initial (relative) CS value
File address of relocation table
Overlay number
Reserved words
OEM identifier (for e_oeminfo)
OEM information; e_oemid specific
Reserved words
File address of new exe header
Все что нас интересует здесь - это только одно значение - e_lfanew. Это двойное слово
является RVA и указывает на структуру IMAGE_NT_HEADERS. Размер DOS-MZ
заголовка составляет 80 байт.
Файловый заголовок
Файловый заголовок находится в PE-файле сразу же после сигнатуры
IMAGE_NT_SIGNATURE. В файле WINNT.H она определена как 00004550H. Файловый
заголовок содержит наиболее общую информацию о данном файле. В файле WINNT.H
файловый заголовок определен следующим образом:
typedef struct _IMAGE_FILE_HEADER {
WORD
Machine;
WORD
NumberOfSections;
DWORD
TimeDateStamp;
DWORD
PointerToSymbolTable;
DWORD
NumberOfSymbols;
WORD
SizeOfOptionalHeader;
WORD
Characteristics;
} IMAGE_FILE_HEADER;
WORDMachine - два байта содержащие платформу, для которой создавался данный PEфайл.
WORD NumberOfSections - количество секций в PE-файле. Значение должно быть
верным. Фактически означает число элементов в таблице секций.
DWORD TimeDateStamp - информация о времени, когда был собран данный PE-файл.
DWORD PointerToSymbolTable - указатель на COFF-таблицу символов PE-формата. Эту
же информацию можно найти в элементе массива DataDirectory с индексом
IMAGE_DIRECTORY_ENTRY_DEBUG.
DWORD NumberOfSymbols - количество символов в COFF-таблице символов. Может
принимать любое значение.
WORD SizeOfOptionalHeader - размер опционального заголовка. Опциональный
заголовок следует сразу же за файловым заголовком. Размер опционального заголовка
зависит от массива DataDirectory, а именно от количества элементов в нем.
WORD
файла.
Characteristics - характеристики – это атрибуты специфичные для данного PE-
Опциональный заголовок
В опциональном заголовке хранится более специфическая информация о приложении и
его потребностях.
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Стандартные поля
//
WORD
BYTE
BYTE
DWORD
DWORD
DWORD
DWORD
DWORD
DWORD
Magic;
MajorLinkerVersion;
MinorLinkerVersion;
SizeOfCode;
SizeOfInitializedData;
SizeOfUninitializedData;
AddressOfEntryPoint;
BaseOfCode;
BaseOfData;
//
// дополнительные поля NT
//
DWORD
ImageBase;
DWORD
SectionAlignment;
DWORD
FileAlignment;
WORD
MajorOperatingSystemVersion;
WORD
MinorOperatingSystemVersion;
WORD
MajorImageVersion;
WORD
MinorImageVersion;
WORD
MajorSubsystemVersion;
WORD
MinorSubsystemVersion;
DWORD
Win32VersionValue;
DWORD
SizeOfImage;
DWORD
SizeOfHeaders;
DWORD
CheckSum;
WORD
Subsystem;
WORD
DllCharacteristics;
DWORD
SizeOfStackReserve;
DWORD
SizeOfStackCommit;
DWORD
SizeOfHeapReserve;
DWORD
SizeOfHeapCommit;
DWORD
LoaderFlags;
DWORD
NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
DWORD AddressOfEntryPoint - адрес, с которого начинают считываться инструкции для
выполнения. Адрес является RVA. Чтобы указать на адрес ниже базового можно
использовать отрицательные значения, т.е. в дополнительном коде. По-другому это
называется - целочисленное переполнение.
DWORD ImageBase - при запуске PE-файла он будет отображен по частям, начиная с
некоторого адреса в памяти. Адрес отображения называется базовым адресом для данного
файла. В данном поле хранится базовый адрес PE-файла. Этот файл естественно является
VA. От него отсчитываются все RVA. Еcли файл не загружается по каким-то причинам
(по этому адресу память уже зарезервирована) по данному адресу, то загрузчику
необходимо применять базовые поправки. Обычно файл загружается по базовому адресу
и базовые поправки не нужны. Это позволяет использовать базовые поправки в своих
целях. Для компоновщиков, по умолчанию устанавливается базовый адрес 400000H.
DWORD SectionAlignment - секция при загрузке PE-файла в память будет начинаться с
адреса кратного данной величине. Вот ограничения данного поля. 1) Это значение
представляет собой степень двойки. 2) SectionAlignment>=FileAlignment.
DWORD FileAlignment - эта величина соответствует смещению секций в файле. Размер
каждой секции кратен данной величине. Вот ограничения данного поля: 1) Это значение
представляет собой степень двойки. 2) Должно быть между 200Hи 10000H. 3)
SectionAlignment>=FileAlignment.
DWORD SizeOfImage - содержит общий размер всех частей отображения. Важно, что
загрузчик проверяет значение этого поля по следующей формуле:
SizeOfImage = VirtualSize + VirtualAddress
DWORD SizeOfHeaders - размер заголовков. Вычисляется по формуле:
SizeOfHeaders = DOS Stub + PE Header + Object Table
Кратно значению FileAlignment. Должно быть корректным.
DWORD CheckSum контрольная сумма образа файла. Для обычных исполняемых файлов
контрольная сумма не проверяется, т.е. может быть любой. Если она нулевая, то она тоже
может быть любой. Для всех системных DLL должна быть корректная.
IMAGE_DATA_DIRECTORY DataDirectory
[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - массив структур типа
IMAGE_DATA_DIRECTORY. Это структура определена следующим образом:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD
VirtualAddress; //RVA директории
DWORD
Size;//Размер директории
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Каждый элемент массива указывает на какую-либо структуру, например на таблицу
импорта. Т.е. каждый элемент это информация о директории, каждая из которых несет
собой определенную смысловую нагрузку. Определенный индекс в массиве соответствует
определенной директории. Директория может быть секцией, а может и не быть секцией,
т.е. быть ее частью. Если нам надо найти, например таблицу экспорта, то обращаемся к
элементу 0 этого массива. Вот полный перечень всех индексов:
#define IMAGE_DIRECTORY_ENTRY_EXPORT
#define IMAGE_DIRECTORY_ENTRY_IMPORT
#define IMAGE_DIRECTORY_ENTRY_RESOURCE
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION
#define IMAGE_DIRECTORY_ENTRY_SECURITY
#define IMAGE_DIRECTORY_ENTRY_BASERELOC
#define IMAGE_DIRECTORY_ENTRY_DEBUG
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
архитектуры
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR
указателей
#define IMAGE_DIRECTORY_ENTRY_TLS
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
при загрузке
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
#define IMAGE_DIRECTORY_ENTRY_IAT
адресов (IAT)
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
0
1
2
3
4
5
6
7
// Директория экспорта
// Директория импорта
// Директория ресурсов
// Директория исключений
// Директория безопасности
//Таблица базовых поправок
// Отладочная директория
//Данные специфичные для
8
// RVA глобальных
9
10
// TLS директория
// Директория конфигурации
11
12
// Директория Bound-импорта
// Таблица импортированных
13
14
//Дескриптор delay-импорта
// COM Runtime дескриптор
Таблица секций
Таблица секций – это база данных, для всех секций используемых в PE-файле. Сразу
после окончания опционального заголовка следует таблица секций. В PE-файле
теоретически может быть сколько угодно секций. Все они могут иметь одинаковые
атрибуты и даже одинаковые имена(!), кроме секции ресурсов. Но обычно секции делят
либо по их логическому предназначению, либо по атрибутам. Имена секций вообще
никого не волнуют и нигде не проверяются (почти). Загрузчик ориентируется на массив
DataDirectory в опциональном заголовке, для того чтобы найти нужные данные. Это
сделано в целях оптимизации, чтобы не сравнивать строки, а просто перейти сразу же к
нужной директории с помощью соответствующих индексов. Таблица секций это массив
элементов типа IMAGE_SECTION_HEADER. Этот тип определен следующим образом:
typedef struct _IMAGE_SECTION_HEADER {
BYTE
Name[8];
union {
DWORD
PhysicalAddress;
DWORD
VirtualSize;
} Misc;
DWORD
VirtualAddress;
DWORD
SizeOfRawData;
DWORD
PointerToRawData;
DWORD
PointerToRelocations;
DWORD
PointerToLinenumbers;
WORD
NumberOfRelocations;
WORD
NumberOfLinenumbers;
DWORD
Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
BYTE
Name[8] - Название секции.
DWORD VirtualSize – для ЕХЕ-файлов содержит виртуальный размер секции. Т.е. это
размер, выровненный на SectionAlignment.
DWORD VirtualAddress – это поле содержит адрес, куда загрузчик должен отобразить
секцию. Это поле является RVA.
DWORD SizeOfRawData – это поле содержит размер секции, выровненный на
ближайшую верхнюю границу размера файла.
DWORD PointerToRawData - файловое смещение, откуда берутся исходные данные для
секции при отображении.
DWORD Characteristics - это поле содержит атрибуты секции. Атрибуты секции
указывают на права доступа к ней, а также на некоторые особенности влияния на нее
загрузчика. Флаги секций могут преобразовываться загрузчиком в атрибуты страниц и
сегментов. Это поле всегда не равно нулю.
Таблица импорта
Импорт в PE-файлах – это механизм позволяющий использовать функции или
переменные из модулей отличных от данного. Если наша программа вызывает функцию
GetMessage, которая находится в библиотеке KERNEL32.DLL, то вместо инструкции
CALL используется инструкция JMP DWORD PTR [XXXXXXXX]. Адрес указанный как
XXXXXXXX находится где-то в таблице импорта.
Оверлей (применительно к PE-файлу) – это та часть файла, которая находится на диске
(не проецируется в память системным загрузчиком), но может считываться самой
программой через обычные функции файлового ввода-вывода. Обычно в оверлей
помещают отладочную информацию, различные сертификаты и т.д.
Для шифрации исполняемого файла нужно записывать в него код дешифратора, который
будет при загрузке перехватывать управление на себя, расшифровывать образ в памяти и
передавать управление на основную программу.
Существует множество различных методов внедрения в PE EXE, но вот основные три:
1) внедрение в заголовок;
2) расширение последней секции;
3) добавление новой секции.
При первом способе код записывается в пустое пространство между последним
элементом таблицы секций и смещением первой секции. При этом размер файла не
изменяется. В двух других случаях размер файла увеличивается. Но свободного места
между первой секцией и таблицей импорта не всегда достаточно для загрузки в него кода
загрузчика. Поэтому были выбраны второй и третий варианты.
2.1 Поиск функциональн близких аналогов
1. AsProtect – протектор, написанный отечественным программистом – Алексеем
Солодовниковым. Он создал действительно мощную систему защиты.
Основные преимущества этого протектора:
1.1. Защита кода программы с помощью очень стойких криптоалгоритмов.
1.2. Защита таблицы импорта (в последних версиях протектора защита позаимствована из
Obsidium).
1.3. Предоставление служебных функций для проверки регистрационных ключей и для
многого другого.
1.4. Использование виртуальной машины, при помощи которой защищается главная ветвь
кода программы.
1.5. Упаковка файла.
1.6. Хорошая техническая поддержка.
1.7. Сильная интеграция в код программы, очень серьезно мешающая при распаковке –
приходится изощряться любыми способами и восстанавливать недостающий код.
2. Armadillo – довольно старый протектор, содержащий некоторые революционные
технологии. Его основные преимущества:
2.1. Защита кода программы с применением очень стойких криптоалгоритмов.
2.2. Защита таблицы импорта.
2.3. Предоставление служебных функций для проверки регистрационных ключей (в
данном протекторе используется плохой алгоритм - были найдены "дыры", а после этого
были "закейгенены" приложения, защищенные Armadillo и использующие функции
проверки регистрационных ключей) и многих других.
2.4. Упаковка файла.
2.5. Применение технологии CopyMem II, которая ни за что не позволит реверс-инженеру
получить весь код программы (сразу :), а только если докопировать недостающие куски
программы по мере надобности.
2.6. Применение технологии Nanomites. При защите эта опция является самой мощной.
3. ExeCryptor 2 – по словам авторов, данная защита неломаема. Взлом этой защиты
является очень затратным (люди, написавшие инструмент для снятия защиты, просят за
него от $1000). Однако на эти деньги можно получить неплохую гарантию того, что
программа не будет взломана.
Преимущества:
3.1. Применение виртуальной машины.
3.2. Обработка кода метаморфным движком (это главное преимущество).
3.3. Защита кода программы с применением очень стойких криптоалгоритмов.
3.4. Защита таблицы импорта.
3. Разработка алгоритмов
3.1 Алгоритм шифрации/дешифрации
Для шифрования файлов используется довольно простой алгоритм:
Пусть х – поток кодов из файла, у – поток кодов из введённого пароля
1. Для байта из х выполняется команда xor с байтом из у.
2. Для байта из у выполняется команда and с байтом 0Fh, оставляя значимыми
младшие 8 бит
3. Для байта из х выполняется команда циклического сдвига ror, сдвигая биты вправо
на число позиций, полученное в пункте 2.
4. Когда из у берется последний байт, происходит переход к первому, т.е. байты
берутся по кругу.
5. Так происходит до тех пор, пока не возьмется последний байт из х
Для дешифрации используется обратный алгоритм:
1. Для байта из у выполняется команда and с байтом 0Fh, оставляя значимыми
младшие 8 бит
2. Для байта из х выполняется команда циклического сдвига rol, сдвигая биты влево
на число позиций, полученное в пункте 1.
3. Для байта из х выполняется команда xor с байтом из у.
4. Когда из у берется последний байт, происходит переход к первому, т.е. байты
берутся по кругу.
5. Так происходит до тех пор, пока не возьмется последний байт из х
Дешифрация происходит вне зависимости от введенного пароля, поэтому нужно каким-то
образом контролировать правильность ввода. Хранить в файле пароль, который
использовался при шифрации очень ненадежно, поэтому для проверки используется
контрольная сумма из первых 32 байт, начинающихся с адреса EntryPoint+5 (сразу после
команды вызова загрузчика). Если сумма расшифрованных байт совпадает с контрольной
суммой, то происходит передача управления основной программе, если нет – вызывается
ExitProcess.
Для внедрения кода загрузчика в исполняемый файл используются два метода (на выбор
пользователя):
1) Добавление новой секции
2) Расширение последней секции
3.2 Добавление новой секции.
1. Выравнивается конец файла в зависимости от файлового смещения (заполняется
нулями)
2. Создается новая секция с параметрами:
a. имя секции: .cdata
b. VirtualAddress = VirtualAddress последней секции + выровненный
размер последней секции относительно SectionAlignment
c. PointerToRawData = конец файла
d. VirtualSize и SizeOfRawData = размер модуля загрузчика
e. Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |
IMAGE_SCN_MEM_WRITE
3.
4.
5.
6.
7.
8.
Записывается код загрузчика в конец файла
Записывается созданная секция в таблицу секций
Изменяются первые 5 байт точки входа на вызов загрузчика
Увеличивается число секций
Очищается директория BOUND импорта
Корректируется размер образа
3.3 Расширение последней секции.
1. Записывается код загрузчика в конец последней секции
2. Изменяются первые 5 байт точки входа на вызов загрузчика
3. Изменяются параметры последней секции:
a. SizeOfRawData += размер загрузчика
b. VirtualSize += размер загрузчика
c. Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |
IMAGE_SCN_MEM_WRITE
4. Корректируется размер образа
4. Описание программы
4.1 Файловой состав.
Программа состоит из двух модулей:
1) pecrypt.cpp – написан на языке С++
2) inject.asm – написан на языке ассемблера
pecrypt.cpp – главный модуль, содержит в себе:
1. графический интерфейс программы
2. функции:
2.1 CheckFileFormat() – проверяет валидность формата РЕ, а так же считывает и
выводит информацию о файле из РЕ-заголовка
2.2 WindowProc() – обрабатывает сообщения всех элементов окна программы.
2.3 InitClass() – регистрирует класс главного окна.
2.4 WinMain() – создает главное окно и все элементы в нём, открывает файл,
переданный через командную строку, если таковой имеется, а так же содержит
цикл обработки сообщений Windows.
3. макрос ALIGN_UP(x,y) для расчета выровненного в большую сторону значения х
относительно у
inject.asm – модуль загрузчика, внедряемого в файл, который создает консольное окно для
ввода пароля, расшифровывает код и если контрольные суммы совпали – передает
управление программе, если нет – выходит с помощью ExitProcess (блок-схема на Рис.1)
Так же модуль содержит процедуры:
– вычисляет адрес загрузки dll в памяти
2. GetGetProcAddress – получает адрес функции GetProcAddress из kernel32.dll
3. SetAccess – для установки прав доступа для секций
1. GetBase
Компилируется программа в среде Microsoft Visual Studio 2008, включающей Microsoft
Macro Assembler для трансляции модуля, написанного на ассемблере
4.2 Блок-схема работы загрузчика:
вызов загрузчика
получение адресов необходимых функций
ввод пароля
дешифрация
да
контрольные суммы
совпадают?
нет
выход
передача управления
основной программе
Рис. 1
4.3 Интерфейс пользователя.
Рис. ХХХ – Главное окно программы
При нажатии кнопки Open выводится диалог выбора файла, его открытие для чтения и
вывод информации в Log.
При нажатии кнопки START анализируется корректность введённого пароля, создается
копия исходного файла и открывается для чтения и записи; вычисляется контрольная
сумма, по которой будет определяться корректность расшифрованного файла; происходит
шифрация и в зависимости от выбранного метода в файл записывается загрузчик, который
будет запрашивать пароль и расшифровывать свой образ в памяти.
При нажатии кнопки Test запускается зашифрованное приложение для проверки его
работоспособности.
При перетаскивании файла на окно программы происходят действия идентичные нажатию
кнопки Open, только вместо диалога выбора файла сразу открывается выбранный файл.
4.4 Возможности развития программы.
1. Использование более стойких алгоритмов шифрации
2. Привязка к конкретному компьютеру – использование в качестве ключа для
шифрования серийный номер жесткого диска и т.п.
3. Использование антиотладочных приемов
4. Использование алгоритмов сжатия исполняемого кода и данных программы
5. Поддержка формата РЕ64 и др.
6. При верно введённом ключе единожды – сохранять его, например в реестре, для того
чтобы не вводить пароль каждый раз при запуске.
5. Тестирование программы
1. Попробуем зашифровать сам шифратор:
Вводим пароль 1234, метод внедрения загрузчика – добавление новой секции.
После того, как в лог выведется надпись “Done.” нажмимаем клавишу Test, чтобы
запустить зашифрованный файл из программы.
Появляется окно с просьбой ввести пароль:
Вводим 1234. Запускается копия нашего шифратора с именем PE-Cryptor-C.exe
Откроем опять его же в нём самом.
2. Видим, что появилась новая секция .cdata. Попробуем зашифровать его с помощью
расширения последней секции. Вводим пароль 55555 и жмём START.
Запускаем… Снова появляется окно с вводом пароля. Введём 1234. Программа закрылась,
т.к. не совпали пароли
3. Попробуем зашифровать файл, в котором большое кол-во секций.
В данном случае – 13. Жмём START.
Появляется уведомление о том, что в файле нет места для новой секции, файл будет
зашифрован с расширением последней секции.
4. Откроем такой файл
Видим, что файл содержит оверлей и что его размер 5112 байт. Попробуем зашифровать с
расширением последней секции.
Появляется сообщение о том, что файл содержит данные, которые будут утеряны если
использовать этот метод внедрения. Предлагается выбор – оставить этот метод, выбрать
метод добавления новой секции, или отменить задание.
Оставим этот метод, нажмём да.
Программа запускается нормально, но в процессе работы она может читать данные из
оверлея. И т.к. там уже находится другая информация (наш загрузчик), то дальнейшее её
поведение не определено.
5. Откроем стандартный калькулятор из 64-битной версии Windows
Программа сообщает нам, что файл соответствует формату РЕ64. Шифрация не
поддерживается.
6. Попробуем зашифровать такой файл
Файл не запускается, т.к. у него таблица импорта находится в двух секциях
7. Открываем файл .NET
Шифрация не возможна
При вводе пароля короче 4 символов выдается ошибка:
6. Ограничения и системные требования
1) Формат файлов: только РЕ32
2) PE32.NET: не поддерживается
3) Количество шифруемых секций: не более 16
4) Таблицы импорта и ресурсов должны располагаться не более, чем в одной секции
каждая
5) Длина пароля: 4 - 32 символа
Системные требования:
Microsoft Windows NT/2000/XP/Vista/7, 8 MB ОЗУ, 16 Кб свободного места на диске,
клавиатура, мышь.
Заключение
В рамках данной курсовой работы был изучен метод создания защищенных программ
методом шифрования. Также была разработана программа, реализующая два метода
внедрения кода в исполняемые файлы. Программа прошла тестирование и отвечает
требованиям, поставленным в задании.
Список использованной литературы
1. Мэтт Питрек. «Секреты системного программирования в Windows 95».
2. П.В.Румянцев. «Исследование программ Win32 до дизассемблера и отладчика».
3. Джеффри Рихтер. «Программирование в Win32 API для Windows NT 3.5 и Windows'95»
4. Интернет ресурс http://www.wasm.ru/
5. Интернет ресурс http://www.insidepro.com/
6. Интернет ресурс http://www.xakep.ru/
7. Интернет ресурс http://msdn.microsoft.com/
8. Интернет ресурс http://www.wikipedia.org
Приложение 1
pecrypt.cpp
#include <windows.h>
#pragma comment(linker,"/manifestdependency:\"type='win32'
name='Microsoft.Windows.Common-Controls' version='6.0.0.0'
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment(linker,"/SECTION:.text,RWE")
#define ALIGN_UP(x,y) ((x+(y-1))&(~(y-1)))
#define MAX_SECTIONS 16
#define MAX_PASSWORDLEN 32
MSG msg;
HINSTANCE hInst;
HWND hwnd, hst1, hst2, hst3, hst4, hfname, hlog, hpwd, hmethod, hopen,
hstart, htest, hgroup;
HANDLE hf;
OPENFILENAME ofn;
IMAGE_DOS_HEADER idh;
IMAGE_FILE_HEADER ifh;
IMAGE_OPTIONAL_HEADER ioh;
IMAGE_SECTION_HEADER ish, mySection, LastSection;
DWORD dwBuf, dwLen, dwFileSize, dwOverlaySize, SectionTableAddr,
EntryPointAddr, ImpRVA, ImpSize, RsrcRVA, RsrcSize;
char buf[1024], EmptyBuffer[512], szFileName[512], szNewFileName[512],
szNewName[] = "-C.exe";
BYTE CodeBuffer[8192];
BYTE mySectionName[8] = ".cdata";
BYTE NewEntryBytes[5] = { 0xE8, 0, 0, 0, 0 };
extern "C" BYTE EntryBytes[5];
extern "C" DWORD InjectStart;
extern "C" DWORD InjectSize;
extern "C" DWORD CheckSum;
extern "C" DWORD SecCount;
extern "C" DWORD SecAddr[MAX_SECTIONS];
extern "C" DWORD SecSize[MAX_SECTIONS];
DWORD SecRawAddr[MAX_SECTIONS];
BOOL InjectMethod;
void InsertText( char* s )
{
SendMessage( hlog, EM_SETSEL, INT_MAX, INT_MAX );
SendMessage( hlog, EM_REPLACESEL, 0, (LPARAM)s );
}
void CheckFileFormat( void )
{
SendMessage( hfname, WM_SETTEXT, 0, (LPARAM)szFileName );
hf = CreateFile( szFileName, GENERIC_READ, FILE_SHARE_READ, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( hf == INVALID_HANDLE_VALUE ) {
SendMessage( hlog, WM_SETTEXT, 0, (LPARAM)"Unable to open the file."
);
hf = 0; return;
}
ReadFile( hf, &idh, sizeof(IMAGE_DOS_HEADER), &dwLen, 0 );
if( idh.e_magic == 'ZM' ) { // "MZ"
SetFilePointer( hf, idh.e_lfanew, 0, FILE_BEGIN );
ReadFile( hf, &dwBuf, sizeof(DWORD), &dwLen, 0 );
if( dwBuf == IMAGE_NT_SIGNATURE && dwLen ) { // "PE"
ReadFile( hf, &ifh, sizeof(IMAGE_FILE_HEADER), &dwLen, 0 );
ReadFile( hf, &ioh, sizeof(IMAGE_OPTIONAL_HEADER), &dwLen, 0 );
if( ioh.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC ) {
SendMessage( hlog, WM_SETTEXT, 0, (LPARAM)"File Type:
Portable Executable 64" );
CloseHandle( hf ); hf = 0; return;
}
ImpRVA =
ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
ImpSize = ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
RsrcRVA =
ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
RsrcSize =
ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size;
wsprintf( buf, "File Type: Portable Executable 32\r\n" );
wsprintf( buf, "%sFile size: %u bytes\r\n\r\n", buf, dwFileSize =
GetFileSize( hf, NULL ) );
wsprintf( buf, "%sPE Header:\r\n", buf );
wsprintf( buf, "%sEntry point RVA: %08X\r\n", buf,
ioh.AddressOfEntryPoint );
wsprintf( buf, "%sImage Base: %08X\r\n", buf, ioh.ImageBase );
wsprintf( buf, "%sImage Size: %X (%u)\r\n", buf, ioh.SizeOfImage,
ioh.SizeOfImage );
wsprintf( buf, "%sImport RVA: %08X\r\n", buf, ImpRVA );
wsprintf( buf, "%sImport Size: %X (%u)\r\n", buf, ImpSize,
ImpSize );
wsprintf( buf, "%sRsrc RVA: %08X\r\n", buf, RsrcRVA );
wsprintf( buf, "%sRsrc Size: %X (%u)\r\n", buf, RsrcSize,
RsrcSize );
wsprintf( buf, "%sFile Alignment: %X (%u)\r\n", buf,
ioh.FileAlignment, ioh.FileAlignment );
wsprintf( buf, "%sSection Alignment: %X (%u)\r\n", buf,
ioh.SectionAlignment, ioh.SectionAlignment );
wsprintf( buf, "%sNumber of sections: %u\r\n\r\n", buf,
ifh.NumberOfSections );
SectionTableAddr = idh.e_lfanew + sizeof(IMAGE_NT_HEADERS);
SendMessage( hlog, WM_SETTEXT, 0, (LPARAM)buf );
SecCount = 0;
for( unsigned i = 0; i < ifh.NumberOfSections; ++i ) {
ReadFile( hf, &ish, sizeof(IMAGE_SECTION_HEADER), &dwLen, 0
);
wsprintf( buf, " %.8s\r\n", ish.Name );
wsprintf( buf, "%sVirtual Address: %08X\r\nVirtual Size: %X
(%u)\r\n", buf, ish.VirtualAddress, ish.Misc.VirtualSize,
ish.Misc.VirtualSize );
wsprintf( buf, "%sRaw Address: %08X\r\nRaw Size: %X
(%u)\r\n", buf, ish.PointerToRawData, ish.SizeOfRawData, ish.SizeOfRawData );
InsertText( buf );
if( !ish.SizeOfRawData || !ish.Misc.VirtualSize || SecCount >
MAX_SECTIONS ) continue;
if( ioh.AddressOfEntryPoint - ish.VirtualAddress <
ish.Misc.VirtualSize )
// Получаем физическое смещение точки входа
EntryPointAddr = ish.PointerToRawData +
ioh.AddressOfEntryPoint - ish.VirtualAddress;
if( ish.VirtualAddress == RsrcRVA ) continue;
if( ImpRVA - ish.VirtualAddress < ish.Misc.VirtualSize )
continue;
SecRawAddr[SecCount] = ish.PointerToRawData;
SecAddr[SecCount] = ish.VirtualAddress;
SecSize[SecCount++] = ish.SizeOfRawData <
ish.Misc.VirtualSize? ish.SizeOfRawData: ish.Misc.VirtualSize;
}
if( dwOverlaySize = dwFileSize - ish.PointerToRawData ish.SizeOfRawData ) {
wsprintf( buf, "Overlay Size: %X (%u)\r\n", dwOverlaySize,
dwOverlaySize );
InsertText( buf );
}
else InsertText( "No Overlay\r\n" );
if(
ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress ) {
InsertText( "\r\nFile is .NET Assembly" );
CloseHandle( hf ); hf = 0;
} else {
InsertText( "\r\nPRESS START TO ENCRYPT IT !" );
// Сохраняем первые 5 байт с точки входа
SetFilePointer( hf, EntryPointAddr, 0, FILE_BEGIN );
ReadFile( hf, &EntryBytes, 5, &dwLen, 0 );
CloseHandle( hf );
}
return;
}
}
SendMessage( hlog, WM_SETTEXT, 0, (LPARAM)"File Type: Unknown format" );
CloseHandle( hf ); hf = 0;
}
LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM
lParam )
{
switch( uMsg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_COMMAND:
if( (HWND)lParam == hopen )
{
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFileName;
ofn.nMaxFile = sizeof(szFileName);
ofn.lpstrFilter = "Portable Executable (.exe)\0*.EXE\0";
ofn.nFilterIndex = 0;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
if( !GetOpenFileName( &ofn ) ) break;
CheckFileFormat( );
}
else if( (HWND)lParam == hstart )
{
if( !hf ) break;
// Проверяем длину пароля
if( GetWindowText( hpwd, buf, MAX_PASSWORDLEN+1 ) < 4 ) {
MessageBox( hwnd, "Your password must be 4 to 32
characters long", 0, MB_ICONHAND );
break;
}
// Проверяем пароль на допустимые символы
for( unsigned i = 0; buf[i]; ++i ) {
if( buf[i] < ' ' || buf[i] > '~' ) {
MessageBox( hwnd, "Incorrect password", 0,
MB_ICONHAND );
*buf = 0; break;
}
}
if( !*buf ) break;
if( !InjectMethod ) {
if( SectionTableAddr + ( ifh.NumberOfSections + 1 ) *
sizeof(IMAGE_SECTION_HEADER) > ioh.SizeOfHeaders ) {
MessageBox( hwnd, "Unable to add new section.\r\n"
"File will be encrypted with
extension of the last section.", 0, MB_ICONEXCLAMATION );
InjectMethod = 1;
SendMessage( hmethod, CB_SETCURSEL, 1, 0 );
}
} else if( dwOverlaySize ) {
dwBuf = MessageBox( hwnd, "File contains the data that
will be lost if you use this method of the loader injection.\r\n"
"Do you want to continue with
this method?", 0, MB_ICONEXCLAMATION | MB_YESNOCANCEL );
if( dwBuf == IDCANCEL ) break;
if( dwBuf == IDNO ) {
InjectMethod = 0;
SendMessage( hmethod, CB_SETCURSEL, 0, 0 );
}
}
// Отбрасываем расширение файла
memcpy( szNewFileName, szFileName, dwLen = strlen(szFileName)
- 4 );
// Добавляем окончание "-C.exe"
memcpy( szNewFileName + dwLen, szNewName, sizeof(szNewName)
);
// Создаем копию оригинального файла и дальше работаем с ней
CopyFile( szFileName, szNewFileName, 0 );
// Открываем новый файл для записи
hf = CreateFile( szNewFileName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( hf == INVALID_HANDLE_VALUE ) {
MessageBox( hwnd, "Unable to open the copy of file.", 0,
MB_ICONHAND );
break;
}
SetFilePointer( hf, EntryPointAddr + 5, 0, FILE_BEGIN );
ReadFile( hf, &CodeBuffer, MAX_PASSWORDLEN, &dwLen, 0 );
// Вычисляем контрольную сумму
for( unsigned i = 0; i < MAX_PASSWORDLEN; ++i ) { CheckSum +=
CodeBuffer[i]; }
// Шифруем
for( unsigned i = 0; i < SecCount; ++i ) {
unsigned k = 0;
SetFilePointer( hf, SecRawAddr[i], 0, FILE_BEGIN );
dwBuf = SecSize[i];
do {
if( dwBuf >= sizeof(CodeBuffer) ) {
ReadFile( hf, &CodeBuffer, sizeof(CodeBuffer),
&dwLen, 0 );
dwBuf -= sizeof(CodeBuffer);
}
else ReadFile( hf, &CodeBuffer, dwBuf, &dwLen, 0 );
for( unsigned j = 0; j < dwLen; ++j ) {
_asm {
mov esi, j
mov edi, k
mov ch, byte ptr [CodeBuffer+esi]
mov cl, byte ptr [buf+edi]
xor ch, cl
and cl, 0x0F
ror ch, cl
mov byte ptr [CodeBuffer+esi], ch
}
if( !buf[++k] ) k = 0;
}
SetFilePointer( hf, -dwLen, 0, FILE_CURRENT );
WriteFile( hf, &CodeBuffer, dwLen, &dwLen, 0 );
} while( dwLen == sizeof(CodeBuffer) );
}
if( InjectMethod == 0 ) {
// Делаем выравнивание для новой секции, заполняя конец
файла нулями
if( dwLen = ALIGN_UP(dwFileSize, ioh.FileAlignment) dwFileSize ) {
SetFilePointer( hf, 0, 0, FILE_END );
WriteFile( hf, &EmptyBuffer, dwLen, &dwLen, 0 );
}
// Создаем новую секцию
ZeroMemory( &mySection, sizeof(mySection) );
memcpy( &mySection.Name, &mySectionName, 8 );
mySection.VirtualAddress = ish.VirtualAddress +
ALIGN_UP((ish.Misc.VirtualSize? ish.Misc.VirtualSize: ish.SizeOfRawData),
ioh.SectionAlignment);
mySection.PointerToRawData = SetFilePointer( hf, 0, 0,
FILE_END );
mySection.Misc.VirtualSize = InjectSize;
mySection.SizeOfRawData = InjectSize;
mySection.Characteristics = IMAGE_SCN_CNT_CODE |
IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_WRITE;
// Записываем код загрузчика
WriteFile( hf, &InjectStart, InjectSize, &dwLen, 0 );
// Записываем новую секцию в таблицу
SetFilePointer( hf, SectionTableAddr +
ifh.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), 0, FILE_BEGIN );
WriteFile( hf, &mySection, sizeof(IMAGE_SECTION_HEADER),
&dwLen, 0 );
// Изменяем первые 5 байт точки входа на вызов загрузчика
*((DWORD*)&NewEntryBytes[1]) = mySection.VirtualAddress ioh.AddressOfEntryPoint - 5;
SetFilePointer( hf, EntryPointAddr, 0, FILE_BEGIN );
WriteFile( hf, &NewEntryBytes, 5, &dwLen, 0 );
// Увеличиваем число секций
SetFilePointer( hf, idh.e_lfanew + 0x06, 0, FILE_BEGIN );
WriteFile( hf, &(++ifh.NumberOfSections), sizeof(WORD),
&dwLen, 0 );
// Очищаем директорию BOUND импорта
if(
ioh.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress ) {
SetFilePointer( hf, idh.e_lfanew + 0xD0, 0,
FILE_BEGIN );
WriteFile( hf, EmptyBuffer, 8, &dwLen, 0 );
}
// Изменяем размер образа
ioh.SizeOfImage = mySection.VirtualAddress +
ALIGN_UP(mySection.Misc.VirtualSize, ioh.SectionAlignment);
SetFilePointer( hf, idh.e_lfanew + 0x50, 0, FILE_BEGIN );
WriteFile( hf, &ioh.SizeOfImage, sizeof(DWORD), &dwLen, 0
);
}
else {
// Записываем код загрузчика
SetFilePointer( hf, ish.PointerToRawData +
ish.SizeOfRawData, 0, FILE_BEGIN );
WriteFile( hf, &InjectStart, InjectSize, &dwLen, 0 );
// Изменяем первые 5 байт с точки входа
*((DWORD*)&NewEntryBytes[1]) = ish.VirtualAddress +
ish.SizeOfRawData - ioh.AddressOfEntryPoint - 5;
SetFilePointer( hf, EntryPointAddr, 0, FILE_BEGIN );
WriteFile( hf, &NewEntryBytes, 5, &dwLen, 0 );
// Изменяем параметры последней секции
SetFilePointer( hf, SectionTableAddr +
(ifh.NumberOfSections-1) * sizeof(IMAGE_SECTION_HEADER), 0, FILE_BEGIN );
ish.SizeOfRawData += InjectSize;
ish.Misc.VirtualSize += InjectSize;
ish.Characteristics = IMAGE_SCN_CNT_CODE |
IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_WRITE;
WriteFile( hf, &ish, sizeof(IMAGE_SECTION_HEADER),
&dwLen, 0 );
// Изменяем размер образа
ioh.SizeOfImage = ish.VirtualAddress +
ALIGN_UP(ish.Misc.VirtualSize, ioh.SectionAlignment);
SetFilePointer( hf, idh.e_lfanew + 0x50, 0, FILE_BEGIN );
WriteFile( hf, &ioh.SizeOfImage, sizeof(DWORD), &dwLen, 0
);
}
wsprintf( buf, "\r\n\r\nDone." );
InsertText( buf );
CloseHandle( hf ); hf = 0;
}
else if( (HWND)lParam == htest )
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
CreateProcess( szNewFileName, 0, 0, 0, 0, 0, 0, 0, &si, &pi
);
}
else if( HIWORD(wParam) == CBN_SELCHANGE )
InjectMethod = SendMessage( hmethod, CB_GETCURSEL, 0, 0 );
break;
case WM_DROPFILES:
DragQueryFile( (HDROP)msg.wParam, 0, szFileName,
sizeof(szFileName) );
DragFinish( (HDROP)msg.wParam );
CheckFileFormat( );
break;
default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return 0;
}
BOOL InitClass( void )
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = NULL;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
// wc.hIcon = LoadIcon( hInst, (LPCSTR)IDI_ICON1 );
// wc.hIcon = (HICON)LoadImage( hInst, MAKEINTRESOURCE(IDI_ICON1),
IMAGE_ICON, 0, 0, LR_SHARED|LR_DEFAULTSIZE );
wc.hIcon = NULL;
// wc.hIconSm = LoadIcon( hInst, MAKEINTRESOURCE(IDI_ICON1) );
// wc.hIconSm = (HICON)LoadImage( hInst, (LPCSTR)IDI_ICON1, IMAGE_ICON, 0,
0, 0 );
wc.hIconSm = NULL;
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "PECryptor";
if( !RegisterClassEx( &wc ) ) return 0;
return 1;
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int )
{
HFONT hFont;
hInst = hInstance;
if( !InitClass() ) return 0;
hwnd = CreateWindowEx( WS_EX_ACCEPTFILES, "PECryptor", "PE32-Cryptor
3000",
WS_MINIMIZEBOX|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_CAPTION|WS_BORDE
R|WS_SYSMENU,
10, 10, 655, 295, NULL, NULL, hInst, NULL );
if( !hwnd ) return 0;
hst1 = CreateWindowEx( 0, "STATIC", "File:", ES_LEFT|WS_VISIBLE|WS_CHILD,
8, 8, 50, 20, hwnd, NULL, hInst, NULL );
hfname = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", "",
ES_CENTER|ES_AUTOHSCROLL|WS_VISIBLE|WS_CHILD,
70, 5, 470, 20, hwnd, NULL, hInst, NULL );
hopen = CreateWindowEx( 0, "BUTTON", "Open",
BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD,
550, 4, 90, 20, hwnd, NULL, hInst, NULL );
hst2 = CreateWindowEx( 0, "STATIC", "Log:", ES_LEFT|WS_VISIBLE|WS_CHILD,
8, 33, 50, 20, hwnd, NULL, hInst, NULL );
hlog = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", "Drag your file on the
window or\r\npress \"Open\"",
ES_LEFT|ES_MULTILINE|ES_AUTOHSCROLL|ES_READONLY|WS_VISIBLE|WS_CHILD|WS_VSCROL
L,
70, 30, 230, 205, hwnd, NULL, hInst, NULL );
hgroup = CreateWindowEx( 0, "BUTTON", "Encrypt settings:",
BS_GROUPBOX|WS_VISIBLE|WS_CHILD,
320, 30, 320, 205, hwnd, NULL, hInst, NULL );
hst3 = CreateWindowEx( 0, "STATIC", "Password:",
ES_LEFT|WS_VISIBLE|WS_CHILD,
335, 58, 70, 20, hwnd, NULL, hInst, NULL );
hpwd = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", "",
ES_CENTER|ES_AUTOHSCROLL|WS_VISIBLE|WS_CHILD,
410, 55, 215, 20, hwnd, NULL, hInst, NULL );
hst4 = CreateWindowEx( 0, "STATIC", "Loader injection:",
ES_LEFT|WS_VISIBLE|WS_CHILD,
335, 88, 100, 20, hwnd, NULL, hInst, NULL );
hmethod = CreateWindowEx( 0, "COMBOBOX", NULL,
CBS_DROPDOWNLIST|ES_LEFT|WS_VISIBLE|WS_CHILD,
445, 85, 180, 20, hwnd, NULL, hInst, NULL );
hstart = CreateWindowEx( 0, "BUTTON", "START",
BS_DEFPUSHBUTTON|WS_VISIBLE|WS_CHILD,
70, 240, 360, 21, hwnd, NULL, hInst, NULL );
htest = CreateWindowEx( 0, "BUTTON", "Test",
BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD,
440, 240, 100, 21, hwnd, NULL, hInst, NULL );
hFont = CreateFont( -11, 6, 0, 0, FW_HEAVY, 0, 0, 0, 0, 0, 0, 0, 0,
"Verdana" );
SendMessage( hst1,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hst2,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hopen,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hstart, WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( htest,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hgroup, WM_SETFONT, (WPARAM)hFont, 0 );
hFont = (HFONT)GetStockObject( DEFAULT_GUI_FONT );
SendMessage( hfname, WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hpwd,
WM_SETFONT, (WPARAM)hFont, 0 );
hFont = CreateFont( -11, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0,
"Verdana" );
SendMessage( hlog,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hst3,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hst4,
WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hmethod, WM_SETFONT, (WPARAM)hFont, 0 );
SendMessage( hmethod, CB_ADDSTRING, 0, (LPARAM)"Add new section" );
SendMessage( hmethod, CB_ADDSTRING, 0, (LPARAM)"Extend last section" );
SendMessage( hmethod, CB_SETCURSEL, InjectMethod, 0 );
ShowWindow( hwnd, SW_SHOW );
if( *lpCmdLine ) {
if( *lpCmdLine == '\"' ) ++lpCmdLine;
lstrcpyn( szFileName, lpCmdLine, sizeof(szFileName) );
for( unsigned i = 0; szFileName[i]; ++i )
if( szFileName[i] == '\"' ) { szFileName[i] = 0; break; }
CheckFileFormat( );
}
while( GetMessage( &msg, NULL, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return msg.wParam;
}
Приложение 2
inject.asm
.386
option casemap:none
.model flat,stdcall
include e:\masm32\include\windows.inc
public InjectStart
public InjectSize
public EntryBytes
public CheckSum
public SecCount
public SecAddr
public SecSize
MAX_SECTIONS
equ 16
MAX_PASSWORDLEN equ 32
.code
InjectStart:
pushad
pushfd
call
delta
delta: mov
ebp,dword ptr [esp]
sub
ebp,offset delta
mov
call
kernel32.dll, в
mov
call
GetProcAddress,
esi,[esp+44]
GetBase
esi - начало PE заголовка
ebx,eax
GetGetProcAddress
в ebx - база kernel32.dll
;старт кода
;размер кода
;байты точки входа
;контр. сумма
;счетчик секций
;адрес секции
;размер секции
;вычисляем дельта-смещение
;адрес внутри kernel32.dll
;после вызова в eax - база
;после вызова в eax - адрес
;получаем адреса остальных
функций
push
push
lea
lea
mov
NextFunc:
push
push
push
call
mov
mov
mov
repne
имени функции
add
pop
loop
eax
ebx
edi,[ebp+FuncName]
esi,[ebp+FuncAddr]
ecx,FuncCount
ecx
edi
[esp+8]
dword ptr [esp+16]
[esi],eax
al,0
ecx,20
scasb
;переходим к следующему
esi,4
ecx
NextFunc
call
dword ptr [ebp+AllocConsole]
push
eax
проверки была ли создана консоль ранее
lea
push
call
;lpProcName
;hModule
;GetProcAddress
;пробуем создать консоль
;сохраняем результат для
edx,[ebp+str0]
edx
;lpConsoleTitle
dword ptr [ebp+SetConsoleTitleA] ;изменяем заголовок окна
консоли
push
STD_OUTPUT_HANDLE
call
dword ptr [ebp+GetStdHandle]
стандартного вывода
;nStdHandle
;получаем дескриптор
push
lea
push
push
lea
push
push
call
0
edx,[ebp+len1]
edx
[edx]
edx,[ebp+str1]
edx
eax
dword ptr [ebp+WriteConsoleA]
push
STD_INPUT_HANDLE
call
dword ptr [ebp+GetStdHandle]
стандартного ввода
push
lea
push
push
lea
push
push
call
sub
0
edx,[ebp+len2]
edx
[edx]
edx,[ebp+str2]
edx
eax
dword ptr [ebp+ReadConsoleA]
[ebp+len2],2
;lpReserved
;lpNumberOfCharsWritten
;nNumberOfCharsToWrite
;lpBuffer
;hConsoleOutput
;выводим строку в консоль
;nStdHandle
;получаем дескриптор
;lpReserved
;lpNumberOfCharsRead
;nNumberOfCharsToRead
;lpBuffer
;hConsoleOutput
;считываем пароль
;не учитываем \r\n
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push
call
mov
дешифрации
lea
lea
nextsec:
add
push
push
call
запись
mov
mov
xor
decrypt:
mov
байт
mov
mov
and
rol
xor
mov
mov
inc
inc
в пароле
cmp
jnz
xor
символу пароля
nozero: loop
call
доступа
pop
pop
add
0
dword ptr [ebp+GetModuleHandleA]
edx,[ebp+SecCount]
;количество секций для
esi,[ebp+SecSize]
edi,[ebp+SecAddr]
;массив размеров секций
;массив адресов секций
[edi],eax
eax
edx
SetAccess
;прибавляем ImageBase
ebx,[edi]
ecx,[esi]
edx,edx
;адрес секции
;размер секции
;индекс символа в пароле
al, byte ptr [ebx]
;считываем зашифрованный
ah, cl
cl, byte ptr [ebp+str2+edx]
cl, 0Fh
al, cl
al, byte ptr [ebp+str2+edx]
byte ptr [ebx], al
cl, ah
ebx
edx
;сохраняем cl
;считываем байт из пароля
;оставляем младшие 8 бит
;сдвигаем
;расшифровываем
;записываем обратно
edx,[ebp+len2]
nozero
edx,edx
;достигли конца пароля?
decrypt
SetAccess
;доходим до конца секции
;восстанавливаем права
edx
eax
edi,4
;следующий адрес
;даем права на чтение и
;увеличиваем адрес
;увеличиваем индекс символа
;переходим снова к первому
add
dec
jnz
esi,4
edx
nextsec
;следующий размер
;уменьшаем счетчик секций
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
call
dword ptr [ebp+FreeConsole]
pop
eax
test
eax,eax
jnz
NoConsole
call
dword ptr [ebp+AllocConsole]
была создана ранее
NoConsole:
lea
esi,[ebp+EntryBytes]
lea
edx,[esp+48]
mov
edi,[edx]
xor
mov
chksum: movzx
add
inc
loop
cmp
jnz
sub
mov
lea
push
push
push
push
call
точке входа
eax,eax
ecx,MAX_PASSWORDLEN
ebx,byte ptr [edi]
eax,ebx
edi
chksum
eax,[ebp+CheckSum]
q
edx,[ebp+OldProtect]
edx
[edx]
5
edi
dword ptr [ebp+VirtualProtect]
add
popfd
popad
ret
esp,12
push
call
0
dword ptr [ebp+ExitProcess]
EntryBytes
GetProcAddress
FuncName:
db 5 dup (?)
db "GetProcAddress",0
db
db
db
db
db
db
db
db
db
FuncAddr:
AllocConsole
ExitProcess
;EntryPoint
;проверяем контрольную сумму
;если не совпала - выходим
dword ptr [edx],5
edi,[edx]
mov
ecx,5
rep
movsb
оригинальные байты точки входа
q:
;создаем консоль если она
"AllocConsole",0
"ExitProcess",0
"FreeConsole",0
"GetModuleHandleA",0
"GetStdHandle",0
"ReadConsoleA",0
"SetConsoleTitleA",0
"VirtualProtect",0
"WriteConsoleA",0
dd ?
dd ?
;lpflOldProtect
;flNewProtect
;dwSize
;lpAddress
;изменяем права доступа в
;восстанавливаем
;uExitCode
FreeConsole
GetModuleHandleA
GetStdHandle
ReadConsoleA
SetConsoleTitleA
VirtualProtect
WriteConsoleA
FuncCount
InjectSize
str0
str1
len1
str2
len2
OldProtect
SecCount
SecAddr
SecSize
CheckSum
dd ?
dd ?
dd ?
dd ?
dd ?
dd ?
dd ?
equ ($-FuncAddr)/4
dd InjectEnd-InjectStart
db "PE32-CRYPTOR 3000",0
db "Enter password: "
dd $-str1
db MAX_PASSWORDLEN+2 dup (0)
dd $-str2
dd PAGE_EXECUTE_READWRITE
dd 0
dd MAX_SECTIONS dup (0)
dd MAX_SECTIONS dup (0)
dd 0
;получение адреса dll в памяти
GetBase proc
and esi,0FFFF0000h
памяти
checkPE:
push esi
.IF WORD ptr [esi]=="ZM"
assume esi:ptr IMAGE_DOS_HEADER
IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
.IF DWORD ptr [esi]=="EP"
pop eax
ret
.ENDIF
.ENDIF
pop esi
sub esi,10000h
jmp checkPE
GetBase endp
;получение адреса GetProcAddress в памяти
GetGetProcAddress proc
assume esi:ptr IMAGE_NT_HEADERS
lea esi,[esi].OptionalHeader
заголовка
assume esi:ptr IMAGE_OPTIONAL_HEADER
lea esi,[esi].DataDirectory
mov esi,dword ptr [esi]
add esi,ebx
IMAGE_EXPORT_DIRECTORY
push esi
assume esi:ptr IMAGE_EXPORT_DIRECTORY
mov esi,[esi].AddressOfNames
add esi,ebx
указателей на имена функций
xor edx,edx
mov eax,esi
mov esi,dword ptr [esi]
NextName:
функции
add esi,ebx
lea edi,[ebp+GetProcAddress]
;гранулярность выделения
;в esi указатель на
;переход к PE заголовку
;в esi - адрес опционального
;в esi - адрес DataDirectory
;в esi - адрес структуры
;в esi - адрес массива
;в edx - индекс
;поиск следующего имени
mov ecx,14
"GetProcAddress"
cld
repe cmpsb
.IF ecx==0
jmp GetAddr
.ENDIF
inc edx
add eax,4
mov esi,dword ptr [eax]
jmp NextName
GetAddr:
pop esi
mov edi,esi
mov esi,[esi].AddressOfNameOrdinals
add esi,ebx
битных индексов (ординалов)
mov dx,word ptr [esi][edx*2]
assume edi:ptr IMAGE_EXPORT_DIRECTORY
sub edx,[edi].nBase
inc edx
начинается с 1
mov esi,[edi].AddressOfFunctions
add esi,ebx
адресов функций
mov eax,dword ptr [esi][edx*4]
add eax,ebx
GetProcAddress
ret
GetGetProcAddress endp
;изменение прав доступа у секций
SetAccess proc
lea
eax,[ebp+OldProtect]
push
eax
push
[eax]
push
dword ptr [esi]
push
dword ptr [edi]
call
dword ptr [ebp+VirtualProtect]
ret
SetAccess endp
InjectEnd:
end
;количество байт в
;нашли имя
;если нашли "GetProcAddress"
;в esi - адрес массива 16-
;вычитаем базовый ординал
;т.к. базовый ординал
;в esi - адрес массива
;в eax - адрес функции
;lpflOldProtect
;flNewProtect
;dwSize
;lpAddress
;изменяем права доступа
Download