| 
  
    |  |  
    |  |  |  
    |     Разведчик |    
            
                 Регистрация: 25.07.2011
                 Сообщений: 0
                 Популярность: 10  
	 Сказал(а) спасибо: 0
		
			
				Поблагодарили 0 раз(а) в 0 сообщениях
			
		
	   | 
                 Пишем криптор на Delphi (+ исходник криптора) 
 
            
               
    В данной статье я приведу пример простого протектора исполняемыхфайлов. В ходе приведения исходного текста будут производиться
 необходимые пояснения.
 
 С чего же начать писать свой собственный протектор? Для начала нам
 необходимо минимум теории. Также мы должны знать что хотим написать.
 Писать мы будем простейший в своем роде протектор, который выполняет
 следующее: шифрует байты на оригинальной точке входа (что это такое
 читайте далее), стирает сигнатуры пакеров (в данном случае UPX), т.е.
 работает как скрамблер, использует антиотладочные приемы. В качестве
 языка я предлагаю ObjectPascal, но при желании Вы легко сможете
 переписать это на другой язык.
 
 Итак, теория и необходимые термины
 
 Начнем с разбора формата исполняемых файлов PortableExecutable (PE). Что же этот формат из себя представляет.
 
 Структура файла такова:
 DOS-заглушка 	Содержит старый DOS-заголовок + DOS-программа
 PE-заголовок 	Содержит необходимую информацию о PE-файле
 Заголовки секций 	Содержат необходимую информацию о секциях в файле
 Сами секции 	Содержат непосредственно сам код, данные, ресурсы и др.
 Оверлей 	Любые данные, приписанные к концу PE-файла (Может не присутствовать)
 
 Ничего сложного здесь нет. DOS-заглушка — это просто досовская
 программа, которая обычно выводит сообщение типа: «This program must be
 run under Win32″ или что-то типа того и завершает свою работу.
 
 PE-заголовок содержит служебную информацию, в процессе написания мы много раз будем ей использовать.
 
 Заголовки секций в программном файле содержат соответственно иформацию о соответствующей секции.
 
 Сами секции это куски кода, данных, ресурсов, служебной информации,
 такой как: таблица импортов/экспортов, релоки, отладочная информация и
 др.
 
 Оверлей это все что находится после конца PE-файла. Часто оверлеев в
 обычных файлах нет, но зато почти всегда есть в SFX-архивах и
 дистрибутивах.
 
 Я думаю, что здесь все понятно, поэтому приступим к более подробному
 изучению формата PE-заголовка и секций. В PE-заголовке я поясню только
 те поля, которые нам понадобятся.
 
 Чтобы найти начало PE-заголовка в файле необходимо считать из файла
 значение типа DWORD по смещению $3C. Там хранится смещение от начала
 файла до начала PE-заголовка.
 
 Необходимые поля PE-заголовка:
 Смещение 	Тип 	Название 	Описание
 $00 	DWORD 	Signature Bytes 	Сигнатурка того, что этот файл собственно говоря является PE — должна быть $4550
 $06 	WORD 	Num of Objects 	это поле указывает на число реальных входов в Object Table (кол-во секций)
 $14 	WORD 	NT Header Size 	размер заголовка PE файла начиная с поля Magic, общий размер заголовка PE файла составляет NT Header Size + $18
 $28 	DWORD 	Entry point RVA 	адрес,
 относительно Image Base по которому передается управление при запуске
 программы или адрес инициализации/завершения библиотеки
 $34 	DWORD 	Image Base 	виртуальный начальный адрес загрузки программы (ее первого байта)
 $38 	DWORD 	Object align 	выравнивание
 программных секций, должен быть степенью 2 между 512 и 256М
 включительно, так же связано с системой памяти. При использовании
 других значений программа не загрузится
 $3C 	DWORD 	File align 	фактор
 используемый для выравнивания секций в программном файле. В байтовом
 значении указывает на границу на которую секции дополняются 0 при
 размещении в файле. Большое значение приводит к нерациональному
 использованию дискового пространства, маленькое увеличивает
 компактность, но и снижает скорость загрузки. Должен быть степенью 2 в
 диапазоне от 512 до 64К включительно. Прочие значения вызовут ошибку
 загрузки файла
 $50 	DWORD 	Image Size 	виртуальный размер в байтах всего загружаемого образа, вместе с заголовками, кратен Object align
 
 Тут нет ничего сложного, за исключением, пожалуй, одной детали. Дело в
 том, что все адреса памяти, записанные в заголовке относительные. А
 относительны они относильно ImageBase (хорошо сказано), т.е. чтобы
 вычислить абсолютный адрес нужно прибавить к относительному ImageBase.
 VA = RVA + ImageBase (VA (от VirtualAddress) — абсолютный адрес; RVA
 (от Relative VirtualAddress) — относительный)
 
 Формат заголовка секции:
 Смещение 	Тип 	Название 	Описание
 $00 	8 Bytes 	Object Name 	Имя
 объекта, остаток заполнен нулями, если имя объекта имеет длину 8
 символов, то заключительного 0 нет. Имя — штука отфонарная и никого ни
 к чему не обязывает
 $08 	DWORD 	Virtual Size 	виртуальный
 размер секции, именно столько памяти будет отведено под секцию. Если
 Virtual Size превышает Physical Size, то разница заполняется нулями,
 так определяются секции неинициализированных данных (Physical Size = 0)
 $0C 	DWORD 	Object RVA 	размещение
 секции в памяти, виртуальный ее адрес относительно Image Base. Позиция
 каждой секции выравнена на границу Object align (степень 2 от 512 до
 256М включительно, по умолчанию 64К) и секции упакованы впритык друг к
 другу, впрочем, можно это не соблюдать
 $10 	DWORD 	Physical Size 	размер
 секции (ее инициализированной части) в файле, кратно полю File align в
 заголовке PE Header, должно быть меньше или равно Virtual Size
 $14 	DWORD 	Physical Offset 	физическое смещение относительно начала EXE файла, выровнено на границу File align поля заголовка PE Header
 $18 	DWORD 	PointerToRelocations 	зарезервировано для OBJ файла, в экзешниках смысла не имеет
 $1C 	DWORD 	PointerToLinenumbers 	зарезервировано для OBJ файла, в экзешниках смысла не имеет
 $20 	DWORD 	NumberOfRelocations 	зарезервировано для OBJ файла, в экзешниках смысла не имеет
 $24 	DWORD 	NumberOfLinenumbers 	зарезервировано для OBJ файла, в экзешниках смысла не имеет
 $28 	DWORD 	Object Flags 	битовые флаги секции
 
 Тут также нет ничего сложного. После всех заголовков, с выравниванием
 кратным File align идут непосредственно сами секции исполняемого файла.
 Если Вы ничего не поняли что написано выше или хотите более подробно
 изучить формат PE, то рекомендую почитать документацию, например
 «ФОРМАТ ИСПОЛНЯЕМЫХ ФАЙЛОВ PortableExecutables» от Hard Wisdom.
 
 Ну теперь приступим к написанию непосредственно нашего протектора.
 
 Для начала входной файл нам нужно проверить является ли он исполняемым,
 да еще в формате PE. Те кто внимательно читал что написано выше уже
 могут догадаться что мы должны предпринять.
 
 Сначала проверим является ли первое слово (WORD), в файле = ‘ZM’($5A4D), если нет, то это по любому не программа.
 
 Во вторых, слово по смещению $18 должно быть >= $40, тогда и только тогда DWORD поле по смещению $3C имеет смысл.
 
 Затем проверяем сигнатуру в PE-заголовке, которая должна быть равна $00004550.
 
 Если все эти условия выполняются то впринципе мы можем попробовать такой файл обработать.
 
 Для удобства мы создадим прототип функции, которая будет обрабатывать наш файл.
 
 fucntion ProtectFile(szFileName: string; dwFlags: DWORD): Boolean;
 begin
 Result:= False;
 end;
 
 szFileName — путь к файлу, dwFlags — дополнительные параметры. Затем просто осуществим проверку файла на валидность
 
 function IsFullPath(const Path: string): Boolean;
 begin
 Result:= ExtractFileDrive(Path) <> '';
 end;
 
 {  Дополнительные параметры при обработке файла  }
 const
 PROTECTION_FLAG_MAKEBACKUP   = $00000001;  // создать резервную копию файла
 PROTECTION_FLAG_SAVEOVERLAY  = $00000010;  // сохранить оверлей
 
 { Функция обработки файла }
 function ProtectFile(szFileName: string; dwFlags: DWORD): Boolean;
 var
 BackupFile: string;
 FHandle: HFILE;
 OFS: OFSTRUCT;
 EXESig: WORD;  // сигнатура файла
 PEHeaderOffset: DWORD; // смещение PE-заголовка
 ImageNtHeaders: TImageNtHeaders; // заголовки файла
 dwTemp: DWORD;
 W: Word;
 
 label
 Quit;
 
 begin
 Result:= False; // пока установим ложь
 
 { Имя входного файла }
 Writeln('Input file: ', ExtractFileName(szFileName), #10#13);
 
 if not IsFullPath(szFileName) then
 szFileName:= GetCurrentDir + '\' + szFileName;
 
 { А если файла нет ? }
 if not FileExists(szFileName) then
 begin
 Writeln('Can not find a file');
 goto Quit;
 end;
 
 { Нужна резервная копия ? }
 if (dwFlags and PROTECTION_FLAG_MAKEBACKUP <> 0) then
 begin
 BackupFile:= szFileName + '.bak';
 SetFileAttributes(PChar(BackupFile), 0);
 DeleteFile(PChar(BackupFile));
 CopyFile(PChar(szFileName), PChar(BackupFile), False);
 end;
 
 { Открываем файл }
 SetFileAttributes(PChar(szFileName), 0);
 FHandle:= OpenFile(PChar(szFileName), OFS, OF_READWRITE);
 if (FHandle = INVALID_HANDLE_VALUE) then
 begin
 Writeln('Open file error');
 goto Quit;
 end;
 
 { Читаем сигнатуру MZ }
 ReadFile(FHandle, EXESig, SizeOf(EXESig), dwTemp, nil);
 if EXESig <> IMAGE_DOS_SIGNATURE then
 begin
 Writeln('Invalid MZ file');
 goto Quit;
 end;
 
 { А если файла левый ? }
 SetFilePointer(FHandle, $18, nil, FILE_BEGIN);
 ReadFile(FHandle, W, SizeOf(W), dwTemp, nil);
 if W < $40 then
 begin
 Writeln('Invalid PE file (1)');
 goto Quit;
 end;
 
 { Проверяем значение PE header offset }
 SetFilePointer(FHandle, $3C, nil, FILE_BEGIN);
 ReadFile(FHandle, PEHeaderOffset, SizeOf(PEHeaderOffset), dwTemp, nil);
 if (PEHeaderOffset = 0) then
 begin
 Writeln('Invalid PE file (2)');
 goto Quit;
 end;
 
 if (PEHeaderOffset < $40) then
 begin
 Writeln('Invalid PE file (3)');
 goto Quit;
 end;
 
 { Проверяем сигнатуру PE }
 SetFilePointer(FHandle, PEHeaderOffset, nil, FILE_BEGIN);
 ReadFile(FHandle, ImageNtHeaders, SizeOf(ImageNtHeaders), dwTemp, nil);
 if ImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE then
 begin
 Writeln('Invalid PE file (4)');
 goto Quit;
 end;
 Result:= True; // все ок и мы здесь можем продолжить...
 //...
 Quit:
 CloseHandle(FHandle);
 end;
 
 Итак, мы умеем проверять файл на валидность. Теперь можно приступать
 непосредственно к написанию кода протектора. Но сначала снова немножко
 теории.
 
 Протектор — это программа, значит она должна содержать исполняемый код.
 Следовательно нам нужно добавить этот код в уже скомпиллированный файл.
 Как известно в PE-файлах код хранится в секции (хотя это и не
 обязательно, код, например, можно хранить в заголовках). Для этого нам
 просто понадобится один из вариантов: дописать код к последней секции
 файла или создать новую секцию. Первый вариант легче, а мы легкого пути
 не ищем, поэтому выбираем второй.
 
 Чтобы добавить секцию в файл необходимо выполнить следующие шаги:
 
 1) Увеличиваем значение поля PE-заголовка NumOfObjects на 1.
 
 2) Дописываем к концу последнего заголовка секции еще один заголовок нашей секции
 
 3)
 Устанавливаем атрибуты нашей секции так: VirtualSize = PhysicalSize =
 4096 (именно это значение потому что нам не придется ничего
 выравнивать, т.к. это поле кратно значению выравнивания, и 4 килобайт
 кода нам вполне хватит). PhysicalOffset = PhysicalOffset последней
 секции + PhysicalSize последней секции. VirtualAddress = VirtualAddress
 последней секции + VirtualSize последней секции, а затем это значение
 нужно установить кратным SectionAlignment до большего значение.
 Атрибуты секции лучше установить на чтение и запись, например такие -
 $E0000040. Остальные поля забиваем нулями. Имя секции можете присвоить
 любое.
 
 4) Дописать саму секцию к концу файла. Её размер мы уже предопределили и он равен PhysicalSize
 
 5)
 Необходимо поправить значение поля PE-заголовка SizeOfImage и увеличить
 его размер на выровненный виртуальный размер нашей новой секции.
 
 После всего этого в программном файле появится новая секция, в нашем
 случае равная размеру одной страницы в памяти. Остается последний
 вопрос каким образом нашему коду получить управление. Для этого
 устанавливаем значение поля PE-заголовка EntryPoint равное
 VirtualAddress нашей секции. И все. Теперь при запуске программы
 управление получит наш код.
 
 Теперь, когда мы добавили свой код в файл нам нужно чтобы он что-то
 выполнял. В самом наипростейшем случае мы должны просто сразу передать
 управление программе-носителю. Это осуществляется с помощью пары
 процессорных команд.
 
 MOV EAX,$АДРЕС_ОРИГИНАЛЬНОЙ_ТОЧКИ_ВХОДА
 
 JMP EAX
 
 Для тех кто не знает ассемблера объясню, что сначала в регистр
 помещяется адрес OEP (значение поля EntryPoint, до того как мы его
 подправили + ImageBase), а затем осуществляется переход по этому
 адресу.
 
 Больше я ничего не буду объяснять, а просто приведу полный листниг
 исходного текста протектора с комментариями. Приведенный ниже код
 полностью работоспособен и лучше данный протектор испытывайте поверх
 UPX, т.к. результат намного лючше. А еще небольшой сюрприз для
 люьителей побаловаться «нехорошими программами». Если запаковать троян,
 например UPX, а затем этим протектором, то «некоторые известные
 антивирусы» вообще не определят, что троян чем-либо запакован, и
 естественно троян не будет замечен icon smile Пишем криптор на Delphi (+ исходник криптора)
 
 program ProtExample;
 
 {$APPTYPE CONSOLE}
 
 uses
 Windows, SysUtils;
 
 function IsFullPath(const Path: string): Boolean;
 begin
 Result:= ExtractFileDrive(Path) <> '';
 end;
 
 {  Дополнительные параметры при обработке файла  }
 const
 PROTECTION_FLAG_MAKEBACKUP   = $00000001;  // создать резервную копию файла
 PROTECTION_FLAG_SAVEOVERLAY  = $00000010;  // сохранить оверлей
 
 { Функция обработки файла }
 function ProtectFile(szFileName: string; dwFlags: DWORD): Boolean;
 const
 NewCodeSize = 4096; // размер новой секции в файле
 SignOffset  = 0;    // нашей смещение сигнатуры в секции
 
 { Сама сигнатура протектора }
 Sign: array[0..23] of Char = (#$E8, #$00, #$00, #$00, #$00, #$5D, #$83, #$C5,
 #$12, #$55, #$C3, #$20, #$83, #$B8, #$ED, #$20,
 #$37, #$EF, #$C6, #$B9, #$79, #$37, #$9E, #$00);
 
 { Сигнатура расшифровки простым XOR }
 DecryptSign: array[0..19] of Byte =
 ($BE, $00, $00, $00, $00,      // MOV ESI,xxxxxxxx ; x - начальное смещение
 $B0, $00,                     // MOV AL,xx        ; первый XOR-байт
 $30, $06,                     // XOR [ESI],AL
 $8A, $06,                     // MOV AL,[ESI]
 $46,                          // INC ESI
 $81, $FE, $00, $00, $00, $00, // CMP ESI,xxxxxxxx  ; x - конечное смещение
 $7C, $F3);                    // JL -0B
 
 { Противоотладочная сигнатура }
 AntiDebugSign: array[0..63] of Byte =
 ($EB, $09,                          // JMP 0040100B
 $90,                               // NOP
 $90,                               // NOP
 $90,                               // NOP
 $58,                               // POP EAX
 $EB, $38,                          // JMP 00401040
 $90,                               // NOP
 $90,                               // NOP
 $90,                               // NOP
 $33, $C9,                          // XOR ECX,ECX
 $83, $C1, $10,                     // ADD ECX,10
 $BB, $FF, $FF, $FF, $77,           // MOV EBX,77FFFFFF
 $64, $8B, $83, $19, $00, $00, $88, // MOV EAX,FS:[EBX+88000019]
 $8B, $44, $48, $10,                // MOV EAX,[ECX*2+EAX+10]
 $0F, $B6, $40, $02,                // MOVZX EAX,BYTE PTR [EAX+02]
 $F7, $D0,                          // NOT EAX
 $83, $E0, $01,                     // AND EAX,01
 $8B, $D8,                          // MOV EBX,EAX
 $68, $F6, $FB, $C3, $00,           // PUSH 00C3FBF6
 $E8, $00, $00, $00, $00,           // CALL 00401035
 $83, $2C, $24, $33,                // SUB DWORD PTR [+ESP],33
 $8B, $F4,                          // MOV ESI,ESP
 $83, $C6, $04,                     // ADD ESI,04
 $FF, $E6);                         // JMP ESI
 
 { Противотрейсерная сигнатура }
 AntiTraceSign: array[0..71] of Byte =
 ($31, $C0,                               // XOR EAX,EAX
 $55,                                    // PUSH EBP
 $E8, $00, $00, $00, $00,                // CALL +05
 $83, $04, $24, $34,                     // ADD DWORD PTR [ESP],$34
 $64, $FF, $30,                          // PUSH DWORD PTR [FS:EAX]
 $64, $89, $20,                          // MOV [FS:EAX],ESP
 $9C,                                    // PUSHFD
 $58,                                    // POP EAX
 $0D, $00, $01, $00, $00,                // OR EAX,$100
 $6A, $00,                               // PUSH 00
 $6A, $FF,                               // PUSH FF
 $6A, $00,                               // PUSH 00
 $6A, $00,                               // PUSH 00
 $50,                                    // PUSH EAX
 $B8, $01, $01, $00, $00,                // MOV EAX,$101
 $89, $E2,                               // MOV EDX,ESP
 $83, $C2, $04,                          // ADD EDX,$04
 $C7, $44, $24, $FC, $0F, $34, $00, $00, // MOV DWORD PTR [ESP-$04],$340F
 $89, $E1,                               // MOV ECX,ESP
 $83, $E9, $04,                          // SUB ECX,$04
 $9D,                                    // POPFD
 $FF, $E1,                               // JMP ECX
 $58,                                    // POP EAX
 $58,                                    // POP EAX
 $58,                                    // POP EAX
 $58,                                    // POP EAX
 $31, $C0,                               // XOR EAX,EAX
 $5A,                                    // POP EDX
 $59,                                    // POP ECX
 $59,                                    // POP ECX
 $64, $89, $10);                         // MOV [FS:EAX],EDX
 
 type
 {  Создадим свой тип для описания заголовка секции  }
 TImageSectionHeader = packed record
 Name: array[0..7] of Char;
 VirtualSize: DWORD;
 VirtualAddress: DWORD;
 PhysicalSize: DWORD;
 PhysicalOffset: DWORD;
 PointerToRelocations: DWORD;
 PointerToLinenumbers: DWORD;
 NumberOfRelocations: WORD;
 NumberOfLinenumbers: WORD;
 Characteristics: DWORD;
 end;
 
 var
 BackupFile: string;
 FHandle: HFILE;
 OFS: OFSTRUCT;
 EXESig: WORD;  // сигнатура файла
 PEHeaderOffset: DWORD; // смещение PE-заголовка
 PEHeaderSize: DWORD;  // размер PE-заголовка
 ImageNtHeaders: TImageNtHeaders; // заголовки файла
 SectionHeader: TImageSectionHeader;  // заголовок секции
 EntryPointOffset: DWORD;  // смещение точки входа
 EndOfCodeSection: DWORD;  // смещение конца секции кода
 NewSectionOffset: DWORD;  // смещение новой секции
 NewSectionRVA: DWORD;     // адрес новой секции
 NewImageSize: DWORD;      // новый размер образа
 FirstSectionOffset: DWORD;  // смещение первой секции
 NewFirstSectionOffset: DWORD; // новое смещение первой секции
 MoveCount: DWORD;
 EndOfImage: DWORD;
 OverlaySize: DWORD;   // размер оверлея
 dwTemp: DWORD;
 ptrBuf: Pointer;
 dwBufSize: DWORD;
 TestBuff: array[0..High(Sign)] of Char;
 I, J: DWORD;
 W: WORD;
 B, X: Byte;
 SD_XorByte: Byte;
 SD_StartOffset: DWORD;
 SD_EndOffset: DWORD;
 EP_XorByte: Byte;
 EP_StartOffset: DWORD;
 EP_EndOffset: DWORD;
 
 label
 Quit;
 
 function RVAToOffset(RVA, SectionRVA, SectionOffset: DWORD): DWORD;
 begin
 Result:= RVA - SectionRVA + SectionOffset;
 end;
 
 function OffsetToRVA(Offset, SectionRVA, SectionOffset: DWORD): DWORD;
 begin
 Result:= Offset + SectionRVA - SectionOffset;
 end;
 
 function Bit(B: DWORD): Byte;
 begin
 if B <> 0 then
 Result:= 1
 else
 Result:= 0;
 end;
 
 { Получает текущее RVA  }
 function GetCurrentRVA: DWORD;
 begin
 Result:= OffsetToRVA(SetFilePointer(FHandle, 0, nil, FILE_CURRENT),
 NewSectionRVA, NewSectionOffset);
 end;
 
 { Заполняет значение по адресу DWORD'ом  }
 procedure FillAddress(ptrArray: Pointer; wFirstIndex: Word; dwValue: DWORD);
 begin
 Inc(DWORD(ptrArray),wFirstIndex);
 Byte(ptrArray^):= LoByte(LoWord(dwValue));
 Inc(DWORD(ptrArray));
 Byte(ptrArray^):= HiByte(LoWord(dwValue));
 Inc(DWORD(ptrArray));
 Byte(ptrArray^):= LoByte(HiWord(dwValue));
 Inc(DWORD(ptrArray));
 Byte(ptrArray^):= HiByte(HiWord(dwValue));
 end;
 
 begin
 Result:= False; // пока установим ложь
 
 { Имя входного файла }
 Writeln('Input file: ', ExtractFileName(szFileName), #10#13);
 
 if not IsFullPath(szFileName) then
 szFileName:= GetCurrentDir + '\' + szFileName;
 
 { А если файла нет ? }
 if not FileExists(szFileName) then
 begin
 Writeln('Can not find a file');
 goto Quit;
 end;
 
 { Нужна резервная копия ? }
 if (dwFlags and PROTECTION_FLAG_MAKEBACKUP <> 0) then
 begin
 BackupFile:= szFileName + '.bak';
 SetFileAttributes(PChar(BackupFile), 0);
 DeleteFile(PChar(BackupFile));
 CopyFile(PChar(szFileName), PChar(BackupFile), False);
 end;
 
 { Открываем файл }
 SetFileAttributes(PChar(szFileName), 0);
 FHandle:= OpenFile(PChar(szFileName), OFS, OF_READWRITE);
 if (FHandle = INVALID_HANDLE_VALUE) then
 begin
 Writeln('Open file error');
 goto Quit;
 end;
 
 { Читаем сигнатуру MZ }
 ReadFile(FHandle, EXESig, SizeOf(EXESig), dwTemp, nil);
 if EXESig <> IMAGE_DOS_SIGNATURE then
 begin
 Writeln('Invalid MZ file');
 goto Quit;
 end;
 
 { А если файла левый ? }
 SetFilePointer(FHandle, $18, nil, FILE_BEGIN);
 ReadFile(FHandle, W, SizeOf(W), dwTemp, nil);
 if W < $40 then
 begin
 Writeln('Invalid PE file (1)');
 goto Quit;
 end;
 
 { Проверяем значение PE header offset }
 SetFilePointer(FHandle, $3C, nil, FILE_BEGIN);
 ReadFile(FHandle, PEHeaderOffset, SizeOf(PEHeaderOffset), dwTemp, nil);
 if (PEHeaderOffset = 0) then
 begin
 Writeln('Invalid PE file (2)');
 goto Quit;
 end;
 
 if (PEHeaderOffset < $40) then
 begin
 Writeln('Invalid PE file (3)');
 goto Quit;
 end;
 
 { Проверяем сигнатуру PE }
 SetFilePointer(FHandle, PEHeaderOffset, nil, FILE_BEGIN);
 ReadFile(FHandle, ImageNtHeaders, SizeOf(ImageNtHeaders), dwTemp, nil);
 if ImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE then
 begin
 Writeln('Invalid PE file (4)');
 goto Quit;
 end;
 
 { Сотрем эти значения PE-заголовка }
 ImageNtHeaders.FileHeader.TimeDateStamp:= 0;
 ImageNtHeaders.OptionalHeader.MajorLinkerVersion:= 0;
 ImageNtHeaders.OptionalHeader.MinorLinkerVersion:= 0;
 ImageNtHeaders.OptionalHeader.CheckSum:= 0;
 
 { Вычислим размер PE-заголовка }
 PEHeaderSize:= ImageNtHeaders.FileHeader.SizeOfOptionalHeader +
 SizeOf(ImageNtHeaders.FileHeader) + 4;
 
 { Пробежимся по заголовкам секций }
 NewImageSize:= 0; // новый размер образа
 J:= ImageNtHeaders.OptionalHeader.SectionAlignment;
 { Уходим на физическое смещение начала заголовка первой секци }
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize, nil, FILE_BEGIN);
 for I:= 1 to ImageNtHeaders.FileHeader.NumberOfSections do
 begin
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 Inc(NewImageSize, SectionHeader.VirtualSize);
 { Вычислим новый размер образа }
 NewImageSize:= (NewImageSize div J) * J + J * Bit(NewImageSize mod J);
 { Если RVA каталога ресурсов попадает в диапазон адресов секции, то OK }
 if (ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].
 VirtualAddress >= SectionHeader.VirtualAddress)
 and (ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].
 VirtualAddress < SectionHeader.VirtualAddress + SectionHeader.VirtualSize) then
 begin
 { Если это секция ресурсов, то установим название '.rsrc' }
 FillChar(SectionHeader.Name, SizeOf(SectionHeader.Name), #0);
 StrPCopy(SectionHeader.Name, '.rsrc');
 end else begin
 { Имена остальных секций стираем, а параметры изменяем на RW }
 FillChar(SectionHeader.Name, SizeOf(SectionHeader.Name), #0);
 SectionHeader.Characteristics:= $E0000000 +
 SectionHeader.Characteristics mod $10000000;
 end;
 SetFilePointer(FHandle, -SizeOf(SectionHeader), nil, FILE_CURRENT);
 WriteFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 end;
 
 { Вычислим секцию кода }
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize, nil, FILE_BEGIN);
 for I:= 1 to ImageNtHeaders.FileHeader.NumberOfSections do
 begin
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 { Если EntryPointRVA попадает в диапазон адресов секции, то OK }
 if (ImageNtHeaders.OptionalHeader.AddressOfEntryPoint >= SectionHeader.VirtualAddress)
 and (ImageNtHeaders.OptionalHeader.AddressOfEntryPoint <
 SectionHeader.VirtualAddress + SectionHeader.VirtualSize) then Break;
 end;
 { Вычислим физическое смещение точки входа }
 EntryPointOffset:= RVAToOffset(ImageNtHeaders.OptionalHeader.
 AddressOfEntryPoint, SectionHeader.VirtualAddress, SectionHeader.PhysicalOffset);
 { Вычислим физическое смещение конца секцию кода }
 EndOfCodeSection:= ImageNtHeaders.OptionalHeader.ImageBase +
 SectionHeader.VirtualAddress + SectionHeader.PhysicalSize;
 
 { Уходим на физическое смещение точки входа }
 SetFilePointer(FHandle, EntryPointOffset, nil, FILE_BEGIN);
 ReadFile(FHandle, I, SizeOf(I), dwTemp, nil);
 
 { А не обработан ли уже наш файл ? }
 SetFilePointer(FHandle, EntryPointOffset + SignOffset, nil, FILE_BEGIN);
 ReadFile(FHandle, TestBuff, SizeOf(TestBuff), dwTemp, nil);
 TestBuff[High(TestBuff)]:= #0;
 if StrComp(TestBuff, Sign) = 0 then
 begin
 Writeln('This file is already protected');
 goto Quit;
 end;
 
 { Уходим на физическое смещение начала заголовка первой секции }
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize, nil, FILE_BEGIN);
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 { Вычисляем физическое смещение первой секции }
 FirstSectionOffset:= SectionHeader.PhysicalOffset;
 I:= (ImageNtHeaders.FileHeader.NumberOfSections-1) * SizeOf(SectionHeader);
 { Уходим на физическое смещение начала заголовка последней секции }
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize + I, nil, FILE_BEGIN);
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 { Вычисляем физическое смещение конца последней секции }
 EndOfImage:= SectionHeader.PhysicalOffset + SectionHeader.PhysicalSize;
 { Получаем размер оверлея }
 OverlaySize:= GetFileSize(FHandle, nil) - EndOfImage;
 
 { Создаем заголовок новой секции }
 FillChar(SectionHeader.Name, SizeOf(SectionHeader.Name), #0);
 { Запишем имя новой секции }
 StrPCopy(SectionHeader.Name, '.data');
 J:= ImageNtHeaders.OptionalHeader.SectionAlignment;
 Inc(SectionHeader.VirtualAddress, SectionHeader.VirtualSize);
 I:= SectionHeader.VirtualAddress;
 { RVA новой секции =  RVA пред. секции + размер пред. секции,
 выровненный на SectionAlignment}
 SectionHeader.VirtualAddress:= (I div J) * J + J * Bit(I mod J);
 { сохраним RVA новой секции }
 NewSectionRVA:= SectionHeader.VirtualAddress;
 Inc(SectionHeader.PhysicalOffset, SectionHeader.PhysicalSize);
 SectionHeader.PointerToRelocations:= 0;
 SectionHeader.PointerToLinenumbers:= 0;
 SectionHeader.NumberOfRelocations:= 0;
 SectionHeader.NumberOfLinenumbers:= 0;
 SectionHeader.Characteristics:= $E0000040; // флаги секции ERW
 { физический и виртуайльный размер новой секции = NewCodeSize }
 SectionHeader.VirtualSize:= NewCodeSize;
 SectionHeader.PhysicalSize:= NewCodeSize;
 { А надо ли сохранить оверлей ? }
 if (dwFlags and PROTECTION_FLAG_SAVEOVERLAY <> 0) then
 { Если надо, то копируем в буфер все секции + оверлей }
 dwBufSize:= GetFileSize(FHandle, nil) - FirstSectionOffset
 else
 { Если НЕ надо, то копируем в буфер только все секции }
 dwBufSize:= GetFileSize(FHandle, nil) - FirstSectionOffset - OverlaySize;
 GetMem(ptrBuf, dwBufSize);
 I:= SetFilePointer(FHandle, 0, nil, FILE_CURRENT);
 SetFilePointer(FHandle, FirstSectionOffset, nil, FILE_BEGIN);
 { Считываем данные секций в буфер }
 ReadFile(FHandle, ptrBuf^, dwBufSize, dwTemp, nil);
 { Дописываем к концу заголовка последней секции свой заголовок }
 SetFilePointer(FHandle, I, nil, FILE_BEGIN);
 WriteFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 
 I:= SetFilePointer(FHandle, 0, nil, FILE_CURRENT);
 J:= ImageNtHeaders.OptionalHeader.FileAlignment;
 { Вычислыем новое смещение первой секции }
 NewFirstSectionOffset:= (I div J) * J + J * Bit(I mod J);
 B:= $00;
 { Сколько необходимо байт дописать }
 MoveCount:= NewFirstSectionOffset-I;
 { Выравниваем заголовки до FileAlignment }
 for I:= 1 to MoveCount do
 WriteFile(FHandle, B, SizeOf(B), dwTemp, nil);
 { Записываем буфер с секциями }
 WriteFile(FHandle, ptrBuf^, dwBufSize, dwTemp, nil);
 FreeMem(ptrBuf);
 
 { Уходим на физическое смещение начала заголовка первой секции }
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize, nil, FILE_BEGIN);
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 { Вычислыем новое смещение первой секции }
 FirstSectionOffset:= SectionHeader.PhysicalOffset;
 { На сколько сместились секции в файле }
 MoveCount:= NewFirstSectionOffset - FirstSectionOffset;
 { Новый размер заголовков }
 ImageNtHeaders.OptionalHeader.SizeOfHeaders:= NewFirstSectionOffset;
 SetFilePointer(FHandle, PEHeaderOffset + PEHeaderSize, nil, FILE_BEGIN);
 { Правим физическое смещение каждой секции на MoveCount }
 for I:= 1 to ImageNtHeaders.FileHeader.NumberOfSections do
 begin
 ReadFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 SectionHeader.PhysicalOffset:= SectionHeader.PhysicalOffset + MoveCount;
 SetFilePointer(FHandle, -SizeOf(SectionHeader), nil, FILE_CURRENT);
 WriteFile(FHandle, SectionHeader, SizeOf(SectionHeader), dwTemp, nil);
 end;
 
 B:= $00;
 { Уходим на физическое смещение конца последней секции }
 SetFilePointer(FHandle, EndOfImage, nil, FILE_BEGIN);
 { А надо ли сохранить оверлей ? }
 if (dwFlags and PROTECTION_FLAG_SAVEOVERLAY <> 0) then
 begin
 { Если надо, копируем оверлей в буфер,
 затем забиваем нулями новую секцию и
 приписываем к концу файла оверлей }
 dwBufSize:= OverlaySize;
 GetMem(ptrBuf, dwBufSize);
 ReadFile(FHandle, ptrBuf^, dwBufSize, dwTemp, nil);
 SetFilePointer(FHandle, EndOfImage, nil, FILE_BEGIN);
 NewSectionOffset:= SetFilePointer(FHandle, 0, nil, FILE_CURRENT);
 for I:= 1 to NewCodeSize do
 WriteFile(FHandle, B, SizeOf(B), dwTemp, nil);
 WriteFile(FHandle, ptrBuf^, dwBufSize, dwTemp, nil);
 FreeMem(ptrBuf);
 end else begin
 { Если НЕ надо, то забиваем нулями новую секцию и обрезаем конец файла }
 NewSectionOffset:= SetFilePointer(FHandle, 0, nil, FILE_CURRENT);
 for I:= 1 to NewCodeSize do
 WriteFile(FHandle, B, SizeOf(B), dwTemp, nil);
 SetEndOfFile(FHandle);
 end;
 
 { Завершаем вычисление NewImageSize }
 Inc(NewImageSize, NewFirstSectionOffset);
 J:= ImageNtHeaders.OptionalHeader.SectionAlignment;
 NewImageSize:= (NewImageSize div J) * J + J * Bit(NewImageSize mod J);
 
 Inc(NewImageSize, NewCodeSize);
 NewImageSize:= (NewImageSize div J) * J + J * Bit(NewImageSize mod J);
 ImageNtHeaders.FileHeader.NumberOfSections:=
 ImageNtHeaders.FileHeader.NumberOfSections + 1;
 { Сохраняем NewImageSize }
 ImageNtHeaders.OptionalHeader.SizeOfImage:= NewImageSize;
 
 {*************************************************  ***************************}
 
 { Отправляемся на смещение начала нашей секции }
 SetFilePointer(FHandle, NewSectionOffset, nil, FILE_BEGIN);
 
 { Записываем в секцию нашу сигнатуру }
 WriteFile(FHandle, Sign, SizeOf(Sign), dwTemp, nil);
 SetFilePointer(FHandle, -1, nil, FILE_CURRENT);
 
 { Вычисляем диапазон адресов для шифровки своего кода }
 SD_XorByte:= Random($FF)+1; // случайный XOR байт
 SD_StartOffset:= ImageNtHeaders.OptionalHeader.ImageBase +
 GetCurrentRVA + SizeOf(DecryptSign);
 SD_EndOffset:= ImageNtHeaders.OptionalHeader.ImageBase +
 NewSectionRVA + NewCodeSize;
 
 FillAddress(@DecryptSign, 1, SD_StartOffset);
 DecryptSign[6]:= SD_XorByte; // XOR байт
 FillAddress(@DecryptSign, 14, SD_EndOffset);
 
 { Записываем сигнатуру расшифровки }
 WriteFile(FHandle, DecryptSign, SizeOf(DecryptSign), dwTemp, nil);
 
 { Записываем антиотладочную сигнатуру }
 WriteFile(FHandle, AntiDebugSign, SizeOf(AntiDebugSign), dwTemp, nil);
 { Записываем антитрассировочную сигнатуру }
 WriteFile(FHandle, AntiTraceSign, SizeOf(AntiTraceSign), dwTemp, nil);
 
 { Вычисляем диапазон адресов для шифровки байтов на OEP }
 EP_XorByte:= Random($FF)+1; // случайный XOR байт
 EP_StartOffset:= ImageNtHeaders.OptionalHeader.ImageBase +
 ImageNtHeaders.OptionalHeader.AddressOfEntryPoint;
 EP_EndOffset:= EP_StartOffset + $FF;
 if EP_EndOffset > EndOfCodeSection then EP_EndOffset:= EndOfCodeSection;
 
 FillAddress(@DecryptSign, 1, EP_StartOffset);
 DecryptSign[6]:= EP_XorByte; // XOR байт
 FillAddress(@DecryptSign, 14, EP_EndOffset);
 
 { Записываем сигнатуру расшифровки }
 WriteFile(FHandle, DecryptSign, SizeOf(DecryptSign), dwTemp, nil);
 
 { записываем прыжок на OEP( оригинальная точка входа ) }
 // B8 xx xx xx xx  MOV EAX,xxxxxxxx
 // FF E0           JMP EAX
 B:= $B8;
 WriteFile(FHandle, B, SizeOf(B), dwTemp, nil);
 I:= ImageNtHeaders.OptionalHeader.ImageBase +
 ImageNtHeaders.OptionalHeader.AddressOfEntryPoint;
 WriteFile(FHandle, I, SizeOf(I), dwTemp, nil);
 W:=$E0FF;
 WriteFile(FHandle, W, SizeOf(W), dwTemp, nil);
 
 I:= SetFilePointer(FHandle, 0, nil, FILE_CURRENT);
 
 { Шифруем свой собственный код }
 B:= SD_XorByte;
 J:= SD_EndOffset - SD_StartOffset;
 Dec(SD_StartOffset, ImageNtHeaders.OptionalHeader.ImageBase);
 SetFilePointer(FHandle, RVAToOffset(SD_StartOffset, NewSectionRVA,
 NewSectionOffset), nil, FILE_BEGIN);
 for I:= 1 to J do
 begin
 ReadFile(FHandle, X, SizeOf(X), dwTemp, nil);
 W:= X;
 X:= X xor B;
 B:= W;
 SetFilePointer(FHandle, -1, nil, FILE_CURRENT);
 WriteFile(FHandle, X, SizeOf(X), dwTemp, nil);
 end;
 
 { Шифруем байты на OEP }
 B:= EP_XorByte;
 J:= EP_EndOffset - EP_StartOffset;
 SetFilePointer(FHandle, EntryPointOffset, nil, FILE_BEGIN);
 for I:= 1 to J do
 begin
 ReadFile(FHandle, X, SizeOf(X), dwTemp, nil);
 W:= X;
 X:= X xor B;
 B:= W;
 SetFilePointer(FHandle, -1, nil, FILE_CURRENT);
 WriteFile(FHandle, X, SizeOf(X), dwTemp, nil);
 end;
 
 { Устанавливаем EntryPoint в начало нашей секции }
 ImageNtHeaders.OptionalHeader.AddressOfEntryPoint:  = NewSectionRVA;
 
 {*************************************************  ***************************}
 
 { Записываем новый PE-заголовок }
 SetFilePointer(FHandle, PEHeaderOffset, nil, FILE_BEGIN);
 WriteFile(FHandle, ImageNtHeaders, SizeOf(ImageNtHeaders), dwTemp, nil);
 
 Write('File successfully protected');
 Result:= True;  // все готово!
 Quit:
 CloseHandle(FHandle);
 end;
 
 var
 FileName: string;
 dwFlags: DWORD;
 
 procedure GetParams;
 var
 I: Integer;
 begin
 dwFlags:= PROTECTION_FLAG_SAVEOVERLAY or PROTECTION_FLAG_MAKEBACKUP;
 for I:= 2 to ParamCount do
 begin
 if UpperCase(ParamStr(I)) = '-B' then
 if (dwFlags and PROTECTION_FLAG_MAKEBACKUP <> 0) then
 Dec(dwFlags, PROTECTION_FLAG_MAKEBACKUP);
 if UpperCase(ParamStr(I)) = '-O' then
 if (dwFlags and PROTECTION_FLAG_SAVEOVERLAY <> 0) then
 Dec(dwFlags, PROTECTION_FLAG_SAVEOVERLAY);
 end;
 end;
 
 begin
 SetErrorMode(SEM_FAILCRITICALERRORS);
 Writeln(#10#13, 'ProtExample', #10#13);
 if ParamCount > 0 then
 begin
 FileName:= ParamStr(1);
 GetParams;
 Randomize;
 ProtectFile(FileName, dwFlags);
 end else begin
 Writeln('Usage: ProtExample [InputFile] [Options]');
 Writeln('');
 Writeln('Options:');
 Writeln('   -b  don''t create backup file (default on)');
 Writeln('   -o  don''t save overlay (default on)');
 end;
 end.
 |