Персональный блог Игоря Антонова aka "spider_net"

Простейший Joiner на Delphi и WinAPI


Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: | |
Просмотров: 7800
Простейший Joiner на Delphi и WinAPI

Joiner – программа, которой пользуется большинство начинающих хакеров. Склеить с игрушкой какой-нибудь полезный файл - что может быть проще и необходимее? Да что говорить, стоит зайти на какой-нибудь форум типа vingrad.ru или antichat.ru и можно встретить кучу топиков, в которых кодеры слезно просят объяснить принцип написания подобных программ. Но, как правило, более продвинутые авторы посылают таких программеров (нет, не туда) изучать скучную теорию. В результате у многих отпадает желание творить. Мы не будем никуда тебя посылать, а расскажем и покажем, как же все-таки создать такое «чудо».

Зачем

Как известно, joiner’ы используют для склейки троянов/вирусов/полезного стаффа (нас с тобой интересует только последнее, ведь мы не какие-нибудь преступники) с какой-либо полезной и безобидной программкой. Из этого следует, что на подготовленный joiner’ом файл не должны кричать благим матом антивирусы, иначе бедный юзер начнет суетиться и уничтожит весь взвод еще при высадке. Кстати говоря, производители антивирусов очень не любят подобные программы и с завидной регулярностью вносят их имена в свои базы данных. А если joiner палится антивирусами, то пользы от него на три копейки.

Все паблик-джойнеры рано или поздно попадают в антивирусные базы. Единственный выход – сделать joiner самому!

Теория самого простого joiner’a

Как работают эти чудо-программки? На самом деле все не просто, а очень просто. Структуру joiner’a можно представить следующим образом:

  • программа-конструктор – собирает специальным образом исполняемый файл, записывая программу-загрузчик, блок с информацией и все необходимые файлы;
  • загрузчик – программа, которая будет загружать все файлы, записанные в ее теле (позже я объясню подробнее, что подразумевается под телом).
  • После запуска созданного программой-конструктором файла, загрузчик прочитает блок с информацией, в котором есть данные обо всех прикрепленных файлах, и затем будет по очереди вытаскивать их из своего тела. Под телом подразумевается часть файла программы-загрузчика. Говоря еще более простым языком, все прикрепляемые файлы мы будем просто дописывать в конец файла программы-загрузчика. Чтобы лучше понять принцип действия, взгляни на структуру создаваемого программой-конструктором файла:

  • Код программы-загрузчика. Именно он будет выполняться в первую очередь.
  • Блок с информацией. В этом блоке будет содержаться информация обо всех прикрепленных файлах (размер, имя, всевозможные опции и т.д.).
  • Файлы – все прикрепленные файлы.
  • Возможности joiner’a

    Рассказать, как скрепить два файла, слишком просто, да и неинтересно. Поэтому я решил показать тебе, как можно написать программу, которая будет склеивать до 11 файлов и устанавливать для них различные опции. Под опциями я подразумеваю изменение поведения файлов при расстыковке с программой-загрузчиком. Например, неплохо иметь возможность задачи пути для распаковки сразу в определенную папку (например, папку с Windows) или установки атрибута «скрытый».

    Joiner на Delphi и Windows API. Главное окно

    Вездесущий API

    Перед тем как приступить к кодингу, нужно рассмотреть функции, которые нам потребуются (программировать мы будем в основном на WinAPI в целях сокращения размера экзешника):

    function CopyFile(lpExistingFileName, lpNewFileName: PChar; bFailIfExists: BOOL): BOOL;

    Как видно из названия, эта функция предназначена для копирования файлов. Функции нужно передать три параметра: lpExistingFileName – имя файла, который будет копироваться; lpNewFileName – имя для скопированного файла; bFailIfExists – флаг, говорящий о необходимости перезаписи файла в случае его существования.

    function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareMode: DWORD;
    lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
    hTemplateFile: THandle): THandle; stdcall;

    Функция определена для создания/открытия файлов/объектов. После своего выполнения функция вернет указатель на открытый/созданный файл. В качестве параметров ей нужно передать:

  • lpFileName – имя открываемого объекта. В качестве объекта может быть обычный путь к файлу, путь в формате UNC (для открытия файлов, расположенных в сети, обращения к устройствам и т.д.).
  • dwDesiredAccess – тип доступа к объекту. Здесь можно указать GENERIC_READ (для чтения) и GENERIC_WRITE (для записи). Помимо этих значений, в параметре можно просто указать 0. В таком случае легко получить атрибуты файла.
  • dwShareMode – устанавливает режим совместного доступа к файлу. Этот параметр принимает одно из следующих значений: 0 – совместный доступ запрещен; FILE_SHARE_READ – доступ для чтения; FILE_SHARE_WRITE – доступ для записи. Если необходимо установить полный доступ, то можно просто сложить два значения (FILE_SHARE_READ+FILE_SHARE_WRITE).
  • lpSecurityAttributes – атрибуты безопасности файла. Указав в этом параметре nil, мы задаем открываемому объекту атрибуты по умолчанию.
  • dwCreationDistribution – способ открытия файла. Тебе доступно:
  • CREATE_NEW – создается новый файл. Если создаваемый файл существует, то происходит ошибка.
  • CREATE_ALWAYS – создается новый файл. Здесь наоборот: если файл существует, то он будет перезаписан.
  • OPEN_EXISTING – открывается существующий файл. Если файла не существует, возникает ошибка.
  • OPEN_ALWAYS – открывается существующий файл. Если его нет, то он создается.
  • dwFlagsAndAttributes – атрибуты и флаги для открытия объекта (скрытый, системный и т.д.). Могут быть:
  • FILE_ATTRIBUTE_ARCHIVE – архивный;
  • FILE_ATTRIBUTE_COMPRESSED – сжатый;
  • FILE_ATTRIBUTE_NORMAL – архивный, сжатый;
  • FILE_ATTRIBUTE_HIDDEN – скрытый;
  • FILE_ATTRIBUTE_READONLY – только для чтения;
  • FILE_ATTRIBUTE_SYSTEM – системный.
  • hTemplateFile – файл-шаблон, атрибуты которого будут использоваться для открытия.
  • Как открывать/создавать файлы, мы знаем. Теперь неплохо было бы разобраться, как можно перемещаться по телу файла. Для этого в наборе Windows API есть функция SetFilePointer.

    function SetFilePointer(hFile: THandle; lDistanceToMove: Longint;
    lpDistanceToMoveHigh: Pointer; dwMoveMethod: DWORD): DWORD; stdcall;
  • hFile – дескриптор файла, например, полученный с помощью CreateFile.
  • lDistanceToMove – количество байт, на которое будет передвинута текущая позиция курсора.
  • lpDistanceToMoveHigh – адрес старшего байта. Используется при перемещении в очень больших файлах.
  • dwMoveMethod – способ перемещения по файлу. Доступно три способа: FILE_BEGIN – передвижение от начала файла; FILE_CURRENT – передвижение от текущей позиции; FILE_END – от конца файла.
  • После выполнения SetFilePointer возвращает младший байт 64-разрядной позиции в файле.

    function ReadFile(hFile: THandle; var Buffer; nNumberOfBytesToRead: DWORD;
    var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;

    Joiner на Delphi и Windows API. Смотрим размер скомпилированного проекта

    Для чтения из файла в Windows предусмотрена функция ReadFile, для записи - WriteFile. У этих функций схожие параметры:

  • hFile – дескриптор файла;
  • Buffer – буфер, в который будут записаны прочитанные (или из которого будут записаны) данные;
  • nNumberOfBytesToRead – количество данных, которые нужно прочитать/записать;
  • lpNumberOfBytesRead – количество фактически прочтенных/записанных данных;
  • lpOverlapped – указатель на структуру типа OVERLAPPED.
  • После успешного выполнения функция возвращает true. Поработав с файлами, их нужно закрыть. Для закрытия файлов припасена функция CloseHandle. Единственное, что ей нужно передать, – дескриптор открытого файла.

    Кодим joiner

    Как я уже говорил, нам придется написать две программы: программу-конструктор и программу-загрузчик. Начнем с программы-конструктора. Запускай Delphi и создавай пустой проект. Форму приведи к виду, представленному на рисунке.

    Joiner на Delphi и Windows API. Выбираем файлы для склейки

    Скажу пару слов о назначении элементов управления на форме. В ListView1 будут добавляться файлы для склейки. Все файлы будут приклеиваться к первому в списке. Для добавления очередного файла предназначена кнопка «Добавить». По ее нажатию будет вызываться OpenDialog. О предназначении кнопки «Склеить», думаю, ты догадаешься сам :). CheckBox’ами можно устанавливать опции для любого выделенного файла. Изменения сохраняются по нажатию кнопки «Установить опции».

    Приготовления

    Перейди в редактор кода и объяви следующую запись:

    FileInfRecord = record
    _filesize: array [0..10] of cardinal;
    _filename: array [0..10] of string[100];
    _autorun:array [0..10] of boolean;
    _windir:array [0..10] of boolean;
    _hideAttr: array [0..10] of boolean;
    _hideRun: array [0..10] of boolean;
    _fileCount: cardinal;
    End;

    Помнишь, я рассказывал тебе о блоке с информацией? Вот это он и есть. В этом блоке мы будем хранить имена (_fileName), размеры (_fileSize), опции всех файлов, которые будут прилеплены (максимум 11). В разделе объявления глобальных переменных объяви переменную _fileHeader типа FileInfRecord. Теперь создавай обработчик события OnClick для кнопки «Склеить» и переписывай в него код из соответствующей врезки.

    _distFile, _fromFile:THandle;
    _buff: array [0..1024] of Char;
    i:Integer;
    _temp, _temp2:cardinal;
    begin
    //Проверяем, а не много ли у нас файлов
    if ListView1.Items.Count>length(_fileHeader._filesize) then
    begin
    ShowMessage('Слишком много файлов');
    Exit;
    end;
    if not SaveDialog1.Execute then
    Exit;
    //Копируем файл загрузчика
    CopyFile(pchar(extractFilePath(application.ExeName)+
    'loader.exe'), pchar(SaveDialog1.FileName), true);
    Sleep(100);
    //Записываем в структуру количество файлов
    _fileHeader._fileCount:=ListView1.Items.Count;
    //Поочередно обрабатываем все файлы
    for i:=0 to ListView1.Items.Count-1 do
    begin
    _fileHeader._filename[i]:=ListView1.Items.Item[i].Caption;
    _fileHeader._filesize[i]:=GetFileSized(ListView1.Items.Item[I].SubItems.Strings[0]+
    ListView1.Items.Item[I].Caption);
    end;
    //Открываем наш загрузчик
    _distFile:=CreateFile(pchar(SaveDialog1.FileName), GENERIC_WRITE, 0, nil,
    OPEN_EXISTING, 0, 0);
    //Перемещаемся в самый конец
    SetFilePointer(_distFile, 0, nil, FILE_END);
    //Записываем наш заголовок
    WriteFile(_distFile, _fileHeader, sizeOf(_fileHeader), _temp, nil);
    //По очереди записываем все файлы
    for i:=0 to ListView1.Items.Count-1 do
    begin
    _fromFile:=CreateFile(pchar(ListView1.Items.Item[i].SubItems.Strings[0]+
    ListView1.Items.Item[i].Caption), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
    repeat
    ReadFile(_fromFile, _buff, sizeOf(_buff), _temp, nil);
    WriteFile(_distFile, _buff, _temp, _temp2, nil);
    ZeroMemory(@_buff, sizeOf(_buff));
    until _temp<>1025;
    CloseHandle(_fromFile);
    end;
    //Закрываем файл
    CloseHandle(_distFile);
    ShowMessage('Склейка успешно завершилась!');
    end;

    Joiner на Delphi и Windows API. Операция склейки выполнена успешно

    Отбросим рассмотрение вызова диалогов открытия/сохранения в нашем коде и перейдем к самой главной части. Первым делом нам нужно скопировать наш загрузчик в место, которое пользователь выбрал в диалоге сохранения. Именно в этот самый файл мы и будем записывать все подготовленные файлы. После функции копирования файла (CopyFile) я вызываю процедуру Sleep, которая выполняет задержку. Это нужно для того, чтобы наш загрузчик успел скопироваться. Дело в том, что функция CopyFile возвращает результат до того, как файл фактически будет скопирован.

    После того как файл загрузчика появится в назначенном месте, приступим к заполнению нашей структуры. Для начала узнаем, сколько всего файлов мы будем записывать. Поскольку файлы, подготовленные для склейки, хранятся в ListView, узнать их количество можно, обратившись к свойству Count. После этого можно приступить к записи информации в структуру. В качестве информации нас будет интересовать имя и размер файла. Обрати внимание на то, как я получаю размер файла:

    _fileHeader._filesize[i]:=GetFileSized(ListView1.Items.Item[I].SubItems.Strings[0]+ ListView1.Items.Item[I].Caption);

    Я использую самописную функцию GetFileSized, которой в качестве параметра передается лишь имя файла, а она возвращает его размер. Код этой функции я рассматривать не буду, поскольку в нем нет ничего сложного. К тому же ты всегда можешь обратиться к исходнику примера, который ждет тебя на диске.

    Структура заполнена, вся информация о файлах получена, значит, можно приступать к склеиванию всех файлов. Для этого я открываю файл программы-загрузчика с помощью знакомой тебе CreateFile. После успешного открытия нужно переместиться в самый конец. В этом мне помогает функция SetFilePointer. Ты знаешь, что, действуя согласно изложенной выше теории, сначала необходимо записать блок с информацией. Именно это и делаем:

    WriteFile(_distFile, _fileHeader, sizeOf(_fileHeader), _temp, nil);

    Записав нашу структуру с необходимой информацией, можно запускать цикл, в котором по очереди будут открываться и записываться в программу-загрузчик все подготовленные пользователем файлы. Копирование файла в файл загрузчика реализовано в цикле:

    repeat
     ReadFile(_fromFile, _buff, sizeOf(_buff), _temp, nil);
     WriteFile(_distFile, _buff, _temp, _temp2, nil);
     ZeroMemory(@_buff, sizeOf(_buff));
     until _temp<>1025;

    Для ускорения копирования чтение файла-источника происходит блоком. За один раз читается и записывается ровно 1024 байта. После записи очередной порции данных нужно позаботиться об очищении памяти. В модуле Windows.pas для этого предусмотрена процедура ZeroMemory. От нас требуется только передать ей два параметра: указатель на буфер, который подлежит очистке, и его размер.

    На этом месте напрашивается вывод, что программа-конструктор готова. Но ведь мы не рассмотрели процесс выставления опций! Думаю, с ним у тебя получится разобраться самостоятельно, ну а если будет трудно - пиши мне. Разберемся вместе!

    Программа-загрузчик

    Конструктор у нас есть. Но вот беда: без программы-загрузчика он мало чем полезен, поэтому нам придется создать новый пустой проект и написать в нем несколько строчек кода. Для программы-загрузчика форма нам не потребуется, поэтому сразу ее удаляй. Вообще, программа-загрузчик должна иметь минимальный размер, а значит, нужно избавиться от всего лишнего. Удали из Uses все модули, оставь лишь Windows и ShellAPI. Их нам будет вполне достаточно. Опиши структуру FileInfRecord. Она должна выглядеть точно так же, как и в программе-конструкторе. Если ты укажешь разные размеры массивов или еще чего-нибудь, то наш загрузчик будет неправильно работать (точнее, не будет работать вовсе).

    Создай константу mySize. В этой константе у нас будет храниться наш собственный размер, то есть размер программы-загрузчика. На данном этапе мы его не знаем, поэтому пока указываем 0. Код программы-загрузчика приведен в соответствующей врезке. Для экономии места я вырезал из него код, который отвечает за обработку опций. Полный вариант ты, как всегда, можешь найти на диске.

    Код программы-загрузчика

    VAR
    _fileDist, _fileSource:THandle;
    _fileHeader:FileInfRecord;
    i, j:cardinal;
    _buff:char;
    _temp:cardinal;
    BEGIN
    _fileSource:=Createfile(pchar(ParamStr(0)), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);
    SetFilePointer(_fileSource, mySize, nil, FILE_BEGIN);
    ReadFile(_fileSource, _fileHeader, sizeOf(_fileHeader), _temp, nil);
    if _fileHeader._fileCount=0 then Exit;
    for i:=0 to _fileHeader._FileCount-1 do
    begin
    _fileDist:=CreateFile(pchar(string(_fileHeader._filename[i])),
    GENERIC_WRITE, FILE_SHARE_WRITE,
    nil,
    CREATE_NEW, 0, 0);
    for j:=1 to _fileHeader._filesize[i] do
    begin
    ReadFile(_fileSource, _buff, sizeOf(_buff), _temp, nil);
    WriteFile(_fileDist, _buff, sizeOf(_buff), _temp, nil);
    end;
    CloseHandle(_fileDist);
    Sleep(100);
    end;
    CloseHandle(_fileSource);
    END.

    Сначала нам нужно открыть для чтения файл программы-загрузчика, то есть самого себя. После открытия выполняем смещение до адреса, с которого начинается код нашего блока с информаций. Как его узнать? Очень просто! Поскольку программа-конструктор записала структуру с информаций в самый конец программы-загрузчика, нужно просто перейти в файле на размер файла загрузчика. Этот размер у нас будет определен в объявленной ранее константе. Позиционирование в файле опять же выполняется с помощью SetFilePointer. При переходе на нужную позицию становится возможным считать структуру. А раз так, то после выполнения «ReadFile(_fileSource, _fileHeader, sizeOf(_fileHeader), _temp, nil); » вся наша структура будет считана. Ну а это значит, что мы обладаем всей необходимой информацией для выдергивания остальных файлов. Код разбивки тела загрузчика на файлы похож на код программы-конструктора, поэтому не будем на нем останавливаться. Окончательно дописав код и прочитав предыдущие строки, скомпилируй проект. После завершения компиляции зайди в меню «Project -> Information» и обрати внимание на строку File Size.

    В ней указан конечный размер exe нашего проекта. У меня он равен 16384. Именно это число нужно присвоить нашей константе mySize. После этого еще раз сохраняй все изменения в проекте и выполняй компиляцию. Все, наш joiner полностью готов, а значит, пора переходить к тесту.

    Тестирование

    Перед тестом скопируй скомпилированный файл загрузчика в папку, в которой у тебя лежит конструктор. Если ты помнишь, то именно в этой папке наш конструктор будет его искать. Теперь попробуй запустить конструктор, добавить несколько файлов и нажать на кнопку «Склеить». Подумав пару секунд (время напрямую зависит от размера выбранных тобой файлов), программа радостно отрапортует тебе о завершении процесса склейки и создаст новый файл.

    В проводнике появился файл с именем test. Это и есть результат работы программы. После его запуска в этой же директории оказываются все прикрепленные нами файлы. Таким образом, программа прошла тест-драйв.

    Что можно улучшить

    В статье я рассмотрел самый простой вариант joiner’а. Но ты не должен на этом останавливаться. Вот некоторые идеи, которые также хорошо было бы реализовать в программе такого типа:

  • Убрать ограничение количества добавляемых файлов.
  • Сделать возможной смену иконки для конечного файла, чтобы не вызывать лишних подозрений у пользователя.
  • Расширить набор опций. Вот здесь есть, где разгуляться. Чтобы представлять себе примерно, что можно реализовать, я советую тебе скачать парочку joiner’ов и посмотреть, какие опции реализованы там. Несколько ссылок на такие программы ты увидишь во врезке.
  • Уменьшить размер загрузчика. Это можно сделать как минимум двумя способами: во-первых, переписать программу на асме, а во-вторых, оптимизировать мой вариант. Казалось бы, оптимизировать уже некуда, но если ты почитаешь статью «Сверхмалые приложения» от @dmin на сайте http://www.mashp.h10.ru, то у тебя могут появиться некоторые мысли.
  • Встроить поддержку шифрования. Согласись, было бы здорово, если все прикрепленные файлы шифровались. Таким образом, антивирусы раньше времени не рычали бы на твой файлик.

    Реализовать возможность упаковки прикрепляемых файлов. Чем меньше будет конечный результат, тем лучше.

    Вывод

    Итак, сегодня твой арсенал пополнился еще одной полезной программой собственного производства, и ты в очередной раз убедился, что нет ничего невозможного. Просто для достижения любой цели требуется время и силы. На этой ноте я хочу попрощаться, удачи тебе в твоих экспериментах! Возникли вопросы или предложения? Пиши!

    Чтобы сделать свой joiner круче, чем у конкурентов, надо быть в курсе того, чего они наворотили у себя. Joiner’ов в инете пруд пруди, вот наиболее интересные:

  • MicroJoiner (www.cobans.net) – качественный joiner от нашего соотечественника. Он написан полностью на асме, распространяется в исходниках и очень хорошо работает. Советую обязательно его скачать и посмотреть.
  • Суперклей (www.bonza.narod.ru) – еще один неплохой представитель joiner’ов. Он обладает большим функционалом, чем MicroJoiner. Единственный его минус - размер созданного файла на порядок больше, чем у MicroJoiner.
  • Статья опубликована журнале "Хакер" (http://xakep.ru). Август 2007 г.

    Ссылка на опубликованную статью сайта издания: http://goo.gl/OcFIKy

    Ссылка на журнал: http://goo.gl/smmfv6

    Исходный код Joiner на Delphi

    Оставьте комментарий!
    comments powered by HyperComments