Blog. Just Blog

Запись в архивы RAR, ZIP и ARJ без помощи архиватора

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Решил вспомнить свою юность, когда я занимался разработкой "самоходного программного обеспечения". Одной из особенностей моих творений было распространение не только через исполняемые файлы, но и через архивы различных форматов. Технология внедрения в архивные файлы не нова, она использовалась в разных вирусах еще со времен MS-DOS. Так как готовых решений на тот момент у меня не было, приходилось доходить до всего самому. Никакого вредоносного кода на этом сайте не появится, а вот некоторыми своими наработками по внедрению в архивы я с удовольствием поделюсь. Поскольку полноценно продублировать алгоритмы сжатия архиваторов в столь малом размере файла не представляется возможным, внедряться в архивы мы будем по методу "Store". Это означает, что файл в архив добавлен с опцией "без сжатия". Внутренние форматы различных архивных файлов, естественно, различаются, но у всех обязательно присутствуют служебные заголовки, используемые архиваторами, и, собственно, сами упакованные данные.

Самый простой для внедрения - формат RAR, используемый одноименным архиватором. Подробную спецификацию формата RAR можно почитать здесь.
  1. ;---------------------------------------------
  2. ; RAR Header
  3. ;---------------------------------------------
  4. rhcrc   dw      ?     ; --> Low-word CRC32 of fields in header
  5. rhtype  db      ?     ; Header type: 0x74
  6. rhflag  dw      ?     ; Bit flags
  7. rhsize  dw      ?     ; File header full size
  8. rcsize  dd      ?     ; Compressed file size
  9. rosize  dd      ?     ; Uncompressed file size
  10. rhoss   db      ?     ; Target OS version
  11. rfcrc   dd      ?     ; --> File CRC32
  12. rdtm    dd      ?     ; File Time/Date
  13. runp    db      ?     ; Archive version to extract
  14. rmeth   db      ?     ; Packing method (store)
  15. rnsize  dw      ?     ; File name size
  16. rfattr  dd      ?     ; File attributes
  17. rfname  rb      (?)   ; File name
Мнемокода "(?)" в FASM нет, просто таким образом я обозначил текстовую строку для записи имени файла неопределенной длины. В реальном проекте будет достаточно MAX_PATH или вообще фиксированного размера.
  1. ; "Хвост" архива - признак окончания данных
  2. tail    db      0C4h,03Dh,07Bh,00h,040h,07h,00h
  3. tail_length     = $-tail
Однако надо знать, что не во все RAR-архивы можно безопасно внедряться. Обязательно надо проверять флаговые биты в основном заголовке архива и пропускать заблокированные, многотомные, непрерывные архивы и архивы с цифровой подписью. Все эти проверки вы сможете сделать самостоятельно, здесь я их описывать не буду.

Переходим к внедрению. Предположим, что файл, который мы будем записывать в архив, уже загружен в память. Заполняем поля заголовка по порядку.
  • rhtype = 74h - тип заголовка (файл);
  • rhflag = 8000h - флаги;
  • runp = 14h - версия архиватора, необходимая для распаковки файла;
  • rmeth = 30h - метод упаковки файла (store);
  • rfattr = 20h - атрибуты файла (архивный);
  • rhoss = 2 - целевая ОС (Windows);
  • rdtm = время и дата создания файла в формате MS-DOS;
  • rfcrc = CRC32 оригинального файла;
  • rosize = размер оригинального файла;
  • rcsize = размер сжатого файла, равен оригинальному размеру;
  • rfname = имя файла;
  • rnsize = длина имени файла;
После этого считаем CRC32 заголовка, начиная от поля rhtype и до rfname включительно, затем надо взять младшее слово от результата расчета CRC32 и записать его в поле rhcrc. Все, заголовок заполнен. Открываем целевой архив для записи, и устанавливаем указатель на позицию 7 байт от конца файла, чтобы удалить "хвост" архива. Записываем наш заголовок, следом записываем внедряемый файл. После этого записываем 7-байтовый "хвост" архива. Точно таким же способом можно прицепляться к RAR-SFX архивам. На этом теорию внедрения в архивы формата RAR можно считать освоенной.

Внедрение в архивы формата RAR5 разобрано в отдельной статье.

Архиватор ARJ во времена MS-DOS фактически являлся стандартом архивирования, но сейчас утратил актуальность. Однако наработки по внедрению в него остались. Есть официальная коммерческая версия и бесплатная с открытым кодом, обе они совместимы между собой и используют одинаковый формат архива. Техническая спецификация формата ARJ есть в файле TECHNOTE.TXT из дистрибутива коммерческой версии. Дописывание файла к архиву ARJ делается немного сложнее, чем к RAR, но тоже не представляет больших трудностей.
  1. ;---------------------------------------------
  2. ; ARJ Header
  3. ;---------------------------------------------
  4. marker  dw      ?     ; Header ID
  5. bhsize  dw      ?     ; --> Basic header size (acrc-fhsize)
  6. fhsize  db      ?     ; --> First header size (afname-fhsize)
  7. anum    db      ?     ; Archive version number
  8. anum2   db      ?     ; Archive version to extract
  9. osver   db      ?     ; Target OS version
  10. aflag   db      ?     ; No any flags
  11. ameth   db      ?     ; Archive method (0 - stored)
  12. aftype  db      ?     ; File type (0 - binary)
  13. ares    db      ?     ; Reserved
  14. dtm     dd      ?     ; Date/Time last modification
  15. csize   dd      ?     ; Compressed size
  16. osize   dd      ?     ; Original size
  17. crc     dd      ?     ; --> Original file CRC32
  18. fspec   dw      ?     ; Filespec position in filename
  19. faccess dw      ?     ; File access mode
  20. hstdata dw      ?     ; Host data
  21. extra1  dd      ?     ; Extended file position
  22. edtma   dd      ?     ; Date-time accessed
  23. edtmc   dd      ?     ; Date-time created
  24. extra2  dd      ?     ; Original file size even for volumes
  25. afname  rb      (?)   ; File name (ASCIIZ)
  26. acomm   db      ?     ; File comment (ASCIIZ)
  27. acrc    dd      ?     ; --> Basic header CRC32
  28. ehsize  dw      ?     ; Extended header size
  1. ; "Хвост" архива - признак окончания данных
  2. tail    db      060h,0EAh,00h,00h
  3. tail_length     = $-tail
Записываемый файл также предварительно надо загрузить в память, после этого заполняем поля заголовка.
  • marker = 0EA60h - маркер заголовка архива;
  • anum = 6 - версия архиватора;
  • anum2 = 1 - версия архиватора для извлечения файла;
  • osver = 11 - ОС для запуска файла (Windows);
  • fhsize = 2Eh - размер первого заголовка от поля fhsize до afname;
  • dtm = время и дата создания файла в формате MS-DOS;
  • crc = CRC32 оригинального файла;
  • osize = размер оригинального файла;
  • csize = размер сжатого файла, равен оригинальному размеру;
  • fname = имя файла в формате ASCIIZ;
После этого надо посчитать размер полученного заголовка от поля fhsize до acomm включительно и вычислить его CRC32. Размер записывается в поле bhsize, а CRC32 в поле acrc. Остальные поля для нас значения не имеют и заполняются нулевыми значениями. Если интересно, можете почитать про них в документации. Открываем целевой архив для записи, и устанавливаем указатель на позицию 4 байта от конца файла, чтобы удалить "хвост" архива, как и в случае с форматом RAR. Записываем наш заголовок, за ним внедряемый файл и 4-байтовый "хвост" архива. Все, с форматом ARJ разобрались. Также не забывайте проверять флаги в главном заголовке архива, чтобы не повредить запароленные, многотомные и другие архивы, в которые нельзя добавлять файлы таким способом. Эти проверки сделайте самостоятельно.

Наиболее сложным для внедрения является формат ZIP. Здесь информация о сжатом файле хранится в нескольких местах: локальном заголовке и так называемой "центральной директории". Это сделано специально, чтобы можно было максимально быстро получить доступ к структуре архива, не просматривая целиком его содержимое. И именно из-за этой особенности при внедрении нам придется обрабатывать весь архив. Для больших архивов придется создавать временный файл, а в нашем случае вполне можно прочитать архив целиком в память. Спецификацию формата ZIP можно почитать на офсайте разработчиков. Этот же формат имеют архивы JAR, по сути это просто переименованные ZIP-файлы.
  1. ;---------------------------------------------
  2. ; ZIP-header (local)
  3. ;---------------------------------------------
  4. zlid    dw      ?     ; Header Id
  5. zlsig   dw      ?     ; Signature
  6. zlvneed dw      ?     ; Version Need
  7. zlflags dw      ?     ; Flags
  8. zlmeth  dw      ?     ; Method
  9. zldtm   dd      ?     ; DateTime
  10. zlfcrc  dd      ?     ; --> CRC32
  11. zlcsize dd      ?     ; Compressed Size
  12. zlosize dd      ?     ; Uncompressed Size
  13. zlnsiz  dw      ?     ; Size of Filename
  14. zlefild dw      ?     ; Size of Extra Field
  15. zlfname rb      (?)   ; Filename
  1. ;---------------------------------------------
  2. ; ZIP-header (central directory)
  3. ;---------------------------------------------
  4. zid     dw      ?     ; Header Id
  5. zsig    dw      ?     ; Signature
  6. zverm   dw      ?     ; Version Made
  7. zvneed  dw      ?     ; Version Need
  8. zflags  dw      ?     ; Flags
  9. zmeth   dw      ?     ; Method
  10. zdtm    dd      ?     ; TimeDate
  11. zfcrc   dd      ?     ; --> CRC32
  12. zcsize  dd      ?     ; Compressed Size
  13. zosize  dd      ?     ; Uncompressed Size
  14. znsiz   dw      ?     ; Size of Filename
  15. zefield dw      ?     ; Size of Extra Field
  16. zcomm   dw      ?     ; Comment Size
  17. zdnumb  dw      ?     ; Disk Number
  18. ziattr  dw      ?     ; Internal Attributes
  19. zeattr  dd      ?     ; External Attributes
  20. zohead  dd      ?     ; Offset Header
  21. zfname  rb      (?)   ; Filename
  1. ;---------------------------------------------
  2. ; ZIP-header (End of central directory)
  3. ;---------------------------------------------
  4. zeid    dw      ?     ; Header Id
  5. zesig   dw      ?     ; Signature
  6. zenumd  dw      ?     ; Number of this disk
  7. zenume  dw      ?     ; Number of this disk
  8. zetotal dw      ?     ; Total number of entries of disk
  9. zedir   dw      ?     ; Total number of entries of the central directory
  10. zesizec dd      ?     ; Size of the central directory
  11. zeoffs  dd      ?     ; Offset of start of central directory
  12. zecomm  dw      ?     ; ZIP file comment length
Локальный заголовок файла и заголовок файла в центральной директории по большинству полей совпадают, а значит, что их можно и нужно заполнять параллельно. Локальный заголовок:
  • zlid = 'PK' - сигнатура заголовка;
  • zlsig = 0403h - тип заголовка - локальный;
  • zlvneed = 10h - версия архиватора для извлечения файла;
  • zlflags = 80h - флаги файла (см. в документации);
  • zlmeth = 0 - метод упаковки без сжатия;
  • zldtm = время и дата создания файла в формате MS-DOS;
  • zlfcrc = CRC32 оригинального файла;
  • zlcsize = размер сжатого файла, равен оригинальному размеру;
  • zlosize = размер оригинального файла;
  • zlnsiz = длина имени файла;
  • zlfname = имя файла;
Центральная директория:
  • zid = 'PK' - сигнатура заголовка;
  • zsig = 0201h - тип заголовка - центральная директория;
  • zverm = 10h - версия архиватора для извлечения файла
  • zvneed = 10h - версия архиватора для извлечения файла;
  • zflags = 80h - флаги файла (см. в документации);
  • zmeth = 0 - метод упаковки без сжатия;
  • zdtm = время и дата создания файла в формате MS-DOS;
  • zfcrc = CRC32 оригинального файла;
  • zcsize = размер сжатого файла, равен оригинальному размеру;
  • zosize = размер оригинального файла;
  • znsiz = длина имени файла;
  • zeattr = 20h - атрибут файла (архивный)
  • zfname = имя файла;
После заполнения заголовков переносим упакованные файлы из исходного архива в новый. Для этого в цикле проверяем сигнатуру локальных заголовков с самого начала архива. Если она равна 0x04034b50, то вычисляем размер блока по сумме размера локального заголовка от поля zlid до zlfname, длины имени файла zlnsiz и длины дополнительных полей zlefild. Когда все упакованные файлы из исходного архива будут перенесены в новый, можно записывать в новый архив наш заполненный локальный заголовок и тело файла. Перед этим надо запомнить абсолютное смещение нашего локального заголовка файла относительно начала нового архива и заполнить этим значением поле zohead заголовка нашего файла в центральной директории. Архив может содержать дополнительные данные (Archive Extra Data), они расположены после файлов и оформлены своим заголовком с сигнатурой 0x08064b50. Формат и значения полей заголовка описаны в документации. Их также надо записать в новый архив. Теперь надо перенести все записи из центральной директории исходного архива в новый. Каждый блок идентифицируется по заголовку 0x02014b50, размер блока равен сумме размера заголовка центральной директории от поля zid до поля zfname, длины имени файла znsiz и длины дополнительных данных zefield. Перед переносом центральной директории надо также запомнить ее абсолютное смещение относительно начала нового архива, это значение потребуется при заполнении поля zeoffs завершающей структуры центральной директории (End of central directory record). Когда центральная директория исходного файла перенесена в новый архив, можно дописывать к ней заголовок центральной директории нашего файла. После этого в новый архив надо перенести завершающую структуру центральной директории, она начинается с сигнатуры 0x06054b50. Предварительно в ней надо увеличить на 1 значения полей, обозначающих количество файлов в архиве (zenumd и zenume) и размер центральной директории (увеличить текущее значение zesizec на размер заголовка нашего файла). Полный размер блока завершающей структуры центральной директории равен сумме размера ее заголовка от поля zeid до поля zecomm включительно и размера комментария архива zecomm. Вот, вроде бы и все. Если из описания процесс обработки ZIP-архивов не очень понятен, то смотрите исходники. Как вариант, можно обрабатывать содержимое архива не с начала файла, а сразу анализируя заголовки центральной директории. Такой способ вполне имеет право на существование и, наверное, может даже считаться более надежным.

UPD: При написании статьи я столкнулся с некоторыми JAR-архивами, у которых в локальном заголовке размеры файла и CRC32 были просто обнулены, в результате этого невозможно посчитать размер блока файла. Видимо программы, работающие с ними, ориентируются на данные центральной директории. Попытка внедриться в такие архивы описанным выше способом приведет к их безвозвратному повреждению, поэтому при этом способе внедрения такие файлы надо пропускать. Я разобрался со способом внедрения в архив через центральную директорию. Здесь алгоритм немного другой. Сперва надо найти End of central directory record. Она имеет фиксированный размер, но архив также может содержать комментарий, который записан ПОСЛЕ End of central directory record в конце файла, а размер комментария записан в нее же. Так что я не придумал ничего лучше, чем просто сканировать файл с конца по сигнатуре 0x06054b50, благо что максимальный размер комментария не может превышать значения WORD, то есть 65535 байт. Из найденной структуры мы извлекаем данные о местоположении центральной директории относительно начала файла. После этого начинаем по очереди перебирать записи из нее, а именно смещение упакованных файлов и их размеры, записывая их в новый архив. Здесь важно учитывать следующий момент: если в локальном заголовке файла в поле zlflags установлен 3-й бит, то информация о контрольной сумме, а также о размерах упакованного и оригинального файла содержатся в 16-байтном блоке data descriptor, который записан сразу же после упакованного файла и начинается с сигнатуры 0x08074b50. Причем в архиве могут одновременно быть файлы как с заполненными локальными заголовками, так и с неполными заголовками + data descriptor. Таким образом, мы через центральную директорию находим файл в архиве, записываем его в новый архив, проверяем наличие data descriptor, в случае его наличия также переносим в новый архив сразу после файла. Когда будут перенесены все файлы, записываем наш файл. После этого записываем оригинальную центральную директорию, данные нашего файла из центральной директории и скорректированную End of central directory record с файловым комментарием (при его наличии). Смотрите исходники, там понятнее. Таким образом можно корректно прицепляться к обычным архивам, JAR-архивам и даже документам OpenOffice (odt, ods) и MS Office 2010 (docx, xlsx), которые по сути также являются ZIP-архивами. Кроме того, при обработке центральной директории можно внедряться не только в конец, но и в середину архива, а также подменять собой уже имеющиеся в архиве файлы. Но это уже переходит границу добрых дел, поэтому останется только идеей.

Не забывайте обрабатывать многотомные и защищенные архивы (данные о шифровании хранятся в отдельном блоке). Еще есть 64-битные версии ZIP-архивов, они имеют несколько другой формат заголовков и не могут быть обработаны как обычные ZIP-архивы. Повторюсь, что подробное описание всех служебных заголовков и форматов можно найти в официальной документации.

К сожалению, современные архиваторы типа 7zip, WinRK, KuaiZip, WinArchiver и WinUHA имеют более сложный внутренний формат, там даже имена файлов подвергаются сжатию или хранятся в отдельном блоке, а KGB Archiver вообще не подразумевает возможность модификации своих архивов. Так что для них такие фокусы не прокатывают. Информацию по внедрению в более редкие виды архивов вы можете найти во второй, третьей и четвертой частях статьи.

В приложении примеры программ с исходными текстами, демонстрирующие все три описанных способа записи в архивы. При каждом запуске программа дописывает себя к архиву "example" соответствующего формата, выбирая случайное имя исполняемого файла.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Store.to.RAR.without.Archiver.Demo.zip (7,456 bytes)

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Store.to.ARJ.without.Archiver.Demo.zip (8,574 bytes)

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Store.to.ZIP.without.Archiver.Demo.zip (42,712 bytes)

Вариант внедрения в ZIP-архив с разбором структуры через центральную директорию. В архиве также приложены три примера разных файлов.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Store.to.ZIP.without.Archiver.Demo.Version.2.zip (63,091 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 17167 | Комментариев: 12

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (20.11.2013 в 00:08):
Jupiter, видел, читал. Осталось найти время на статью.
Jupiter (19.11.2013 в 23:54):
Формат RAR обновлён до версии 5.0
Заголовок архива теперь увеличен на 1 байт.
Посмотри сам, всё очень подробно описано:
http://www.rarlab.com/technote.htm#rarsign
ManHunter (26.02.2013 в 23:15):
Прикрутить можно что угодно, но какой смысл использовать кучу костылей, чтобы добавить чуть-чуть комфорта для работы с архиватором? Поэтому я и говорю, что это религиозный вопрос. Мне не нравится - я не использую. Кому-то нравится - он использует. Всё очень просто.
UNDYING (26.02.2013 в 22:37):
>>Или если бы любая винда отрабатывала его архивы как папки.
Powershell(v3):
Read-Archive -Format SevenZip "path_to_file"
и не только 7z, но и gzip, iso и др.
>>Или если бы в Total Commander можно было переименовывать файлы прямо в архиве, как это делается в ZIP.
В архивах как бы хранят информацию - извлекли, изменили, перепаковали. Ну и "тормознутость" 7-zip определяется опциями сжатия и/или "скоростью" ПК. На домашнем ПК или ноутбуке смысла в 7-zip нет, а на "бэкапящем" серванте (в виде tar.lzma или tar.xz) - вполне, например, в виде "снапшотов" ZFS, пожатых оным.

>>Если бы в нем была такая же удобная панель для работы и с возможностью просмотреть файл как в WinRAR.
В качестве программы для просмотра можно прикрутить тот же Lister от TotalCommander или аналог, или вообще "враппер написать", по F3/F4 - смотрим/редактируем.
ManHunter (26.02.2013 в 17:35):
Если бы в нем была такая же удобная панель для работы и с возможностью просмотреть файл как в WinRAR, то может быть я бы его вытерпел. Или если бы любая винда отрабатывала его архивы как папки. Или если бы в Total Commander можно было переименовывать файлы прямо в архиве, как это делается в ZIP. А пока что нафиг это тормозное убожество.
Руслан (26.02.2013 в 17:11):
А почему интересно не пользуешься 7z?
SVS (25.02.2013 в 20:37):
Jadavin, там, поди, в файлах всё нулями было забито, вот и распаковалось до таких размеров.
ManHunter (25.11.2011 в 20:48):
Ну так надо было попробовать этим же 7z с максимальными настройками компрессии, непрерывным архивом, словарем и прочими наворотами. Я параметры навскидку не скажу, т.к. не пользуюсь 7z по религиозным соображениям.
Jadavin (25.11.2011 в 20:35):
Секрет остаётся секретом.
Jadavin (02.09.2011 в 09:59):
Немного не в тему, но про архивацию. Несколько лет назад мне прислали cd-диск с софтом, заархивированным в 7-Zip. На cd помещалось 700 мб, естесственно. После разархивации весь софт весил почти 2 Гб.
Вопрос: каким образом можно так заархивировать файлы? Я пробовал разными архиваторами, но ничего не получилось. В чём секрет?
ManHunter (22.08.2011 в 11:56):
Добавил описание и пример внедрения в ZIP-архивы через центральную директорию.
Игорь (14.08.2011 в 19:18):
Спасибо, статья помогла разобраться с заголовками архивов.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0062 сек.) / Память: 4.5 Mb
Наверх