Blog. Just Blog

Запись в архивы RAR5 без помощи архиватора

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Сегодня я расскажу, как можно добавить какой-нибудь файл в архив формата RAR5 без использования программ-архиваторов. Что это такое и для чего вообще надо, об этом можно почитать в предыдущих статьях.

Формат RAR5 появился с выходом 5-й версии архиватора WinRAR. Он очень сильно отличается от старого формата архивов RAR, изменились структуры заголовков, появились дополнительные поля, некоторые поля вовсе исчезли, превратившись в самостоятельные блоки данных. Одним из главных нововведений, которое приходится учитывать, это появление данных так называемого формата vint - "variable length integer". Это последовательности байт заранее неизвестной длины в количестве от 1 до 10, чего достаточно для манипуляций с 64-битными числами. Как написано в документации, если понадобится, то максимальная длина последовательностей может быть увеличена. Каждый байт последовательности vint имеет только 7 младших значащих битов, старший 8-й бит является информационным. Если он равен 1, то требуется обрабатывать следующий байт последовательности, если 0, то это последний байт данных в текущей последовательности. Оставшиеся значащие биты слепляются в полноценные байты, из которых формируется итоговое целочисленное значение.

100011010 01011011 => 000001101 01011011
В некоторых случаях я могу оправдать такой подход, например, размер файла может быть как несколько терабайт, так и несколько байт, длина vint-последовательности для хранения таких значений будет разной. Хотя и тут можно поспорить, я не думаю, что размер бытовых файлов не поместится в 64-битное число, а для архивирования петабайт данных этих ваших интернетов наверняка есть какие-то более другие решения. Если взять всякую мелочевку типа флагов, идентификатора заголовка, атрибутов файла и подобного, почему нельзя было оставить фиксированные значения? Вряд ли автор совершит столь большие прорывы в разработке WinRAR, что понадобится QWORD для флагов или WORD для типов заголовков. А в документации сплошь и рядом поля формата vint. Но это создает проблемы в основном при чтении имеющегося архива, тогда как при записи можно позволить себе определенные упрощения, зафиксировав размер некоторых полей.

Для работы со значениями типа vint я написал две вспомогательные функции, которые переводят последовательность vint неизвестной длины в целое число и наоборот, из целого числа создают последовательность vint. Тут надо иметь в виду, что размерность входных и выходных данных в этом примере ограничена DWORD. Проверок корректности входных данных типа превышения лимита в 10 байт также не производится.
  1. ;-----------------------------------------------------------
  2. ; Преобразование DWORD в vint
  3. ;-----------------------------------------------------------
  4. ; На входе:
  5. ;    dValue - преобразуемое значение
  6. ;    lpVint - указатель на память для записи vint
  7. ; На выходе:
  8. ;    EAX = количество записанных байт
  9. ;-----------------------------------------------------------
  10. proc int2vint dValue:DWORD, lpVint:DWORD
  11.         xor     eax,eax
  12.         pusha
  13.  
  14.         mov     ebx,[dValue]
  15.         mov     edi,[lpVint]
  16. @@:
  17.         mov     al,bl
  18.  
  19.         shr     ebx,7
  20.         or      ebx,ebx
  21.         jz      @f
  22.  
  23.         or      al,10000000b
  24.         stosb
  25.         inc     dword [esp+28]
  26.         jmp     @b
  27. @@:
  28.         stosb
  29.         inc     dword [esp+28]
  30.  
  31.         popa
  32.         ret
  33. endp
  1. ;-----------------------------------------------------------
  2. ; Преобразование vint в DWORD
  3. ;-----------------------------------------------------------
  4. ; На входе:
  5. ;    lpVint - указатель на последовательность vint
  6. ;    lpValue - указатель на DWORD с полученным значением
  7. ; На выходе:
  8. ;    EAX = количество обработанных байт
  9. ;-----------------------------------------------------------
  10. proc vint2int lpVint:DWORD, lpValue:DWORD
  11.         xor     eax,eax
  12.         pusha
  13.  
  14.         xor     ecx,ecx
  15.         xor     ebx,ebx
  16.         mov     esi,[lpVint]
  17. .loc_loop:
  18.         inc     dword [esp+28]
  19.         lodsb
  20.         movzx   eax,al
  21.         or      al,al
  22.         js      .loc_signed
  23. .loc_mormal:
  24.         xor     edi,edi
  25.         jmp     .loc_process
  26. .loc_signed:
  27.         and     al,01111111b
  28.         xor     edi,edi
  29.         inc     edi
  30. .loc_process:
  31.         shl     eax,cl
  32.  
  33.         or      ebx,eax
  34.         or      edi,edi
  35.         jz      .loc_done
  36.  
  37.         add     cl,7
  38.         jmp     .loc_loop
  39. .loc_done:
  40.         mov     eax,[lpValue]
  41.         mov     [eax],ebx
  42.  
  43.         popa
  44.         ret
  45. endp
Согласно официальной документации, архив RAR состоит из главного заголовка архива, за которым следуют блоки данных разных форматов, предваряемые соответствующими заголовками. Это могут быть упакованные данные, информация о восстановлении, информация о шифровании, комментарии к архиву и т.п. Поскольку наша задача дописать в архив файл, то интересовать нас будет именно файловый заголовок. Его структуру можно представить следующим образом:
  1. ;---------------------------------------------
  2. ; RAR Header
  3. ;---------------------------------------------
  4. rhcrc   dd      ?    ; Header CRC32
  5. rhsize  rb      (?)  ; Header size
  6. rhtype  db      ?    ; Header type (0x02)
  7. rflags  db      ?    ; Header flags (0x03)
  8. rextra  db      ?    ; Extra area size (0x0B)
  9. rpacked rb      (?)  ; Packed data size
  10. rfflag  db      ?    ; File flags (0x04)
  11. rfsize  rb      (?)  ; Unpacked size
  12. rattr   db      ?    ; Attributes (0x20)
  13. rdcrc   dd      ?    ; Data CRC32
  14. rinfo   dw      ?    ; Compression information (0x8000)
  15. rhost   db      ?    ; Host OS (0x00)
  16. rflen   db      ?    ; File name length
  17. rfname  rb      (?)  ; Name
  18. ;----------------------------------
  19. ; Дополнительные данные
  20. ;----------------------------------
  21. rxsize  db      ?    ; Size (0x0A)
  22. rxtype  db      ?    ; Type (0x03)
  23. rxflag  db      ?    ; Flags (0x02)
  24. rxtime  FILETIME     ; Modification time
Именно такой заголовок обычно создает WinRAR при упаковке архивов. Но если не требуется сохранять время создания или модификации файла, то можно значительно упростить структуру заголовка, отказавшись от дополнительных данных. При этом файловая запись в архиве останется корректной и внедренный файл будет абсолютно нормально извлекаться.
  1. ;---------------------------------------------
  2. ; RAR Header (упрощенный)
  3. ;---------------------------------------------
  4. rhcrc   dd      ?    ; Header CRC32
  5. rhsize  rb      (?)  ; Header size
  6. rhtype  db      ?    ; Header type (0x02)
  7. rflags  db      ?    ; Header flags (0x02)
  8. rpacked rb      (?)  ; Packed data size
  9. rfflag  db      ?    ; File flags (0x04)
  10. rfsize  rb      (?)  ; Unpacked size
  11. rattr   db      ?    ; Attributes (0x20)
  12. rdcrc   dd      ?    ; Data CRC32
  13. rinfo   dw      ?    ; Compression information (0x8000)
  14. rhost   db      ?    ; Host OS (0x00)
  15. rflen   db      ?    ; File name length
  16. rfname  rb      (?)  ; Name
В принципе, если уж совсем заморочиться на ручной оптимизации, то можно отказаться от хранения CRC32 исходных данных, тем самым сократив размер заголовка еще на несколько байт. Но это, по-моему, уже экономия на спичках и дополнительное палево в виде сигнатуры заголовка, совершенно не свойственного обычному архиватору.
  1. ;---------------------------------------------
  2. ; RAR Header (краткий)
  3. ;---------------------------------------------
  4. rhcrc   dd      ?    ; Header CRC32
  5. rhsize  rb      (?)  ; Header size
  6. rhtype  db      ?    ; Header type (0x02)
  7. rflags  db      ?    ; Header flags (0x02)
  8. rpacked rb      (?)  ; Packed data size
  9. rfflag  db      ?    ; File flags (0x00)
  10. rfsize  rb      (?)  ; Unpacked size
  11. rattr   db      ?    ; Attributes (0x20)
  12. rinfo   dw      ?    ; Compression information (0x8000)
  13. rhost   db      ?    ; Host OS (0x00)
  14. rflen   db      ?    ; File name length
  15. rfname  rb      (?)  ; Name
Вот так выглядят в интерфейсе WinRAR файлы, внедренные в архив с разными заголовками. Меньше всего подозрений вызывает файл с обычным заголовком. Незаполненные поля заголовков у двух других файлов сразу бросаются в глаза.

Внедренные в архив файлы
Внедренные в архив файлы

Напомню, что мнемокода "(?)" в FASM нет. Как и в предыдущих статьях на эту тему, таким образом я обозначил данные неопределенной длины. Это может быть, например, имя внедряемого файла, а также абсолютно все данные формата vint.

Каждый RAR-архив завершается особым блоком, после которого архиватор не будет обрабатывать больше никакие данные. Это так называемый "хвост" архива.
  1. ; "Хвост" архива - признак окончания данных
  2. tail    db      01Dh,077h,056h,051h,003h,005h,004h,000h
  3. tail_length     = $-tail
Переходим к внедрению. Предположим, что файл, который мы будем записывать в архив, уже загружен в память. Заполняем поля заголовка по порядку.
  • rhtype = 0x02, тип заголовка;
  • rflags = 0x03 для полного заголовка или 0x02 для упрощенного, флаги заголовка;
  • rextra = 0x0B, размер дополнительных данных, для упрощенного заголовка поле отсутствует;
  • rpacked = размер оригинального файла;
  • rfflag = 0x04 для полного и упрощенного заголовка или 0x00 для краткого заголовка, флаги файла;
  • rfsize = размер сжатого файла, равен оригинальному размеру;
  • rattr = 0x20, атрибуты файла;
  • rdcrc = CRC32 оригинального файла, для краткого заголовка поле отсутствует;
  • rinfo = 0x8000, информация о сжатии;
  • rhost = 0x00, целевая OS - Windows;
  • rflen = длина имени файла;
  • rfname = строка имени файла;
  • rxsize = 0x0A, размер дополнительных данных, для упрощенного или краткого заголовка поле отсутствует;
  • rxtype = 0x03, тип дополнительных данных, для упрощенного или краткого заголовка поле отсутствует;
  • rxflag = 0x02, флаги данных, для упрощенного или краткого заголовка поле отсутствует;
  • rxtime = структура FILETIME, время модификации файла, для упрощенного или краткого заголовка поле отсутствует;
После этого считаем размер заголовка от поля rhtype до поля rfname или rxtime включительно, в зависимости от используемого заголовка, и заполняем этим значением поле rhsize. Тут заголовок небольшой, для его хранения достаточно фиксированного поля размером в 1 байт. Заключительным шагом считаем CRC32 заголовка от поля rhsize до поля rfname или rxtime включительно и записываем его в поле rhcrc. Все, данные готовы к внедрению.

Открываем целевой архив для записи, и устанавливаем указатель на позицию 8 байт от конца файла, чтобы удалить существующий "хвост" архива. Записываем сформированный заголовок, следом записываем внедряемый файл. После этого записываем 8-байтовый "хвост" архива. Кстати, аналогичным способом можно прицепляться к RAR-SFX архивам.

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

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

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

Store.to.RAR5.without.Archiver.Demo.zip (18,853 bytes)


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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (27.05.2024 в 17:08):
Ага, если посмотреть в example, там размер файла записан как 0x93, 0x00, причем дважды. Это действительно декодируется из vint в 0x13, но как минимум два байта можно смело записывать в излишек. Офигенная экономия :) Файл сделан штатными средствами архиватора.
Petya (27.05.2024 в 17:03):
ЦитатаЕсли взять всякую мелочевку типа флагов, идентификатора заголовка, атрибутов файла и подобного, почему нельзя было оставить фиксированные значения?

Тут, я полагаю, как раз не задел на будущее, а экономия на спичках - типа, зачем тратить 8 байт там, где хватает одного? Помнится, при разборе одного формата я именно с этого бесился - ради одного бита записывался десяток байт - идентификатор блока, длина блока, затем собственно бит, но в виде uint32...

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

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

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