April 30, 2024

WiX-фикс. Исследуем инсталлятор Windows Installer XML Toolset

Ин­стал­ляторы быва­ют раз­ные: и поп­роще, и пос­ложнее. Иног­да сре­ди них попада­ются уж сов­сем наворо­чен­ные — их иссле­дова­ние прев­раща­ется в нас­тоящее прик­лючение. Сегод­ня мы рас­смот­рим имен­но такой слу­чай: раз­берем­ся, как устро­ен изнутри инстал­лятор WiX.

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

Итак, у нас име­ется инстал­лятор для неко­его тех­ничес­кого пакета, офор­млен­ный в виде весь­ма уве­сис­того (раз­мером в нес­коль­ко гигабай­тов) самодос­таточ­ного EXE-фай­ла. Интерфейс у инстал­лятора прод­винутый и стиль­ный, он не похож на визу­аль­ное пред­став­ление ни одно­го из встре­чав­шихся нам до это­го стан­дар­тных инстал­ляци­онных пакетов. Здесь мно­го эле­мен­тов управле­ния, которые шус­тро перек­люча­ют стра­нич­ки по мере запол­нения, приб­лижая начало уста­нов­ки прог­раммы. Но вот беда — в опре­делен­ный момент инстал­лятор начина­ет тре­бовать лицен­зию, без которой упор­но не жела­ет перехо­дить на сле­дующий экран.

Мы уже встре­чались с подоб­ным поведе­нием прог­рамм уста­нов­ки в пре­дыду­щих стать­ях. Одна­ко в этот раз выяс­нилось, что прог­рамма содер­жит все неп­рият­ные фичи, при­сущие инстал­ляторам:

  1. Из весь­ма уве­сис­того EXE-фай­ла исполня­емым явля­ется толь­ко кро­хот­ный и совер­шенно неин­форма­тив­ный заг­рузчик.
  2. Вла­делец про­цес­са тоже отно­ситель­но неболь­шой EXE-файл, запущен­ный из сис­темной пап­ки вре­мен­ных фай­лов.
  3. Пе­ред нами не хорошо изу­чен­ные msiexec или InstallShield, а что‑то иное.

По­пытав­шись при­атта­чить­ся к запущен­ному про­цес­су при помощи x64dbg, мы видим, что код явно ском­пилиро­ван в про­цес­се исполне­ния. Этот код до боли напоми­нает .NET, что кос­венно под­твержда­ется наличи­ем в памяти про­цес­са заг­ружен­ных дот­нетов­ских биб­лиотек (clr.dll, clrjit.dll, mscoree.dll и так далее). Одна­ко ни сам инстал­лятор, ни исполня­емый файл из вре­мен­ной пап­ки .NET-при­ложе­ниями не явля­ются.

Дот­нетов­ский отладчик dnSpy хоть и рас­позна­ет про­цесс как род­ной и даже успешно кон­нектит­ся к нему, но никако­го дот­нетов­ско­го кода при этом не показы­вает и трас­сировать при­ложе­ние не дает. Необ­ходимую зацеп­ку нам дает Detect It Easy.

Оче­вид­но, и сам инстал­лятор, и вре­мен­ный файл рас­позна­ются DIE как WiX Toolset installer с овер­леем Microsoft Cabinet File (CAB). Про CAB, в прин­ципе, мож­но было бы догадать­ся по сиг­натуре 4D534346 (MSCF) у овер­леев, но это нам еще при­годит­ся чуть поз­же.

А пока поп­робу­ем вспом­нить, что такое WiX и чем имен­но это зна­ние может помочь в даль­нейшем ана­лизе при­ложе­ния. Это сло­во из трех букв рас­шифро­выва­ется как Windows Installer XML Toolset и пред­став­ляет собой (как сле­дует из наз­вания) набор инс­тру­мен­тов для соз­дания инстал­ляци­онных пакетов на осно­ве XML-опи­сания. Если тебе инте­рес­но узнать попод­робнее про этот пакет, ты можешь озна­комить­ся с его осо­бен­ностя­ми.

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

Cуть в том, что основное дос­тоинс­тво тех­нологии WiX в ее рас­ширя­емос­ти — нес­мотря на наличие сво­их собс­твен­ных интерфей­сных шаб­лонов, в ней име­ется воз­можность под­клю­чить кас­томный интерфейс поль­зовате­ля, написан­ный на .NET (Custom Bootstrapper Application). Что мы и наб­люда­ем в нашем при­ложе­нии.

Пе­ред нами же сто­ит пря­мо про­тиво­полож­ная задача — разоб­рать­ся с сущес­тву­ющим при­ложе­нием. Для это­го его нуж­но хотя бы разоб­рать на сос­тавные фай­лы, и, к счастью, WiX пре­дос­тавля­ет для это­го необ­ходимый инс­тру­мен­тарий. Для начала заходим на GitHub и кача­ем отту­да архив wix311-binaries.zip. Он содер­жит набор ути­лит коман­дной стро­ки для работы с WiX-инстал­лятора­ми, из которо­го для нас пред­став­ляет инте­рес dark.exe — экс­трак­тор фай­лов из WiX-архи­ва. Поль­зовать­ся им не прос­то, а очень прос­то: надо все­го‑нав­сего запус­тить его из коман­дной стро­ки:

dark.exe <имя нашего инсталлятора.exe> -x <имя каталога, в который будут распаковываться файлы пакета>

Пос­ле отра­бот­ки коман­ды в ука­зан­ном нами катало­ге будет соз­дана слож­ная сис­тема под­катало­гов с инстал­лиру­емы­ми MSI-пакета­ми, ресур­сами и биб­лиоте­ками, необ­ходимы­ми для уста­нов­ки. К велико­му сожале­нию, это работа­ет толь­ко в одну сто­рону. Фарш невоз­можно про­вер­нуть назад, и соб­рать обратно из этой солян­ки инстал­ляци­онный EXE будет сов­сем не прос­то, но мы подума­ем об этом поз­же. А сей­час нас инте­ресу­ют ресур­сы WiX, которые находят­ся в под­папке UX в кор­не рас­пакован­ного катало­га. В час­тнос­ти, осо­бый инте­рес пред­став­ляет файл BootstrapperCore.config, в сек­ции wix.bootstrapper которо­го (как мы узна­ли из про­читан­ной докумен­тации) и содер­жится имя Custom Bootstrapper Application:

<?xml version="1.0" encoding="utf-8" ?><configuration> <configSections> <sectionGroup name="wix.bootstrapper" type="Microsoft.Tools.WindowsInstallerXml.Bootstrapper.BootstrapperSectionGroup, BootstrapperCore"> <section name="host" type="Microsoft.Tools.WindowsInstallerXml.Bootstrapper.HostSection, BootstrapperCore" /> </sectionGroup> </configSections> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" /> </startup> <wix.bootstrapper> <host assemblyName="Xeam.VisualInstaller"> <supportedFramework version="v4\Full" /> <supportedFramework version="v4\Client" /> </host> </wix.bootstrapper></configuration>

По­хоже, мы выш­ли на сле­дующий уро­вень, ибо Xeam Visual Installer — это еще одна надс­трой­ка над WiX. А имен­но сис­тема соз­дания визу­аль­ных инстал­ляторов для тех, кому не нра­вят­ся «скуч­ные обои» встро­енных в WiX шаб­лонов инстал­ляци­онных интерфей­сов. Покурив тут же нагуг­ленное ру­ководс­тво по дан­ному пакету, обна­ружи­ваем, что раз­бира­емый нами инстал­лятор — пол­ностью шаб­лонное при­ложе­ние Xeam Visual Installer, заточен­ное вот под такой шаб­лон.

Соз­датели Xeam Visual Installer изба­вили сво­их поль­зовате­лей от рисова­ния и прог­рамми­рова­ния интерфей­са. Им нуж­но все­го‑нав­сего написать мел­кие про­цеду­ры валида­ции вво­димых парамет­ров и перехо­дов меж­ду экра­нами. Заг­рузив в dnSpy биб­лиоте­ку Bootstrapper.Ui.dll из катало­га UX и нем­ного изу­чив код клас­са Xeam.VisualInstaller.MainWindow, находим вот такой валида­тор лицен­зии.

Нес­мотря на кажущу­юся слож­ность, мы уже научи­лись закора­чивать подоб­ные методы прос­тым двух­бай­товым пат­чем return 1 по сме­щению фай­ла 0x2750.

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

Как я уже писал, пос­ле того как мы разоб­рали WiX-пакет на сос­тавля­ющие при помощи dark.exe, нель­зя так прос­то взять и соб­рать про­пат­ченный пакет обратно. Поп­равить пару бай­тов в самом пакете тоже нель­зя, там ком­прес­сия. Поэто­му при­дет­ся покопать­ся во внут­ренней струк­туре пакета, которая, к сожале­нию, не докумен­тирова­на вооб­ще.

Од­нако в мире дос­таточ­но любопыт­ных энту­зиас­тов, которые оза­дачи­вались этой проб­лемой до нас. В прин­ципе, нес­ложно было бы и самим разоб­рать­ся во всем, минут пят­надцать погоняв код заг­рузчи­ка в отладчи­ке, но не буду заг­ромож­дать повес­тво­вание под­робнос­тями, как получать информа­цию, а перей­ду сра­зу к резуль­тату. Если открыть инстал­лятор в каком‑нибудь редак­торе EXE (нап­ример, Hiew), то в его заголов­ке мы уви­дим сле­дующие сек­ции:

Number Name VirtSize RVA PhysSize Offset Flag 1 .text 00048FF7 00001000 00049000 00000400 60000020 2 .rdata 0001F760 0004A000 0001F800 00049400 40000040 3 .data 000016FC 0006A000 00000A00 00068C00 C0000040 4 .wixburn 00000038 0006C000 00000200 00069600 40000040 5 .rsrc 0007DECC 0006D000 0007E000 00069800 40000040 6 .reloc 00003DD0 000EB000 00003E00 000E7800 42000040

Об­рати вни­мание на сек­цию .wixburn, спе­цифи­чес­кую для каж­дого WiX-инстал­лятора. Имен­но по ней, кста­ти, Detect It Easy и опре­деля­ет при­над­лежность при­ложе­ния к это­му типу. Перей­дя к ука­зан­ной сек­ции, мы видим такую пос­ледова­тель­ность бай­тов.

Она интер­пре­тиру­ется сле­дующим обра­зом:

0 DD Magic number 0xF14300 4 DD Version 2 8 DB 16 DUP (?) Bundled GUID {3F10D12E-D4C3-4DE6-EDBD-079DBB81B6A9}0x18 DD Engine (stub) size 0xEB600 0x1C DD Original checksum 0x1F45150 0x20 DD Original signature offset 0x1F383A8 0x24 DD Original signature size 0x2D78 0x28 DD Container Type (1 = CAB) 1 0x2C DD Container Count 2 0x30 DD Byte count of manifest + UX container 0x1E4CDA2 0x34 DD Byte count of attached container 0x4363C6C9

Поп­робу­ем разоб­рать­ся, что пред­став­ляют собой эти поля. Engine (stub) size — это раз­мер исполня­емо­го модуля, пос­ле него по сме­щению 0xEB600 от начала фай­ла начина­ется овер­лей. Он пред­став­ляет собой содер­жимое катало­га UX, то есть ресур­сы инстал­лятора WiX и Xeam Visual Installer, которые и содер­жат про­пат­ченный нами файл Bootstrapper.Ui.dll.

Как я уже упо­минал, этот овер­лей име­ет сиг­натуру 4D534346 (MSCF), то есть упа­кован в CAB-архив, что и под­твержда­ет стро­ка Container Type=1. Сра­зу за UX container по сме­щению Original signature offset=0x1F383A8 от начала фай­ла начина­ется под­пись фай­ла. Пос­ле нее по сме­щению 0x1F383A8+0x2D78=0x1F3B120 от начала фай­ла рас­положен самый боль­шой attached container, содер­жащий в себе все фай­лы инстал­лиру­емо­го пакета. Этот архив тоже име­ет сиг­натуру MSCF и тип CAB.

Мо­раль всех этих изыс­каний такова: если мы хотим, что­бы инстал­лятор кор­рек­тно работал пос­ле перепа­ков­ки и замены CAB-кон­тей­нера, мы дол­жны поп­равить как минимум сме­щения в заголов­ке сек­ции .wixburn.

Поп­робу­ем про­делать эту опе­рацию на прак­тике.

Для начала откро­ем наш файл в редак­торе WinHex и разобь­ем его на три час­ти: 0-0xEB600 — исполня­емый образ, 0xEB600-0x0x1F383A8 — UX container и хвост от 0x1F383A8 до кон­ца фай­ла — сиг­натура + attached container.

Те­перь нам надо заменить в UX container файл Bootstrapper.Ui.dll исправ­ленным. Эта опе­рация на самом деле не такая уж и прос­тая. Рас­паковы­вать CAB-фай­лы уме­ют прак­тичес­ки все архи­вато­ры, одна­ко внут­реннее содер­жимое у них нес­трук­туриро­ван­ное, фай­лы обез­личены, при­чем они час­то име­ют номер­ные наз­вания. Вдо­бавок мало кто из извес­тных архи­вато­ров уме­ет собирать CAB-архи­вы. 7-Zip и WinRAR, к при­меру, не уме­ют, а вот экзо­тичес­кий PowerArchiver уме­ет, им и вос­поль­зуем­ся.

Вык­рутить­ся с име­нами фай­лов тоже доволь­но прос­то: пос­коль­ку раз­меры фай­лов уни­каль­ные, дос­таточ­но упо­рядо­чить эти самые фай­лы по величи­не и пос­мотреть, какой файл в архи­ве соот­ветс­тву­ет раз­мером нашему Bootstrapper.Ui.dll. В нашем слу­чае это u5 — пере­име­новы­ваем исправ­ленный Bootstrapper.Ui.dll в u5, закиды­ваем его вмес­то преж­него и сно­ва собира­ем CAB при помощи PowerArchiver. Раз­мер при перепа­ков­ке, естес­твен­но, изме­нил­ся, поэто­му пос­ле того, как мы сно­ва скле­или три час­ти, поп­равим заголо­вок сек­ции .wixburn. Как показы­вает прак­тика, дос­таточ­но поменять Original signature offset для того, что­бы инстал­лятор кор­рек­тно отра­ботал.

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