warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Новое — это хорошо забытое старое, поэтому любую тему, чтобы не приелась, надо подольше выдержать в прохладном темном подвале. Я думаю, ты помнишь старую статью «Ломаем инсталлятор. Как обмануть инсталлятор MSI методом для ленивых», в которой мы начинали разбор инсталляционных скриптов для пакета InstallShield. Я тогда сразу предупредил, что это самый ленивый способ патча без разбора виртуальной машины и отладки для совсем простых случаев. Ну это примерно как патчить EXE-файл только при помощи WinHex без отладчика и дизассемблера или чинить компьютер одной плоской отверткой.
Прошло уже пять лет, мы многому научились, так что пора достать эту тему из подвала и продолжить разбор, пока InstallShield совсем не потерял актуальность. Итак, у нас есть инсталлятор приложения, который при установке запрашивает данные пользователя и серийный номер.
При вводе неверного серийного номера, как ты уже, наверное, догадался, инсталлятор выдает предупреждение и отказывается двигаться дальше. Нам предстоит понять, как он проверяет код. На этот раз патчить мы ничего не будем. Во‑первых, потому что мы честные люди и нам интересен не конечный результат, а сам процесс чисто в образовательных целях, а во‑вторых, процесс патчинга мы уже разбирали в упомянутой статье, с которой я тебе рекомендую ознакомиться для лучшего понимания того, о чем я буду писать ниже. Впрочем, для ленивых и нетерпеливых я буду стараться объяснять так, чтобы читатель не сильно страдал от недостатка информации.
Чтобы не засорять повествование дублированием информации из предыдущей статьи, опустим процесс поиска, извлечения и декомпиляции инсталляционного скрипта setup.
— в нашем случае файлы setup.
, data1.
, data1.
и другие лежат в явном виде рядом с установщиком setup.
. Для расшифровки и декомпиляции скрипта используем утилиту isDcc31.
— это более новый аналог утилиты isDcc, описанной в предыдущей статье, а что поделать, годы летят! Тулзу можно взять, например, из последней версии пакета UniExtract. Кстати, этим же инструментом, точнее, входящей в его состав утилитой IsXunpack.
можно при необходимости распаковать и исследовать инсталляционный cab-архив, но нас пока что интересует инсталляционный скрипт setup.
. Расшифровываем (unscramble) его следующей командой:
isdcc31.exe -u setup.inx
Затем декомпилируем:
isdcc31.exe setup.inx.dec >setup.dec
На этом предварительный этап заканчивается и начинается процесс изысканий. Мы получили примерно мегабайт текстового кода, в котором нет ни имен переменных или процедур, ни текстовых строк, способных как‑то внести ясность в понимание процесса проверки и чтения серийного номера. Поэтому схалтурить, как в предыдущем случае, у нас не получится — надо расчехлять отладчик и кропотливо разбирать виртуальную машину InstallShield.
Мы уже успели изрядно поднатореть в этом непростом деле, поэтому действуем по стандартной схеме. Когда установщик висит на окне ввода серийного номера и данных пользователя, подключаемся отладчиком x64dbg к процессу setup.
. Для начала попробуем проверить самую очевидную гипотезу, что шитый код виртуальной машины в этот момент находится в памяти процесса. В этом случае можно было бы поставить точку останова на обращение к какому‑то известному участку этого кода, чтобы отследить его со стороны виртуальной машины.
К сожалению, поиск в памяти результатов не дает, что наводит на предположение: шитый код не интерпретируется непосредственно, а с ним при загрузке происходит какая‑то обработка, возможно, даже JIT-компиляция, как в случае с IL или JVM. Конечно, таких пытливых специалистов, как мы, это досадное препятствие не пугает, поэтому попробуем подойти к решению проблемы со стороны WinAPI.
Резонно полагая, что для чтения текста из поля ввода обычно используется функция user32.
, ставим бряк на нее и смело жмем Next. Это очень распространенная функция, поэтому приходится проскочить с десяток неинтересных нам системных вызовов из различных обработчиков событий, пока не натыкаемся на интересующее нас место прямого обращения из InstallShield, стек вызовов которого выглядит так.

Наш натренированный глаз сразу распознает рекурсивный вызов подпрограмм виртуальной машины (повторяющиеся паттерны в стеке выделены стрелками). Вдобавок стало понятно, в какой именно библиотеке эта самая виртуальная машина сидит, — это модуль issetup.
, в нашем случае лежащий в каталоге рядом с setup.
. Правда, хитрые разработчики неуклюже запаковали ее при помощи PECompact, но это даже смешно: не отвлекаясь на поиск распаковщика, тупо дампим его при помощи Scylla и скармливаем сдампленное дизассемблеру IDA.
Теперь начинаем вдумчиво изучать стек вызовов, опираясь на восстановленный в IDA код issetup.
. Буквально на втором сверху вызове нас ждет интересное открытие — user32.
непосредственно вызывается из функции, ссылка на которую находится в vftable такого вида.
Для дальнейшего понимания этой таблицы попробуем сделать то, что мы должны были сделать еще в предыдущей статье, но впопыхах проскочили из‑за банальной лени, а именно — разобрать систему команд шитого кода интерпретатора инсталляционного скрипта. В прошлый раз мы, помнится, ограничились эмпирическим нахождением опкодов 0xD
(сравнение на эквивалентность) и 0xE
(сравнение на неравенство). На этот раз мы копнем поглубже и попробуем вытащить полную систему команд.
Для этого нам понадобится какой‑нибудь декомпилятор INX в исходных кодах. Можно использовать исходники нашего isDcc c гитахба, но, по‑моему, гораздо удобнее и нагляднее использовать для этого другой декомпилятор — InstallScript Decompiler, который умеет не только декомпилировать, но еще и дизассемблировать INX-код. Впрочем, на мой взгляд, реально декомпилировать скрипты все же удобнее isDcc, потому что InstallScript Decompiler изрядно глюкав.
В итоге, покопавшись в коде InstallScript Decompiler, мы находим модуль Action, содержащий список команд интерпретатора (их так и называют — Actions) со своими опкодами. Их не так много, поэтому приведу здесь таблицу целиком:
Опкод | Описание |
---|---|
1 | CNOPAction |
2 | CAbortAction |
3 | CExitAction |
4 | CIfAction |
5 | CGotoAction |
6 | CAssignAction |
7 | BinAdd |
8 | BinMod |
9 | BinLT |
10 | BinGT |
11 | BinLTE |
12 | BinGTE |
13 | BinEq |
14 | BinNEq |
15 | BinSub |
16 | BinMul |
17 | BinDiv |
18 | BitAnd |
19 | BitOr |
20 | BitXor |
21 | ~ |
22 | BitShl |
23 | BitShr |
24 | LogAnd |
25 | LogOr |
26 | CAddressOfAction |
27 | * |
28 | CIndirectStructAction |
29 | CSetByteAction |
30 | CGetByteAction |
32 | CDLLFuncCallAction |
33 | CInternalFuncCallAction |
34 | CFuncPrologAction |
35 | CReturnAction |
36 | CReturnAction |
37 | CReturnAction |
38 | CEndFuncAction |
39 | CNOPAction |
40 | CStrLengthCharsAction |
41 | CStrSubAction |
42 | CStrFindAction |
43 | CStrCompareAction |
44 | CStrToNumAction |
45 | CNumToStrAction |
46 | CHandlerAction |
47 | CHandlerExAction |
48 | CDoHandlerAction |
49 | CResizeAction |
50 | CSizeofAction |
51 | CPropPutAction |
52 | CPropPutRefAction |
53 | CPropGetAction |
54 | CTryAction |
55 | CEndTryAction |
56 | CEndCatchAction |
57 | CUseDLLAction |
58 | CUnUseDLLAction |
59 | CBindVariableAction |
60 | CAddressOfWideAction |
Эмпирически подобранные нами в предыдущей статье акции с опкодами 0xD
(13) и 0xE
(14), соответственно, являются BinEq
и BinNEq
, что вполне вписывается в рассматриваемую схему. Еще немного покурив исходники декомпиляторов, приходим к примерному пониманию структуры шитого кода инсталляционного скрипта. Разберем ее на примере функции с условным названием function0
, бинарный код которой приведен на следующем рисунке.
Декомпилированный в isDcc31 код этой функции выглядит так:
function function0(pBool0)
begin
Label0:
008142:0006: pBool0 = 0;
00814E:0014: lString0 = lString4 ^ "MANUALS\\setup.exe";
00816C:0021: call function438(3,lString0);
00817A:0006: lNumber0 = number0;
008184:000D: lNumber0 = lNumber0 == 1;
008193:0004: if lNumber0 == false then goto label1 ;
00819F:0006: pBool0 = 1;
Label1:
0081AD:0024: return;
0081BB:0026: end;
end;
Для лучшего понимания приведу еще ее «дизассемблированный» с помощью InstallScript Decompiler код, но я предупреждал, дизассемблер там весьма специфичен:
8137: v {CNumArg} -101 = 0
8137: v {CStrArg} -101 = v {CStrArg} 4 13 "MANUALS\setup.exe"
8137: Func_1107(3, v {CStrArg} -101)
8137: v {CNumArg} -102 = v {CVariantArg} 0
8137: v {CNumArg} -102 = v {CNumArg} -102 7 1
8137: If (else:1) v {CNumArg} -102
8137: v {CNumArg} -101 = 1
81ab: RETURN
81ab: EndFuncAction
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее