Упаковка бинарных ресурсов в батник ("CBI method" by Dragokas)

Dragokas

Angry & Scary Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
8,064
Решения
15
Реакции
6,838
Cabinet's Batch inline. "CBI method" by Dragokas

Метод предоставляет возможность встраивать бинарную информацию (любые файлы)
в качестве ресурсов BAT-файла, получая на выходе 1 комбинированный файл
с расширением BAT или CMD.
Батник затем сможет распаковать свой ресурс при необходимости его использования.

В статье представлены описание метода и программная реализация.

Описание метода

Оригинальная идея принадлежит человеку с ником: Somebody.

Она звучала так:
Можно сделать cab архив с расширением bat, а в конце - пустую строку и extrac32 /e %0.
extrac32 в винде вроде всегда есть. Побочный эффект - попытка запуска "MSCF"
(заголовок .cab).

Данный способ не подходит для случаев упаковки бинарных ресурсов различного содержания,
т.к. некоторые комбинации символов для формата CMD являются недоспустимым и
приводят к его "падению" прежде, чем BAT-файл начнет исполнение своей части кода
из конца Cabinet архива.

К счастью структура формат CAB предоставляет возможность резервирования произвольного
количества байт (начиная со смещения 0x28) для хранения цифровой подписи.

Эту область мы задействуем под свой нужны, расположив в ней основной код
и команду распаковки.

Техническая часть

Код батника будет содержать команду для распаковки себя же:
Код:
@extrac32.exe /E /Y /L "папка, куда распаковуем" "%~f0"
/e - распаковать все
/y - без подтверждения при замене

Структура формата Cabinet архива описана в статье базы знаний: MSKB417343

Она разделена на 4 блока:

  • CFHEADER
  • CFFOLDER
  • CFFILE
  • CFDATA

Описание первого блока:

C++:
// Размер | имя параметра | смещене | описание параметра

struct CFHEADER
{
  u1  signature[4]  // 0x00 - сигнатура архива "MSCF"
  u4  reserved1     // 0x04 - зарезервировано
  u4  cbCabinet     // 0x08 - размер архива в байтах
  u4  reserved2     // 0x0С - зарезервировано
  u4  coffFiles     // 0x10 - смещение первого вхождения блока CFFILE
  u4  reserved3     // 0x14 - зарезервировано

  u1  versionMinor  // 0x18 - версия формата архива (minor)
  u1  versionMajor  // 0x19 - версия формата архива (major)

  u2  cFolders      // 0x1A - количество блоков CFFOLDER
  u2  cFiles        // 0x1C - количество блоков CFFILE
  u2  flags         /* 0x1E - флаги наличия или отсутствия зарезервированной областей
                              abReserve[], szCabinetPrev[], szDiskPrev[], szCabinetNext[], szDiskNext[]   */
  u2  setID         // 0x20 - должен быть одинаковый для всех частей составного архива
  u2  iCabinet;     // 0x22 - порядковий номер этого файла в составном архиве
  u2  cbCFHeader;   // 0x24 - (опционально) размер зарезервированной под ЭЦП области архива

  u1  cbCFFolder;       // 0x26 - (опционально) размер зарезервированной области каждой папки
  u1  cbCFData;         // 0x27 - (опционально) размер зарезервированной области каждого блока данных
  u1  abReserve[];      // 0x28 - (опционально) область, зарезервированная под ЭЦП (сюда будем записывать батник)
  u1  szCabinetPrev[];  // (опционально) имя предыдущего файла CAB
  u1  szDiskPrev[];     // (опционально) имя предыдущего диска
  u1  szCabinetNext[];  // (опционально) имя следующего файла CAB
  u1  szDiskNext[];     // (опционально) имя следующего диска
};

Чтобы создать скомбинированный файл, сперва нужен сам архив.
Наиболее родной способ - воспользоваться встроенной в систему утилитой makecab.
Синтаксис у нее непростой, поэтому лучше воспользоваться готовой реализацией:

CMD/BATCH:
@echo off
SetLocal
:: Имя для CAB-архива
set "CabinetName=test.cab"
:: Путь к папке, которую упаковуем
set src=C:\Users\Alex\Desktop\gardening\gardening

chcp 1251 >NUL
For /F "delims=" %%a in ('dir /B /a-d "%src%\*.*"') do echo "%src%\%%a">>"%Temp%\list.txt"
chcp 866 >NUL

set .=
set .=%.% /D FolderSizeThreshold=50000000
set .=%.% /D DestinationDir=.
set .=%.% /D MaxDiskSize=0
set .=%.% /D cabinet=on
set .=%.% /D compression=on
set .=%.% /D CabinetDir=\.
set .=%.% /D CabinetNameTemplate="%CabinetName%"
set .=%.% /D CabinetName="%CabinetName%"
set .=%.% /D InfFileName=NUL
set .=%.% /D RptFileName=NUL
set .=%.% /D CompressionMemory=21
set .=%.% /D DiskDirectoryTemplate=.
set .=%.% /D UniqueFiles=Off
set .=%.% /D CompressionType=LZX

makecab %.% /V1 /F "%Temp%\list.txt"

del /F "%Temp%\list.txt"
pause

Тем не менее, у Microsoft есть и другая утилита из состава Resource Kit
под названием CabArc.

Ввел в YANDEX слово Cabarc и получил "Сфинкс". Круто! M$ отдыхает :)

Создать архив CAB можно такой простой командой:
Код:
cabarc N test.cab *.*

Упакует все файлы в текущей папке в архив test.cab

Чтобы вставить код Batch в такой архив, нам нужно зарезервировать область
соответствующего размера, и здесь Cabarc будет как нельзя кстати.

Синтаксис:
Код:
CABARC [опции] команда cabfile [@список] [файлы] [папка назначения\]

Основных каманды всего три:

L - просмотр
X - разархивировать
N - архивировать

Код:
Опции:
  -c   подтверждать операции с файлами
  -o   во время распаковки не запрашивать подтверждения при замене
  -m   алгоритм сжатия [LZX:<15..21>|MSZIP|NONE], (по-умолчанию - MSZIP)
  -p   сохранять пути файлов (абсолютные пути не поддерживаются)
  -P   урезать указанный префикс из файлов при добавлении в архив
  -r   рекурсивно с подкаталогами при добавлени в архив (см. также -p)
  -s   зарезервировать место под цифровую подпись (например, -s 6144 резервирует 6 КБ.)
  -i   Устанавливает ID архива при создании (по-умолчанию - 0)
  -d   установить размер частей (по-умолчанию, размер не определен, т.е. создается единый архив)

Ключ -s позволяет установить произвольный размер зарезервированного под ЭЦП блока
(от 1 до 65535 байт (0xFFFF)).

Теперь смотрим, какой размер у батника, к которому мы хотим добавить ресурс.
Пусть будет 250 байт (0xFA).

Прибавляем 6 байт (ниже узнаете зачем).
250 + 6 = 256 (0x100).

Создаем архив с резервной областью в 256 байт:

Код:
cabarc -s 256 N test.cab *.*

Принцип слияния кода батника с архивом такой:

Берем смещение архива 0x28 (блок abReserve[]).
Добавляем туда 2 переноса строки (0x0D, 0x0A, 0x0D, 0x0A) - 4 байта.

Далее вставляем сам код и еще + перенос строки (0x0D, 0x0A) - 2 байта.

4 + 2 = 6 байт, о которых мы говорили выше.

В коде батника должна быть предусмотрена команда распаковки архива из себя
и очистка сообщения о неверной команде (когда батник попытается исполнить хидер CAB-архива):

Код:
@cls & @extrac32.exe /E /Y /L .\ "%~f0"

Комбинированный CBI-файл готов.
Нужно переименовать его расширение в CMD.

Правка упакованного батника напрямую

Также можно выделить резервную область путем правки определенных байт CAB-архива:

1. В структуре CFHEADER:

C++:
  u4  cbCabinet     // 0x08 - размер архива в байтах
  u4  coffFiles     // 0x10 - смещение первого вхождения блока CFFILE
  u2  flags         // 0x1E - флаги наличия или отсутствия зарезервированной областей
  u2  cbCFHeader;   // 0x24 - (опционально) размер зарезервированной под ЭЦП области архива

flags должен содержать константу:
C++:
#define cfhdrRESERVE_PRESENT    0x0004

cbCFHeader - для архива с флагом = 0 (нет резервных областей) имеет смещение 0x2C.

2. В каждой из структур CFFOLDER:

C++:
u4  coffCabStart;   // смещение первого вхождения блока CFDATA для этой папки

Программная реализация

В дополнение к статье готовая программа для прикрепления к батнику ресурса из указанных файлов и/или каталогов.

cabpacker.webp
 

Вложения

Назад
Сверху Снизу