warning
Статья написана в исследовательских целях, имеет ознакомительный характер и предназначена для специалистов по безопасности. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Использование или распространение ПО без лицензии производителя может преследоваться по закону.
В статье «Ломаем инсталлятор. Как обмануть инсталлятор MSI методом для ленивых» я писал, что частенько приходится допиливать не только саму программу, но и ее инсталлятор. В той заметке мы рассматривали принципы реверс‑инжиниринга и патчинга инсталляционных пакетов, созданных при помощи инсталлятора InstallShield (MSI). Сегодня нашей целью будет другой популярный пакет — InnoSetup. Думаю, этот тип инсталляторов настолько широко распространен, что не нуждается в подробном описании, поэтому сразу перейдем к конкретной задаче.
Итак, у нас имеется установщик для некоего графического плагина, оформленный в виде самодостаточного исполняемого EXE-модуля. При запуске он просит ввести серийный номер, судя по всему, проверяет его на удаленном сервере и при неправильном вводе выдает сообщение об ошибке.
Открыв наш инсталлятор в Detect It Easy, выясняем сразу два факта: во‑первых, это наш пациент, а во‑вторых, InnoSetup насквозь писан на Delphi.
Как подсказывает опыт, открывать инсталлятор в IDA нет ни малейшего смысла. Прежде всего, это дельфи, тут скорее помог бы IDR. С другой стороны, файл чуть менее чем целиком состоит из упакованного или зашифрованного оверлея (строка Serial Number is invalid предсказуемо не находится в нем в открытом виде), то есть его загрузчик не несет ничего полезного для решения нашей проблемы.
Поэтому сразу попробуем пощупать процедуру проверки серийного номера «изнутри», в процессе работы программы. Загрузив инсталлятор в отладчик x64dbg, мы обнаруживаем, что наши предположения верны. Загрузчик порождает несколько процессов, которые далее живут собственной жизнью независимо от него. В частности, окно сообщения и все остальные диалоговые окна вызываются из процесса, порождаемого модулем, который находится во вложенной папке \
каталога временных файлов системы.
При загрузке инсталлятор перво‑наперво создает этот каталог, распаковывает в него данный модуль, который потом запускает, а в конце инсталляции убирает за собой, удаляя и файл, и каталог. Рассмотрим этот модуль более детально.
Строка Serial
отсутствует в открытом виде и здесь тоже. Detect It Easy не говорит про модуль ничего внятного, кроме того, что он тоже написан на Delphi и содержит в ресурсе еще один модуль, написанный на Microsoft Visual C.
Попробуем копнуть чуть глубже: аттачимся с помощью x64dbg к процессу в момент появления диалогового окна "Serial
. Код вызова MessageBox выглядит примерно так:
...
005B8CBB | mov dword ptr fs:[ecx],esp
005B8CBE | push esi
; [ebp-8]:L"Setup"005B8CBF | mov eax,dword ptr ss:[ebp-8]
005B8CC2 | push eax
; edi:L"Serial Number is invalid. Please enter valid license you received or contact support"005B8CC3 | push edi
005B8CC4 | push ebx
005B8CC5 | call <JMP.&MessageBoxW>
005B8CCA | mov dword ptr ss:[ebp-C],eax
005B8CCD | xor eax,eax
005B8CCF | pop edx
005B8CD0 | pop ecx
...
Открыв модуль в IDR и найдя этот фрагмент кода, мы видим, что он является частью метода _Unit72.
, — что ж, вполне логично. Попробуем теперь отследить, откуда было вызвано это сообщение об ошибке.
Открываем вкладку «Стек вызовов» и буквально семью вложениями выше (или ниже, кому как больше нравится) обнаруживаем интересный метод _Unit76.
. Этот метод и по названию, и по логике работы сильно напоминает так часто встречаемый нами интерпретатор шитого байт‑кода. Легко и просто находится место выборки и расшифровки текущей команды.
На скриншоте видно, что байт‑код извлекается в регистр esi
из потока по адресу [
, где edx
— базовый адрес текущей процедуры, а eax
— текущее смещение относительно него. Что же это за скрипты такие и какой байт‑код им соответствует?
Погуглив по названию класса TPSExec
, мы сразу натыкаемся на термин Pascal Script. В двух словах — это паскалеподобный скриптовый язык, используемый, в частности, в сценариях InnoSetup.
Как только мы разобрались, с чем имеем дело, дальнейший путь превращается в скоростное шоссе. Для начала попробуем вытащить скомпилированный байт‑код скрипта из инсталлятора. Оказывается, для этого вовсе не обязательно танцевать с бубном, дампя скомпилированный байт‑код из памяти отладчика. Специально обученные энтузиасты создали несколько проектов распаковщиков дистрибутивов InnoSetup, причем с открытым кодом. Например, innoextract и innounp. Запустив innounp.
из последнего пакета с ключом -m
, мы получаем информацию о встроенном в него Pascal-скрипте (не путать с инсталляционным скриптом .
, представляющим собой список файлов устанавливаемого дистрибутива):
; Version detected: 6100 (Unicode)
Compression used: lzma
Files: 457 ; Bytes: 218186809
Compiled Pascal script: 10715 byte(s)
Если мы распакуем дистрибутив этой утилитой с ключом -m
, то компилированный код Pascal Script будет сохранен в файл с капитанским названием CompiledCode.
. Что же за код находится внутри данного файла?
По счастью, и здесь от нас не требуется изобретать велосипед — все уже придумано до нас. Слегка погуглив, находим проект IFPSTools, включающий в себя дизассемблер Pascal Script ifpsdasm. Существует даже весьма толковый декомпилятор CompiledCode в исходный паскалевский код Inno Setup Decompiler. К сожалению, проект, похоже, мертв, однако сам декомпилятор все еще можно скачать по ссылке. С него мы и начнем исследовать наш код. Довольно быстро мы находим в нем вызов окна сообщения:
...v_58 := 'Status';v_59 := 0;v_60 := v_1;v_54 := IDISPATCHINVOKE(v_60, v_59, v_58, v_55);v_53 := v_54 < 500;v_45 := v_45 and v_53;label_8570:flag := not v_45;if flag then goto label_8846; Этот переход надо заменить безусловнымlabel_8583:v_62 := 0;v_63 := 2;v_64 := 'Serial Number is invalid. Please enter valid license you received or contact support';v_61 := MSGBOX(v_64, v_63, v_62);result := 0;goto label_8858;label_8846:result := 1;label_8858:goto label_9271;...
Попробуем теперь найти это место в скомпилированном коде, чтобы поправить его. Дизассемблировав CompiledCode.
при помощи ifpsdasm, находим ассемблерный эквивалент приведенного выше скриптового кода:
...
lt Var3, Var4, S32(500) pop ; StackCount = 3
and Var2, Var3
pop ; StackCount = 2
loc_4ec:
sfz Var2
pop ; StackCount = 1
jf loc_600 ; Этот переход надо заменить безусловным
pushtype S32 ; StackCount = 2
pushtype S32 ; StackCount = 3
assign Var3, S32(0) pushtype TMSGBOXTYPE ; StackCount = 4
assign Var4, TMSGBOXTYPE(2) pushtype UnicodeString_2 ; StackCount = 5
assign Var5, UnicodeString_3("Serial Number is invalid. Please enter valid license you received or contact support") pushvar Var2 ; StackCount = 6
call MSGBOX
pop ; StackCount = 5
pop ; StackCount = 4
pop ; StackCount = 3
pop ; StackCount = 2
pop ; StackCount = 1
assign RetVal, BOOLEAN(0) jump loc_60c
loc_600:
assign RetVal, BOOLEAN(1)loc_60c:
...
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»