МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ ГОУ НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ИМ. Р.Е. АЛЕКСЕЕВА ИНСТИТУТ РАДИОЭЛЕКТРОНИКИ И ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ Кафедра «Вычислительные системы и технологии» Дисциплина: «Системное программное обеспечение» Пояснительная записка к курсовой работе Тема: «Средства защиты программ методом шифрования» Выполнил: студент группы 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 ;изменяем права доступа