Найпростіший Joiner на Delphi і WinAPI
- Теорія найпростішого joiner'a
- можливості joiner'a
- всюдисущий API
- Кодима joiner
- приготування
- Програма-завантажувач
- Код програми-завантажувача
- тестування
- Що можна поліпшити
- висновок
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
Вихідний код Joiner на Delphi
Склеїти з іграшкою який-небудь корисний файл - що може бути простіше і необхідніше?Як його дізнатися?
Виникли питання або пропозиції?