Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Если установка программы протекает совсем не так, как хочется пользователю, приходится вмешиваться в работу инсталлятора. Сегодня мы поговорим о том, как устроен инсталляционный пакет InnoSetup, и научимся менять его логику изнутри.
warning
Статья написана в исследовательских целях, имеет ознакомительный характер и предназначена для специалистов по безопасности. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Использование или распространение ПО без лицензии производителя может преследоваться по закону.
В статье «Ломаем инсталлятор. Как обмануть инсталлятор MSI методом для ленивых» я писал, что частенько приходится допиливать не только саму программу, но и ее инсталлятор. В той заметке мы рассматривали принципы реверс‑инжиниринга и патчинга инсталляционных пакетов, созданных при помощи инсталлятора InstallShield (MSI). Сегодня нашей целью будет другой популярный пакет — InnoSetup. Думаю, этот тип инсталляторов настолько широко распространен, что не нуждается в подробном описании, поэтому сразу перейдем к конкретной задаче.
Итак, у нас имеется установщик для некоего графического плагина, оформленный в виде самодостаточного исполняемого EXE-модуля. При запуске он просит ввести серийный номер, судя по всему, проверяет его на удаленном сервере и при неправильном вводе выдает сообщение об ошибке.
Открыв наш инсталлятор в Detect It Easy, выясняем сразу два факта: во‑первых, это наш пациент, а во‑вторых, InnoSetup насквозь писан на Delphi.
Как подсказывает опыт, открывать инсталлятор в IDA нет ни малейшего смысла. Прежде всего, это дельфи, тут скорее помог бы IDR. С другой стороны, файл чуть менее чем целиком состоит из упакованного или зашифрованного оверлея (строка Serial Number is invalid предсказуемо не находится в нем в открытом виде), то есть его загрузчик не несет ничего полезного для решения нашей проблемы.
Поэтому сразу попробуем пощупать процедуру проверки серийного номера «изнутри», в процессе работы программы. Загрузив инсталлятор в отладчик x64dbg, мы обнаруживаем, что наши предположения верны. Загрузчик порождает несколько процессов, которые далее живут собственной жизнью независимо от него. В частности, окно сообщения и все остальные диалоговые окна вызываются из процесса, порождаемого модулем, который находится во вложенной папке is-JJ5LI.tmp
каталога временных файлов системы.
При загрузке инсталлятор перво‑наперво создает этот каталог, распаковывает в него данный модуль, который потом запускает, а в конце инсталляции убирает за собой, удаляя и файл, и каталог. Рассмотрим этот модуль более детально.
Строка Serial Number is invalid
отсутствует в открытом виде и здесь тоже. Detect It Easy не говорит про модуль ничего внятного, кроме того, что он тоже написан на Delphi и содержит в ресурсе еще один модуль, написанный на Microsoft Visual C.
Попробуем копнуть чуть глубже: аттачимся с помощью x64dbg к процессу в момент появления диалогового окна "Serial Number is invalid"
. Код вызова 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.TApplication.MessageBox
, — что ж, вполне логично. Попробуем теперь отследить, откуда было вызвано это сообщение об ошибке.
Открываем вкладку «Стек вызовов» и буквально семью вложениями выше (или ниже, кому как больше нравится) обнаруживаем интересный метод _Unit76.TPSExec.RunScript
. Этот метод и по названию, и по логике работы сильно напоминает так часто встречаемый нами интерпретатор шитого байт‑кода. Легко и просто находится место выборки и расшифровки текущей команды.
На скриншоте видно, что байт‑код извлекается в регистр esi
из потока по адресу [edx+eax]
, где edx
— базовый адрес текущей процедуры, а eax
— текущее смещение относительно него. Что же это за скрипты такие и какой байт‑код им соответствует?
Погуглив по названию класса TPSExec
, мы сразу натыкаемся на термин Pascal Script. В двух словах — это паскалеподобный скриптовый язык, используемый, в частности, в сценариях InnoSetup.
Как только мы разобрались, с чем имеем дело, дальнейший путь превращается в скоростное шоссе. Для начала попробуем вытащить скомпилированный байт‑код скрипта из инсталлятора. Оказывается, для этого вовсе не обязательно танцевать с бубном, дампя скомпилированный байт‑код из памяти отладчика. Специально обученные энтузиасты создали несколько проектов распаковщиков дистрибутивов InnoSetup, причем с открытым кодом. Например, innoextract и innounp. Запустив innounp.exe
из последнего пакета с ключом -m
, мы получаем информацию о встроенном в него Pascal-скрипте (не путать с инсталляционным скриптом .iss
, представляющим собой список файлов устанавливаемого дистрибутива):
; Version detected: 6100 (Unicode)
Compression used: lzma
Files: 457 ; Bytes: 218186809
Compiled Pascal script: 10715 byte(s)
Если мы распакуем дистрибутив этой утилитой с ключом -m
, то компилированный код Pascal Script будет сохранен в файл с капитанским названием CompiledCode.bin
. Что же за код находится внутри данного файла?
По счастью, и здесь от нас не требуется изобретать велосипед — все уже придумано до нас. Слегка погуглив, находим проект 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.bin
при помощи 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:
...
Источник: xakep.ru