Запись в архивы RAR5 без помощи архиватора
Сегодня я расскажу, как можно добавить какой-нибудь файл в архив формата 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 байт также не производится.
Code (Assembler) : Убрать нумерацию
- ;-----------------------------------------------------------
- ; Преобразование DWORD в vint
- ;-----------------------------------------------------------
- ; На входе:
- ; dValue - преобразуемое значение
- ; lpVint - указатель на память для записи vint
- ; На выходе:
- ; EAX = количество записанных байт
- ;-----------------------------------------------------------
- proc int2vint dValue:DWORD, lpVint:DWORD
- xor eax,eax
- pusha
- mov ebx,[dValue]
- mov edi,[lpVint]
- @@:
- mov al,bl
- shr ebx,7
- or ebx,ebx
- jz @f
- or al,10000000b
- stosb
- inc dword [esp+28]
- jmp @b
- @@:
- stosb
- inc dword [esp+28]
- popa
- ret
- endp
Code (Assembler) : Убрать нумерацию
- ;-----------------------------------------------------------
- ; Преобразование vint в DWORD
- ;-----------------------------------------------------------
- ; На входе:
- ; lpVint - указатель на последовательность vint
- ; lpValue - указатель на DWORD с полученным значением
- ; На выходе:
- ; EAX = количество обработанных байт
- ;-----------------------------------------------------------
- proc vint2int lpVint:DWORD, lpValue:DWORD
- xor eax,eax
- pusha
- xor ecx,ecx
- xor ebx,ebx
- mov esi,[lpVint]
- .loc_loop:
- inc dword [esp+28]
- lodsb
- movzx eax,al
- or al,al
- js .loc_signed
- .loc_mormal:
- xor edi,edi
- jmp .loc_process
- .loc_signed:
- and al,01111111b
- xor edi,edi
- inc edi
- .loc_process:
- shl eax,cl
- or ebx,eax
- or edi,edi
- jz .loc_done
- add cl,7
- jmp .loc_loop
- .loc_done:
- mov eax,[lpValue]
- mov [eax],ebx
- popa
- ret
- endp
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------
- ; RAR Header
- ;---------------------------------------------
- rhcrc dd ? ; Header CRC32
- rhsize rb (?) ; Header size
- rhtype db ? ; Header type (0x02)
- rflags db ? ; Header flags (0x03)
- rextra db ? ; Extra area size (0x0B)
- rpacked rb (?) ; Packed data size
- rfflag db ? ; File flags (0x04)
- rfsize rb (?) ; Unpacked size
- rattr db ? ; Attributes (0x20)
- rdcrc dd ? ; Data CRC32
- rinfo dw ? ; Compression information (0x8000)
- rhost db ? ; Host OS (0x00)
- rflen db ? ; File name length
- rfname rb (?) ; Name
- ;----------------------------------
- ; Дополнительные данные
- ;----------------------------------
- rxsize db ? ; Size (0x0A)
- rxtype db ? ; Type (0x03)
- rxflag db ? ; Flags (0x02)
- rxtime FILETIME ; Modification time
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------
- ; RAR Header (упрощенный)
- ;---------------------------------------------
- rhcrc dd ? ; Header CRC32
- rhsize rb (?) ; Header size
- rhtype db ? ; Header type (0x02)
- rflags db ? ; Header flags (0x02)
- rpacked rb (?) ; Packed data size
- rfflag db ? ; File flags (0x04)
- rfsize rb (?) ; Unpacked size
- rattr db ? ; Attributes (0x20)
- rdcrc dd ? ; Data CRC32
- rinfo dw ? ; Compression information (0x8000)
- rhost db ? ; Host OS (0x00)
- rflen db ? ; File name length
- rfname rb (?) ; Name
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------
- ; RAR Header (краткий)
- ;---------------------------------------------
- rhcrc dd ? ; Header CRC32
- rhsize rb (?) ; Header size
- rhtype db ? ; Header type (0x02)
- rflags db ? ; Header flags (0x02)
- rpacked rb (?) ; Packed data size
- rfflag db ? ; File flags (0x00)
- rfsize rb (?) ; Unpacked size
- rattr db ? ; Attributes (0x20)
- rinfo dw ? ; Compression information (0x8000)
- rhost db ? ; Host OS (0x00)
- rflen db ? ; File name length
- rfname rb (?) ; Name
Внедренные в архив файлы
Напомню, что мнемокода "(?)" в FASM нет. Как и в предыдущих статьях на эту тему, таким образом я обозначил данные неопределенной длины. Это может быть, например, имя внедряемого файла, а также абсолютно все данные формата vint.
Каждый RAR-архив завершается особым блоком, после которого архиватор не будет обрабатывать больше никакие данные. Это так называемый "хвост" архива.
Code (Assembler) : Убрать нумерацию
- ; "Хвост" архива - признак окончания данных
- tail db 01Dh,077h,056h,051h,003h,005h,004h,000h
- 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, время модификации файла, для упрощенного или краткого заголовка поле отсутствует;
Открываем целевой архив для записи, и устанавливаем указатель на позицию 8 байт от конца файла, чтобы удалить существующий "хвост" архива. Записываем сформированный заголовок, следом записываем внедряемый файл. После этого записываем 8-байтовый "хвост" архива. Кстати, аналогичным способом можно прицепляться к RAR-SFX архивам.
Однако надо знать, что не во все RAR-архивы можно безопасно внедряться. Обязательно надо проверять флаговые биты в основном заголовке архива и пропускать заблокированные, многотомные, непрерывные архивы и архивы с цифровой подписью. Все эти проверки вы сможете сделать самостоятельно, здесь я их описывать не буду.
В приложении примеры программ с исходными текстами, демонстрирующие запись в архивы RAR5 со всеми тремя описанными заголовками. При каждом запуске программа дописывает себя к архиву "example.rar5", выбирая случайное имя исполняемого файла.
Просмотров: 631 | Комментариев: 2
Метки: Assembler, архиваторы
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(27.05.2024 в 17:08):
Ага, если посмотреть в example, там размер файла записан как 0x93, 0x00, причем дважды. Это действительно декодируется из vint в 0x13, но как минимум два байта можно смело записывать в излишек. Офигенная экономия :) Файл сделан штатными средствами архиватора.
Petya
(27.05.2024 в 17:03):
Тут, я полагаю, как раз не задел на будущее, а экономия на спичках - типа, зачем тратить 8 байт там, где хватает одного? Помнится, при разборе одного формата я именно с этого бесился - ради одного бита записывался десяток байт - идентификатор блока, длина блока, затем собственно бит, но в виде uint32...
Добавить комментарий
Заполните форму для добавления комментария