В сегод­няшней статье мы под­робнее раз­берем работу вир­туаль­ной машины уста­нов­щика InstallShield в динами­ке при помощи нашего любимо­го отладчи­ка x64dbg на при­мере инстал­лятора одно­го тех­ничес­кого при­ложе­ния.

warning

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

Но­вое — это хорошо забытое ста­рое, поэто­му любую тему, что­бы не при­елась, надо подоль­ше выдер­жать в прох­ладном тем­ном под­вале. Я думаю, ты пом­нишь ста­рую статью «Ло­маем инстал­лятор. Как обма­нуть инстал­лятор MSI методом для ленивых», в которой мы начина­ли раз­бор инстал­ляци­онных скрип­тов для пакета InstallShield. Я тог­да сра­зу пре­дуп­редил, что это самый ленивый спо­соб пат­ча без раз­бора вир­туаль­ной машины и отладки для сов­сем прос­тых слу­чаев. Ну это при­мер­но как пат­чить EXE-файл толь­ко при помощи WinHex без отладчи­ка и дизас­сем­бле­ра или чинить компь­ютер одной плос­кой отвер­ткой.

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

При вво­де невер­ного серий­ного номера, как ты уже, навер­ное, догадал­ся, инстал­лятор выда­ет пре­дуп­режде­ние и отка­зыва­ется дви­гать­ся даль­ше. Нам пред­сто­ит понять, как он про­веря­ет код. На этот раз пат­чить мы ничего не будем. Во‑пер­вых, потому что мы чес­тные люди и нам инте­ресен не конеч­ный резуль­тат, а сам про­цесс чис­то в обра­зова­тель­ных целях, а во‑вто­рых, про­цесс пат­чинга мы уже раз­бирали в упо­мяну­той статье, с которой я тебе рекомен­дую озна­комить­ся для луч­шего понима­ния того, о чем я буду писать ниже. Впро­чем, для ленивых и нетер­пеливых я буду ста­рать­ся объ­яснять так, что­бы читатель не силь­но стра­дал от недос­татка информа­ции.

Что­бы не засорять повес­тво­вание дуб­лирова­нием информа­ции из пре­дыду­щей статьи, опус­тим про­цесс поис­ка, извле­чения и деком­пиляции инстал­ляци­онно­го скрип­та setup.inx — в нашем слу­чае фай­лы setup.inx, data1.cab, data1.hdr и дру­гие лежат в явном виде рядом с уста­нов­щиком setup.exe. Для рас­шифров­ки и деком­пиляции скрип­та исполь­зуем ути­литу isDcc31.exe — это более новый ана­лог ути­литы isDcc, опи­сан­ной в пре­дыду­щей статье, а что поделать, годы летят! Тул­зу мож­но взять, нап­ример, из пос­ледней вер­сии па­кета UniExtract. Кста­ти, этим же инс­тру­мен­том, точ­нее, вхо­дящей в его сос­тав ути­литой IsXunpack.exe мож­но при необ­ходимос­ти рас­паковать и иссле­довать инстал­ляци­онный cab-архив, но нас пока что инте­ресу­ет инстал­ляци­онный скрипт setup.inx. Рас­шифро­выва­ем (unscramble) его сле­дующей коман­дой:

isdcc31.exe -u setup.inx

За­тем деком­пилиру­ем:

isdcc31.exe setup.inx.dec >setup.dec

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

Мы уже успе­ли изрядно под­натореть в этом неп­ростом деле, поэто­му дей­ству­ем по стан­дар­тной схе­ме. Ког­да уста­нов­щик висит на окне вво­да серий­ного номера и дан­ных поль­зовате­ля, под­клю­чаем­ся отладчи­ком x64dbg к про­цес­су setup.exe. Для начала поп­робу­ем про­верить самую оче­вид­ную гипоте­зу, что шитый код вир­туаль­ной машины в этот момент находит­ся в памяти про­цес­са. В этом слу­чае мож­но было бы пос­тавить точ­ку оста­нова на обра­щение к какому‑то извес­тно­му учас­тку это­го кода, что­бы отсле­дить его со сто­роны вир­туаль­ной машины.

К сожале­нию, поиск в памяти резуль­татов не дает, что наводит на пред­положе­ние: шитый код не интер­пре­тиру­ется непос­редс­твен­но, а с ним при заг­рузке про­исхо­дит какая‑то обра­бот­ка, воз­можно, даже JIT-ком­пиляция, как в слу­чае с IL или JVM. Конеч­но, таких пыт­ливых спе­циалис­тов, как мы, это досад­ное пре­пятс­твие не пуга­ет, поэто­му поп­робу­ем подой­ти к решению проб­лемы со сто­роны WinAPI.

Ре­зон­но полагая, что для чте­ния тек­ста из поля вво­да обыч­но исполь­зует­ся фун­кция user32.GetDlgItem, ста­вим бряк на нее и сме­ло жмем Next. Это очень рас­простра­нен­ная фун­кция, поэто­му при­ходит­ся прос­кочить с десяток неин­терес­ных нам сис­темных вызовов из раз­личных обра­бот­чиков событий, пока не натыка­емся на инте­ресу­ющее нас мес­то пря­мого обра­щения из InstallShield, стек вызовов которо­го выг­лядит так.

Наш нат­рениро­ван­ный глаз сра­зу рас­позна­ет рекур­сивный вызов под­прог­рамм вир­туаль­ной машины (пов­торя­ющиеся пат­терны в сте­ке выделе­ны стрел­ками). Вдо­бавок ста­ло понят­но, в какой имен­но биб­лиоте­ке эта самая вир­туаль­ная машина сидит, — это модуль issetup.dll, в нашем слу­чае лежащий в катало­ге рядом с setup.exe. Прав­да, хит­рые раз­работ­чики неук­люже запако­вали ее при помощи PECompact, но это даже смеш­но: не отвле­каясь на поиск рас­паков­щика, тупо дам­пим его при помощи Scylla и скар­мли­ваем сдам­плен­ное дизас­сем­бле­ру IDA.

Те­перь начина­ем вдум­чиво изу­чать стек вызовов, опи­раясь на вос­ста­нов­ленный в IDA код issetup.dll. Бук­валь­но на вто­ром свер­ху вызове нас ждет инте­рес­ное откры­тие — user32.GetDlgItem непос­редс­твен­но вызыва­ется из фун­кции, ссыл­ка на которую находит­ся в 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, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии