Как взломать программу в формате MSI

Как взломать программу MSI

Что делать, если инсталлятор не желает запускаться или, хуже того, не хочет устанавливать приложение? Любой уверенно скажет: ломать! И будет прав. Сегодня мы поговорим о том, как взломать программу в формате MSI, быстро и без лишних усилий.

Еще по теме: Отладка программ с помощью WinDbg

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

Поскольку за давностью лет связаться с разработчиком не представляется возможным, я решил поковырять установщик, чтобы разобраться, какого рожна ему надо для нормальной инсталляции.

Как взломать программу в формате MSI

Раз уж мы уже собрались идти по пути наименьшего сопротивления, то перво-наперво я открыл установочный пакет (самодостаточный модуль .exe) даже не в дизассемблере, а просто в Hex-редакторе, чисто поинтересоваться наличием текста сообщения об ошибке. При этом сильно не расстроился, не найдя этой строки (и вообще никаких строк сопутствующих сообщений) ни в юникодном, ни в обычном виде.

Ну что ж, не очень-то и хотелось, зато попутно краем глаза я заприметил ресурс [0] с характерной сигнатурой InstallShield. В принципе, разбор пакетов InstallShield — распространенная задача, и решение ее хоть по понятным причинам и не документировано, но при желании находится поиском по специальным форумам. Я, возможно, запилю об этом другую статью, но в данном случае мы условились, что курение мануалов не наш метод.

Вместо погружения в них я для очистки совести попытался, пока висит окно с предупреждением о неверном серийнике, подключиться к запущенному процессу инсталляции через имеющийся под рукой ОllyDbg. Тоже без особого энтузиазма и больших надежд, ибо напрочь забыл, как отлаживать интерпретатор InstallShield (однако прекрасно помня, что это весьма муторная и неинтересная задача). По сути, я даже и присоединяться не стал, увидев в списке годных для присоединения процессоров под заголовком окна с предупреждением MSIEXEC -Embedded труднопроизносимую мешанину шестнадцатеричных букв и цифр.

На самом деле, конечно, расшифровав эту мешанину, я бы мог определить название подкаталога папки Temp, из которой сейчас выполняется интерпретация инсталляционного скрипта, но зачем все усложнять? Я просто открыл папку Temp и обнаружил в ней две вещи.

  1. Пакет MSI, который инсталлятор сразу родил при инсталляции.
  2. Временную папку с рядом DLL, характерных InstallShield и (что самое главное!) сам инсталляционный скрипт setup.inx.

Теперь небольшое лирическое отступление. В данном случае отладчик — совершенно лишнее звено в технологической цепочке. Повторяю: я запустил его просто на всякий случай и (как будет сказано ниже) позже не использовал совсем. Гораздо более быстрым, а главное, точным путем стало бы отслеживание файлового ввода-вывода процесса инсталляции. Для этого существует простая и распространенная утилита ProcessMonitor (для старых версий винды FileMonitor). Проанализировав ее весьма увесистый лог, можно было бы определить и папку, и скрипт, и момент рождения пакета MSI.

Как показал более детальный анализ, процесс зачем-то был сделан трехступенчатым: EXE родил MSI, который родил другой MSI, который, собственно, и произвел на свет содержимое искомого подкаталога с инсталляционным скриптом. Но на этом лирику я заканчиваю, подводим итог: у нас появился чистый пакет MSI, равнозначный инсталляционному EXE, и собственно инсталляционный скрипт setup.inx, в котором зашита вся логика инсталляции, как она есть.

С этого момента задача приобрела определенность. Убедившись в том, что MSI действительно полностью равнозначен исходному инсталлятору, который, по сути, был оберткой над ним, я принялся его потрошить. Формат MSI хоть и документирован (причем по его спецификации даже имеются мануалы от самой Microsoft), но наш девиз, напоминаю, лень и еще раз лень. Нам некогда ковырять мудреные и косноязычные английские технические описания, полные неточностей и непонятных терминов, нам надо получить результат сразу и с минимальными трудозатратами.

Поэтому берем обычный архиватор 7z и, не мудрствуя лукаво, открываем им пакет MSI. Наблюдаем много интересного: в частности, внутри лежит файл .cab, содержащий в себе все-все-все файлы и библиотеки, которые распаковываются, если серийные номера и лицензионные коды внезапно подошли. Если бы перед нами стояла задача не корректно инсталлировать пакет, а просто найти недостающий для работы файл, то можно было бы с чистым сердцем бросать дальнейшие раскопки и идти пить пиво.

Однако, несмотря на лень, мы все-таки ответственно подходим к задаче и идем до конца: добиваемся честной инсталляции пакета с прописыванием всех путей, ключей реестра, регистрации контролов и прочих неочевидных действий, совершаемых инсталлятором. А поэтому потерпим еще немного: нам предстоит преодолеть cамую неприятную стадию процесса. Неприятность ее заключается в том, что мы внезапно не обнаруживаем в списке распакованных файлов инсталляционного скрипта setup.inx, а значит, если нам придет в голову править его в инсталляционном пакете, это будет непросто. Завязывая этот узелок себе на память, с тяжелым чувством начинаем разбирать сам инсталляционный скрипт.

После беглого взгляда на этот самый скрипт мое уныние усилилось — вместо читаемого кода он целиком состоит из мешанины псевдослучайных символов. Ни установочных сообщений, ни текстовых строк, вообще ни единого читаемого слова. Скрипт явно закодирован. Делать нечего, придется все-таки поднапрячься и поискать утилиты для его раскодировки-декомпиляции.

Слава всемогущему Гуглю: оказывается, добрые люди уже позаботились об этом и запилили чудесную утилиту IsDcc, причем ее исходники даже выложены на гитхабе! Мало того что она умеет расшифровывать закодированные скрипты (это называется scramble/unscramble), но еще может декомпилировать расшифрованный скрипт в исходный читаемый код! Компилировать обратно, правда, не умеет, но не все ж коту масленица, будем преодолевать трудности по мере их поступления. В общем, последовательно раскодировав и декомпилировав инсталляционный скрипт, я получил чуть больше мегабайта бейсикоподобного кода, в котором нашел искомое место проверки серийника:

00E8EE:000D:         lNumber18 = lString9 == lString17;
00E8FB:000D:         lNumber18 = lNumber18 == 0;
00E90A:0004:         if lNumber18 == false then goto label198 ;
00E916:0021:         call function651("Sorry, but the license key that you entered is invalid.");

Как видишь, скрипт вычисляет валидный ключ и сравнивает с введенным юзером. Первая мысль — разобраться в алгоритме его генерации и написать свой кейген. Однако, мельком взглянув на алгоритм (несколько страниц индийского неструктурированного кода, местами бессмысленного и беспощадного), с негодованием отметаем эту идею, как не соответствующую основной концепции ленивого взлома. Мы пойдем другим путем: просто поменяем одну из трех проверок на противоположную.

Однако как это сделать с минимальными трудозатратами? Систему команд интерпретатора мы не знаем (можно, конечно, покопать IsDcc, благо есть хорошо комментированные исходники), но и этот путь чересчур уныл для меня. Обращаю внимание на особенность декомпилированного кода: слева стоит пара шестнадцатеричных цифр.

Декомпилированный код скрипта установки
Декомпилированный код скрипта установки

Это вовсе не сегментированный адрес, как могло бы показаться, а пара смещение:опкод. Мельком просмотрев несколько соседних команд, делаем предположение, что D — это опкод сравнения на эквивалентность, в то время как его антагонист (сравнение на неэквивалентность) имеет опкод E. Подумаешь, бином Ньютона, для подобного вывода вовсе не обязательно кропотливо изучать систему команд. Нам остается только залезть Hex-редактором в скомпилированный скрипт и по нужному смещению поправить D на E: теперь любой введенный неправильный код будет правильным — то, что нам надо! Анскрембим полученный setup.inx и приступаем к решению следующей проблемы.

Напомню ее суть: в распакованных из MSI файлах вовсе нет ничего похожего на файл setup.inx. Более того, даже поиск в нем любого фрагмента скремблeрного кода дает отрицательный результат. Но сам-то скрипт в MSI-файле определенно присутствует, надо же ему откуда-то браться? Тем более поиск внутри инсталляционного пакета MSI находит даже ссылку на его название — setup.inx. А значит, он там явно присутствует — возможно, в зашифрованном или запакованном виде.

Так как курить спецификацию MSI у нас по-прежнему нет ни времени, ни желания, пробуем снова выкрутиться «ленивым» методом. На помощь опять приходит замечательная утилита FileMon. Берем лог обращений к файловой системе инсталляционного пакета и находим в нем место рождения файла setup.inx — создание его в подкаталоге папки Temp и запись в него первого блока данных. Нам повезло: этой записи предшествует чтение такого же блока из родительского файла — пакета MSI и даже смещение этого блока имеется.

Смотрим данные по этому смещению в родительском MSI: ничего общего с содержимым setup.inx. Однако само название setup.inx находится прямо перед этим блоком данных, и тут же мы видим четыре байта размера файла setup.inx — явно заголовок зашифрованного блока. А значит, в нашем нелегком пути идейного лодыря пройден еще один этап — мы нашли источник данных скрипта.

Мы нашли источник данных скрипта
Мы нашли источник данных скрипта

Казалось бы, мы зашли в тупик: данные все равно зашифрованы и алгоритм нам неизвестен, а значит, поправить их мы не можем. То есть самое время бросать заниматься ерундой и начинать писать кейген или хотя бы искать спецификацию MSI. Собственно, в случае шифрования нормальными «взрослыми» криптоалгоритмами так оно и было бы — методом тыка невозможно определить алгоритм и ключ шифрования. Но мы не сдаемся: особо ни на что не надеясь, я предположил, что авторы были столь же ленивы, как и я сам, поэтому закодировали данные банальным XOR.

Это предположение, кстати, косвенно подтверждает беглый просмотр соседних зашифрованных данных других подобных файлов — они содержат блоки, заполненные одинаковыми повторяющимися паттернами, местами ломающимися на несколько байт, местами прерывающимися, как будто файл просто поксорен короткими блоками байт 8–12 (особо наблюдательный исследователь может даже заметить, что длина такого паттерна равна длине названия зашифрованного файла, но нам это не особо интересно, чтобы отвлекаться).

Если это предположение верно, то из-за коммутативности и ассоциативности операции XOR для патча одного байта в зашифрованном коде достаточно отксорить его с исключающей разницей исходных байтов. Сравниваем два скремблeнных файла setup.inx — исходный и патченный. Исключающая разница, равная четырем, обнаруживается в правленом байте по смещению E8FB.

Ищем исключающую разницу

Отсчитываем смещение E8FB от предполагаемого начала данных файла setup.inx внутри MSI и ксорим байт по этому смещению с четверкой. С замиранием сердца запускаем MSI на инсталляцию… Упс, фокус не удался, инсталляция виснет. Похоже, мы облажались со своей ленивой гипотезой, файл закодирован сложнее, чем банальным XOR.

Облом — инсталляция не запускается
Облом — инсталляция не запускается

Были бы мы чуть менее ленивы, то в этом месте бросили бы все и сели бы за кейген. Но нет, мы идем до конца: надо ж выяснить, где именно мы облажались. Снова выуживаем новый свежерожденный setup.inx и сравниваем его с исходным — что в нем изменилось после нашей правки? Оказывается, не все потеряно: разница все-таки в одном-единственном байте. Правда, смещение не E8FB и поксорился с четверкой почему-то противоположный полубайт. Ну раз так, делаем поправку на нужное смещение и переставляем полубайты местами: вместо 04 делаем XOR с 0x40. И вот тут наши усилия наконец увенчиваются успехом — исправленный инсталляционный пакет радостно принимает случайный введенный код и благополучно устанавливает библиотеки на компьютер. Конечно, будь я большим перфекционистом, можно было бы пойти еще дальше, найти порожденный пакет MSI в исходном инсталляторе .exe, но мне лень. Мы ведь решили задачу, избегая лишних телодвижений, а значит, эксперимент завершился полной и безоговорочной победой.

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

Еще по теме: Что такое Фаззинг и как искать уязвимости в программах

ВКонтакте
OK
Telegram
WhatsApp
Viber

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *