PDA

Просмотр полной версии : [статья] Пишем простейший джойнер файлов. Часть 1


becensed
19.11.2009, 19:53
Эта статья разбита на две части: 1. написание лоадера,
2. написание билдера.

Хочу сказать, что статья написана для _новичков_. Люди знающие здесь
ничего нового не узнают.


I. ИНСТРУМЕНТЫ.

- FASM ([Ссылки доступны только зарегистрированным пользователям]);
- немного терпения.


II. ЛОАДЕР. Что это, принцип работы нашего лоадера.

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

.-------------------,
¦ код распаковщика ¦
|-------------------|
¦ заголовок 1 ¦
¦ данные 1 ¦
|-------------------|
¦ заголовок 2 ¦
¦ данные 2 ¦
|-------------------|
¦ заголовок N ¦
¦ данные N ¦
`-------------------'
Схема 1. Простейший лоадер.

Но как распаковщик узнает, где он заканчивается, а где начинаются данные?
Очень просто. В самом распаковщике мы создадим константу и назовем ее,
например, ldr_size. Ей присвоим значение, равное размеру распаковщика.
Откуда мы узнаем размер распаковщика? Самый простой способ:

1. Собираем наш лоадер;
2. смотрим его размер;
3. присваиваем ldr_size размер;
4. пересобираем.

Таким образом, принцип работы нашего лоадера следующий:
а) открываем себя для чтения;
б) смещаемся на ldr_size байт;
в) обрабатываем заголовок;
г) обрабатываем данные (создаём (скрытый) и запускаем файл);
д) считываем следующий заголовок;
е) если его нет, то выходим, иначе пункт в).


III. НАПИСАНИЕ ЛОАДЕРА.

Ну что же, с теоретической частью закончили. Можно приступить к практике.
Писать сам лоадер мы будем на ассемблере, используя fasm. Ниже приведён
полный исходный код. Я его немного прокомментировал, чтобы новичку было
легче разобраться.
Как вы увидите, ничего сложного здесь нет. Читайте комментарии, смотрите
документацию, погоняйте в отладчике. Я не исключаю, что где-то мог допустить
ошибку. Значит вам её и исправлять.

;-------------------------------------------------------------------------------

format PE GUI 4.0

;-------------------------------------------------------------------------------

include 'win32ax.inc'

;-------------------------------------------------------------------------------

section '.data' data readable writeable

lnFileName dw 0 ; word для длины имени файла и
lnCmdLine dw 0 ; командной строки хватит вполне.
lnFileSize dd 0 ; длина файла (данных)

szFileName rb 100h ; резервируем 100h байт для имени файла и
szCmdLine rb 100h ; командной строки.

szModule rb 100h ; путь нашего модуля

ldr_size dd 0 ; здесь будет размер лоадера, после ассемблирования

hModule dd 0 ; Хендл файла-лоадера
hFiles dd 0 ; Хендл созданных файлов (данных)
hAllocMem dd 0 ; Для резервирования памяти (для файлов)
nBytesRead dd 0 ; Количество прочитанных байт

;-------------------------------------------------------------------------------

section '.code' code readable writeable executable

entry $
; Получим путь к нашему файлу
invoke GetModuleFileName, NULL, szModule, 100h

; Открываем наш файл для чтения/записи
invoke CreateFile, szModule, GENERIC_READ, \
FILE_SHARE_READ or FILE_SHARE_WRITE, \
NULL, \
OPEN_EXISTING, \
0, 0
cmp eax, -1 ; если не открылся
jz .close ; то выйдем
mov [hModule], eax ; сохраним хендл открытого файла

;----------------------------------------------,
; 2560 я получил после первого ассемблирования |
; Например, упаковав лоадер с помощью upack, |
; я получил размер 1280 байт. Я их впишу сюда, |
; пересоберу и снова запакую. Только тогда |
; распаковщик отработает корректно. |
mov [ldr_size], 2560; |
;----------------------------------------------'

; Установим указатель в файле
; на конец распаковщика (FILE_BEGIN+lde_size)
invoke SetFilePointer, [hModule], [ldr_size], 0, FILE_BEGIN
.loop:
; Прочитаем наш заголовок (8 байт)
invoke ReadFile, [hModule], lnFileName, 8, nBytesRead, 0
test dword [nBytesRead], -1 ; если ничего не прочиталось,
jz .close_1 ; то выйдем (ошибка)

movzx eax, word [lnCmdLine] ; eax = длина параметров

; Прочитаем командную строку из заголовка
invoke ReadFile, [hModule], szCmdLine, eax, nBytesRead, 0
test dword [nBytesRead], -1
jz .close_1

movzx eax, word [lnFileName] ; eax = длина имени файла
or eax, eax ; если 0 (файлов нет),
jz .close_1 ; то выйдем

; иначе прочитаем имя файла
invoke ReadFile, [hModule], szFileName, eax, nBytesRead, 0
test dword [nBytesRead], -1
.close_1: jz .the_end

; и создадим файл с именем szFileName
invoke CreateFile, szFileName, GENERIC_WRITE, FILE_SHARE_READ, 0, \
CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, 0
cmp eax, -1 ; если не создался,
je .loop ; то начнем заново
mov [hFiles], eax ; сохраним хендл файла

; Выделяем память по размеру файла
invoke GlobalAlloc, GMEM_FIXED, [lnFileSize]
or eax, eax
jz .close
mov [hAllocMem], eax

; Прочитаем файл в выделенную память
invoke ReadFile, [hModule], [hAllocMem], [lnFileSize], nBytesRead, 0
test dword [nBytesRead], -1
jz .close

; А теперь из памяти запишем на диск
invoke WriteFile, [hFiles], [hAllocMem], [lnFileSize], nBytesRead, 0
.close:
invoke CloseHandle, [hFiles] ; Закреом хендл файла (данных)
invoke GlobalFree, [hAllocMem] ; Освободим память

; Запустим файл
invoke ShellExecute, 0, 0, szFileName, szCmdLine, 0, SW_SHOW
jmp .loop
.the_end::
invoke CloseHandle, [hModule] ; закроем хендл лоадера
.exit:
invoke ExitProcess, 0 ; выход

;-------------------------------------------------------------------------------

section '.idata' import data readable

library kernel32,'KERNEL32.DLL',\
shell32,'SHELL32.DLL'
include 'api\kernel32.inc'
include 'api\shell32.inc'

;-------------------------------------------------------------------------------

IV. ПОСЛЕСЛОВИЕ.

В следующей части я покажу, как написать простейший билдер к нашему
лоадеру и получить полноценный склейщик файлов. А пока, в качестве
домашнего задания, попробуйте сделать билдер сами.
Также, файлы легко склеить руками. Нам известен заголовок. Остается его
заполнить. Покажу на простом примере.
Имеется файл fasmw.exe. Необходимо прикрепить его к лоадеру, чтобы тот
запустил его с параметрами "hello.asm".

Итак: - имя файла: fasmw.exe (9 байт + 0 в конце, итого 10 или же 0Ah)
- командная строка: hello.asm (тоже 9 байт + 0 в конце, итого 0Ah).
- размер fasmw.exe: 122 880 байт (0001E000h)

То есть заголовок у нас будет такой:

длина длина размер
имени комм. файла
файла строки fasmw.exe
.-^-. .--^--. .----^----.
0A 00 0A 00 00 E0 01 00 дальше идут параметры и имя файла.
\ / \ / \ /
размер: WORD WORD DOUBLE WORD

Всё! Теперь вы в начало fasm.exe запишем заголовок и смело весь файл
припишем в конец нашему лоадеру.

Просто сравните два файла fasmw.exe до и после добавления заголовка
для нашего лоадера.

файл fasmw.exe ДО обработки:

00000000: 4D 5A 80 00-01 00 00 00-04 00 10 00-FF FF 00 00 MZА **
00000010: 40 01 00 00-00 00 00 00-40 00 00 00-00 00 00 00 @ @
00000020: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
00000030: 00 00 00 00-00 00 00 00-00 00 00 00-80 00 00 00 А
00000040: 0E 1F BA 0E-00 B4 09 CD-21 B8 01 4C-CD 21 54 68 ***9553; ***9508; ***9552;!***9557; L***9552;!Th
00000050: 69 73 20 70-72 6F 67 72-61 6D 20 63-61 6E 6E 6F is program canno
00000060: 74 20 62 65-20 72 75 6E-20 69 6E 20-44 4F 53 20 t be run in DOS
00000070: 6D 6F 64 65-2E 0D 0A 24-00 00 00 00-00 00 00 00 mode. $
00000080: 50 45 00 - - - PE


А теперь файл fasmw.exe ПОСЛЕ обработки:

длина длина длина
имени комм. файла Далее идут командная
файла строки fasmw.exe строка и имя файла
00000000: 0A 00 0A 00 00 E0 01 00 68 65 6C 6C-6F 2E 61 73 а hello.as
00000010: 6D 00 46 41 53 4D 57 2E 45 58 45 00-4D 5A 80 00 m FASMW.EXE MZЂ
^^^^^^^^^^^
отсюда пошёл fasm.exe
00000020: 01 00 00 00 04 00 10 00 FF FF 00 00-40 01 00 00 яя @
00000030: 00 00 00 00 40 00 00 00 00 00 00 00-00 00 00 00 @
00000040: 00 00 00 00 00 00 00 00 00 00 00 00-00 00 00 00
00000050: 00 00 00 00 00 00 00 00 80 00 00 00-0E 1F BA 0E Ђ є
00000060: 00 B4 09 CD 21 B8 01 4C CD 21 54 68-69 73 20 70 ґ Н!ё LН!This p
00000070: 72 6F 67 72 61 6D 20 63 61 6E 6E 6F-74 20 62 65 rogram cannot be
00000080: 20 72 75 6E 20 69 6E 20 44 4F 53 20-6D 6F 64 65 run in DOS mode
00000090: 2E 0D 0A 24 00 00 00 00 00 00 00 00-50 45 00 . $ PE

Вот мы и написали самый простой лоадер для самого простого джойнера.
Разобрали, как можно им пользоваться, даже без билдера.

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

Не спрашивайте, ищите ответы _сами_.

Тогда и только тогда вы легко сможете сделать джойнер более навороченным,
добавить свой функционал.


(c) becensed


Я статьи писать не мастер. Критика по орфографическим ошибкам и недочетам, коих я не заметил - принимается. Всё остальное (гавно, палиться, баян) - игнорирую.

Спасибо, что уделили время моей статье. Надеюсь, она кому-то поможет понять принцип и написать что-то своё, более сложное, более функциональное.