Пишем криптор на Delphi (+ исходник криптора)
Пишем свой простой протектор (автор: Bagie) |
В данной статье я приведу пример простого протектора исполняемых
файлов. В ходе приведения исходного текста будут производиться
необходимые пояснения.
С чего же начать писать свой собственный протектор? Для начала нам
необходимо минимум теории. Также мы должны знать что хотим написать.
Писать мы будем простейший в своем роде протектор, который выполняет
следующее: шифрует байты на оригинальной точке входа (что это такое
читайте далее), стирает сигнатуры пакеров (в данном случае 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, а затем этим протектором, то “некоторые известные
антивирусы” вообще не определят, что троян чем-либо запакован, и
естественно троян не будет замечен :)
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.
Скачать исходник криптора на Делфи
18 комментариев к “Пишем криптор на Delphi (+ исходник криптора)”
Написать комментарий
RCE.SU рекомендует!
Поделиться
Свежие записи
- Видеокурсы по JAVA
- Наша компания займется созданием самого лучшего проекта – лендинг пейдж
- Как создать галерею Pixabay в Joomla
- Мы предлагаем создание сайтов с уникальной структурой и функционалом, разработанных под конкретные задачи клиента!
- Сайты для покупки-продажи недвижимости
- Enext.ua – копания по производству электротехнического оборудования
- Преимущества продвижения сайта в поисковых системах
- Анализ ключевых слов на новом уровне
- JOOMLA? Это как раз то, что вам было нужно!
Знаю, что текст местами вылазит, но что поделаешь + это не сильно мешает :)
поставьте плагин highlight подсветка кода, все кодеры любят святищиеся штучки))
Старая статья, которая скорее показывает возможность работы с пе форматом.
Флещъ, чтобы не вылазил ставь плагин wp-codebox и в него сорцы хуячь
Оке, спосебо :)
все отлично. вот ток врубится не могу как вывести свои сигнатуры…
Как известно в PE-файлах код хранится в секции (хотя это и не
обязательно, код, например, можно хранить в заголовках). Для этого нам
просто понадобится один из вариантов: дописать код к последней секции
файла или создать новую секцию. Первый вариант легче, а мы легкого пути
не ищем, поэтому выбираем второй.
DecryptSign[6]:= SD_XorByte; // XOR байт на делфи открыл здесь ошибка
включите самую нижнюю опцию (assignable typed constans) в настройках проекта:
https://rce.su/wp-content/uploads/2011/04/protnastr.png
интересная статья, но где вводиться обрабатываемый файл??? и как получить что то на выходе???извини за нубские вопросы, но тем не менее ….только начинаю позновать всё это))
программа консольная. рано вам еще такие статьи читать, ибо результата все равно не будет.
Напишите пожалуйста как его использовать
А можно сразу готовую и компилированную врсию
Здравствуйте, где можно найти инфу по опкоду? Хочу написать криптор, шифрующий RC4. Подскажите пожалуйста =)
Добрый день
не могли бы Вы описать как указывать путь к файлу? и что за процедура GetParams, о каких параметрах идёт речь? можно попродробней..
admin, ты м0л0дец, расписал в начале для нубов, про работу с PE форматом, о том как определить по заголовку прогу, данной инфы и так в инете полно, без твоей статьи..
А вот как изменить точку входа на свою, как зашифровать OEP, ну и собсна сама функа крипта – молчок(
Такое ощущение , что статью писали 2 разных человека…
Ну а конкретно по статье, для криптора, ИМХО, метку что файл уже криптован вообще лучьше убрать, так как по ней антивири и будут палить, так что уж если и делать её, то делать мусор+результат мат функции, что бы четкую сигнатуру положить нельзя было (ну а в идеале использовать асинхронные алгоритмы, шифровать публичным ключём а проверять приватным)..
Автор ты не против что я размещу это у себя на сайте? Естественно укажу копирайты и гиперссылку.
Автор – Bagie, а не я. Никакие гипрессылки указывать не обязательно. У меня когда-то эта статья была сохранена и я решил ею поделиться.