Подбираем ключ к архивам Visionaire Studio
Суббота, 17. Ноябрь 2012
Раздел: Perl, автор: Kaimi
В этой статье речь пойдет о подборе ключа к архивам, созданным с помощью Visionaire Studio, которые хранят в себе игровые ресурсы.
Visionaire Studio - это среда для упрощенного создания игр в жанре adventure, с её помощью были созданы такие игры, как, например: The Dark Eye: Chains of Satinav, A New Beginning, Deponia. В общем, практически весь репертуар продуктов студии Daedalic Entertainment. Самого процесса распаковки я касаться не буду, так как для этого уже существуют несколько утилит: (скрипт для QuickBMS, VISExt, Unpakke) и более-менее полное описание формата содержимого файла (краткое описание формата VIS*). Минусом всех этих утилит является необходимость собственноручно вбивать ключ, которым зашифрован VIS-файл (ключ шифрования для одной и той же игры может быть различным в зависимости от, например, локализации, а также ключи не сразу добавляются в утилиты при выходе новой игры), попробуем разобраться, как вычислить этот ключ программно.
Как видно из описания формата, в качестве шифрования применяется XOR, где ключом служит половина MD5-хэша, полученного от имени файла (не всегда), то есть ключ шифрования имеет формат [0-9a-f]{16}.
Теперь, для упрощения исследования, нам понадобятся несколько VIS-файлов с известными ключами. Ключи можно получить из вышеперечисленных утилит для распаковки, а файлы, например, из вышеуказанных игр (обычно искомый файл называется data.vis). Если качать игры целиком нет желания, то в качестве примера можно взять этот небольшой файл (выкладывался в качестве примера на XeNTaX). На его примере я объясню алгоритм восстановления ключа.
Скачаем QuickBMS и запустим его, указав путь к скрипту распаковки (visionaire.bms), путь к VIS-файлу (d9ef88157bcbcae0.vis) и добавив -a DUMP, чтобы сдампить расшифрованное содержимое первого "сегмента", который содержит в себе информацию о смещении, размере и типе каждого файла, который хранится внутри.
По сути, нас интересуют первые 48 байт созданного в результате вышеописанной операции файла visionaire_dump.dat. Почему? Потому что, если посмотреть скрипт visionaire.bms, то мы увидим, что в оригинальном VIS-файле (до расшифровки) первые 4 байта отведены под сигнатуру (VIS3), следующие 4 байта под размер "файла-индекса" (который мы будем рассматривать чуть ниже) и далее начинается содержимое, которое циклически ксорится на ключ.
Рассмотрим первые 48 байт из файла, полученного после расшифровки скриптом для QuickBMS:
48 44 52 00 00 00 00 00 00 2B 5B 00 00 2B 5B 00 00 00 08 00 00 2B 5B 00 00 6C A8 00 00 6C A8 00 00 00 00 00 00 98 03 00 00 4D 95 00 00 4D 95 00 |
48 44 52 00 - это сигнатура (HDR), которую можно пропустить, далее данные хранятся фрагментами по 16 байт в формате offset - zsize - size - type.
То есть 00 00 00 00 - offset, 00 2B 5B 00 - zsize, 00 2B 5B 00 - size, 00 00 08 00 - type.
Если взять какие-либо другие VIS-файлы и посмотреть результат расшифровки, то мы увидим, что первые 8 байт всегда выглядят одинаково, то есть получение первой половины ключа не составляет абсолютно никакой проблемы. Очевидно, что вторая половина ключа тоже легко находится, так как поле offset второго фрагмента эквивалентно полям zsize и size (они обычно равны) предыдущего, что логично. Соответственно вторая половина ключа также легко восстанавливается, так как у нас есть исходные данные и ожидаемый результат.
Теперь напишем простой скрипт, который автоматизирует восстановление ключа.
use strict;use warnings;use File::Basename; # Сигнатура верного заголовка, которая используется для получения первого фрагмента ключаmy@want=qw/48 44 52 00 00 00 00 00/;# Хэш для валидации символов ключаmy%xor_val=map{$_=>1}(0..9,'a'..'f');my@key=(); # Выводим FAQ, если скрипту не передан аргументif(scalar@ARGV<1){print"Usage: ".basename($0)." file.vis";exit;} # Читаем 40 байт из VIS-файлаopen A,'<',$ARGV[0]ordie$!;read A,my$data,8+16+16;close A; # Проверяем начальную сигнатуруif(substr$data,0,4eq'VIS3'){print"Invalid signature (expected VIS3)\n";exit;} # Получаем первые 8 байт ключа и проверяем валидность символовmy@data=split//,substr$data,8,8;for(my$i=0;$i<scalar@data;$i++){my$byte=hex$want[$i]^ord$data[$i];push@key,$xor_val{chr$byte}?chr$byte:'?';} # Расшифровываем часть данных, используя полученный фрагмент ключаmy@data_xor=();@data=split//,substr$data,8+16,8;for(my$i=4;$i<scalar@data;$i++){push@data_xor,chr(ord$data[$i]^ord$key[$i%scalar@key]);} # Расшифрованное смещениеmy$offset=hexunpack'H8',join'',@data_xor;# 8 байт оригинальных данных, которые будут использованы для получения ключаmy$i_value1=hexunpack'H8',join'',substr$data,8+8,4;my$i_value2=hexunpack'H8',join'',substr$data,8+8+4,4;# Получаем оставшийся фрагмент ключа, поксорив ожидаемое содержимое на исходные данныеmy$k_value1=$offset^$i_value1;my$k_value2=$offset^$i_value2;# Преобразуем фрагменты в необходимый видpush@key,pack'N',$k_value1;push@key,pack'N',$k_value2;# Выводим найденный ключprint@key; |
Скачать скрипт одним файлом: vis_keytool.pl
Вот и всё, в качестве проверки я попробовал получить ключ для нескольких файлов - всё отлично сработало, что подтвердила дальнейшая удачная распаковка ресурсов с помощью VISExt, например, этой картинки: