Взлом протектора .NET Reactor

Взлом протектора .NET Reactor

the Matrix


Есть множество различных способов защиты от реверса приложений .NET. Среди них — ком­прес­сия, шиф­рование и протекторы типа Agile.Net и Enigma. Мы уже рассказывали о взломе протектора Enigma. Сегодня поговорим о взломе защиты протектора .NET Reactor.

Еще по теме: Обход защиты протектора Obsidium

Протектор .NET Reactor

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

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

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


Запуск программы в отладчи­ке dnSpy
Взлом приложения с защитой .NET Reactor

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

Вернемся в отладчик dnSpy и попробуем т­расси­ровать работа­ющее приложение. А теперь хорошая новость: в программе нет анти­отладчи­ка и она прек­расно запус­кает­ся и трас­сиру­ется, при­чем при трас­сиров­ке «пус­тых» методов во вклад­ке Call Stack мы видим, что счет­чик команд переме­щает­ся по невиди­мому коду и про­вали­вает­ся в вызовы.

Побродив всле­пую по коду, мы видим еще одну хорошую вещь: не все методы пере­име­нова­ны, некото­рые наз­вания очень даже осмыслен­ны, и можем даже нащупать про­цесс про­вер­ки валид­ности (на скрин­е выше — isValid). Тело дан­ного метода скры­то, однако наз­вание и индекс извес­тны, и это вери гуд.

Теперь пробуем попробовать деоб­фуска­цию по стан­дар­тной схе­ме: в начале подсунем предложение в de4dot. Увы, но в нашем примере данный метод не сработал и de4dot не деоб­фусци­рует. Более ранние вер­сии сра­зу вываливаются с ошиб­кой:

1234567891011de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.comLatest version and source code: https://github.com/0xd4d/de4dot Detected .NET Reactor 4.8 Не­обра­ботан­ное исклю­чение: System.Security.Cryptography.CryptographicException: Недопус­тимая дли­на дан­ных для дешиф­рования. в System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount) в de4dot.code.deobfuscators.DeobUtils.AesDecrypt(Byte[] data, Byte[] key, Byte[] iv) в D:\a\de4dot-cex\de4dot-cex\de4dot.code\deobfuscators\DeobUtils.cs:стро­ка 87 в de4dot.code.deobfuscators.dotNET_Reactor.v4.EncryptedResource.DecrypterV1.Decrypt(EmbeddedResource resource) в D:\a\de4dot-cex\de4dot-cex\de4dot.code\deobfuscators\dotNET_Reactor\v4\EncryptedResource.cs:стро­ка 225 в de4dot.code.deobfuscators.dotNET_Reactor.v4.EncryptedResource.Decrypt()...

Вер­сии новее фор­мулиру­ют ошиб­ку лаконич­нее:

123456789Latest version and source code: http://www.de4dot.com/21 deobfuscator modules loaded! Detected .NET Reactor 4.8 ERROR:ERROR:ERROR:ERROR: Hmmmm... something didn’t work. Try the latest version.

Ну теперь мы хотя бы понимаем, с каким протектором име­ем дело, — это .NET Reactor пред­положи­тель­но версии 4.8. Это старая версия, но с ней не может справиться даже спе­циаль­но заточенный под .NET Reactor de4dot. Та же ошиб­ка и нам опять пред­лага­ют поис­кать вер­сию поновее.

Открываем нашу зло­получ­ное приложение в отладчике x32dbg. Заг­ружа­ем биб­лиоте­ку cljit.dll, отла­доч­ные сим­волы к ней и уста­новим точ­ку оста­нова на вход JIT-ком­пилято­раCILJit::compileMethod.

Ука­зан­ный спо­соб работа­ет, то есть при каж­дом вызове ком­пилято­ра в полеILCode струк­турыCORINFO_METHOD_INFO мы видим рас­шифро­ван­ный IL-код каж­дого метода. В прин­ципе, мож­но ана­лизи­ровать код и даже пат­чить на лету, но это дол­го и уто­митель­но, вдо­бавок нас ждет еще одна лож­ка дег­тя.

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

Так вот, этот хал­турный спо­соб работа­ет не всег­да, в чем мы с огор­чени­ем и убеж­даем­ся. Зная индекс методаisValid 25250 (0x62A2), дела­ем усло­вием оста­нов­ки на брейк‑пой­нтеCILJit::compileMethod выраже­ниеword:[[[esp+0xc]]]==0x62A2, но точ­ка оста­нова не сра­баты­вает, хотя опре­делен­ные этим спо­собом индексы на дру­гих методах похожи на пра­виль­ные. Что‑то пош­ло не так, надо искать более кор­рек­тный спо­соб иден­тифика­ции метода.

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

А при­дума­ли они про­ект под наз­вани­ем NetReactorSlayer. Так же как и упо­мяну­тый выше мод de4dot, он заточен под рас­шифров­ку и деоб­фуска­цию .NET Reactor, но в отли­чие от пре­дыду­щего он не валит­ся с ошиб­кой, а впол­не себе успешно соз­дает деоб­фусци­рован­ный модуль, в котором методы уже не скры­ты от дизас­сем­бли­рова­ния в dnSpy.


Мо­дуль пос­ле NetReactorSlayer

При­чем деоб­фуска­цией мож­но управлять: у прог­раммы есть клю­чи коман­дной стро­ки. К при­меру, при исполь­зовании клю­ча--no-deob (Don’t deobfuscate methods) мы получа­ем исходный обфусци­рован­ный байт‑код метода в том виде, в котором он хра­нит­ся в фай­ле.

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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public bool get_IsValid(){ while (false) { object arg_0A_0 = null[0]; } int arg_66_0 = 0; while (true) { switch (arg_66_0) { case 0: if (!this.IsActivated) { arg_66_0 = 4; if (!false) { continue; } } break; case 1: case 4: goto IL_49; case 5: goto IL_93; } IL_32: if (!this.IsEvaluation) { return true; } arg_66_0 = 5; if (false) { goto IL_49; } continue; IL_83: goto IL_32; IL_49: if (this.IsGenericLicense) { goto IL_83; } return false; } IL_93: return this.DaysLeft > 0;}

А пос­ле деоб­фуска­ции сво­рачи­вает­ся в коротень­кое однос­троч­ное выраже­ние:

12345public bool get_IsValid(){ return (this.IsActivated || this.IsGenericLicense) && (!this.IsEvaluation || this.DaysLeft > 0);}

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

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

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

Рас­шифров­кой кода в про­екте занима­ется модульNecroBit.cs, а кон­крет­но методExecute. Этот метод счи­тыва­ет из обфусци­рован­ного модуля блок зашиф­рован­ных дан­ных и в два при­ема рас­шифро­выва­ет его. Пос­ле стро­ки:

1XorEncrypt(methodsData, GetXorKey(decryptorMethod))

мас­сив methodsData содер­жит рас­шифро­ван­ный код методов обфусци­рован­ного модуля. Давайте пос­мотрим, что NetReactorSlayer про­делы­вает с этим мас­сивом даль­ше, и выяс­ним при­мер­ный фор­мат хра­нения дан­ных в нем. Цикл чте­ния и ана­лиза всех рас­шифро­ван­ных методов выг­лядит так:

123456789101112while ((ulong)methodsDataReader.Position < (ulong)((long)(methodsData.Length - 1))){ ... int size2 = methodsDataReader.ReadInt32(); // Размер IL-кода метода byte[] methodData = methodsDataReader.ReadBytes(size2); // IL-код метода if (!rvaToIndex.TryGetValue(rva3, out int methodIndex)) // methodIndex — индекс метода { Logger.Warn("Couldn't find method with RVA: " + rva3); } else { uint methodToken = (uint)(100663297 + methodIndex); Токен метода

Вот сра­зу за этим мес­том мы уже зна­ем сме­щение до рас­шифро­ван­ного IL-кода метода нуж­ного нам индекса и сам рас­шифро­ван­ный код. В нашем слу­чае IL-код методаisValid выг­лядит так:

123456789/* 0x00158568 2B09 */ IL_0000: br.s IL_000B/* 0x0015856A 28FFFFFFFF */ IL_0002: call /* 0x0015856F 14 */ IL_0007: ldnull/* 0x00158570 16 */ IL_0008: ldc.i4.0/* 0x00158571 9A */ IL_0009: ldelem.ref/* 0x00158572 26 */ IL_000A: pop/* 0x00158573 16 */ IL_000B: ldc.i4.0/* 0x00158574 2DF9 */ IL_000C: brtrue.s IL_0007...

Что­бы любая лицен­зия ста­ла валид­ной, нам дос­таточ­но поменять в этом коде два пер­вых бай­та на сле­дующие:

12/* 0x00158568 17 */ IL_0000: ldc.i4.0/* 0x00158569 2A */ IL_0001: ret

В исходном (нерас­шифро­ван­ном) модуле по это­му RVA зна­чения двух бай­тов соот­ветс­твен­но рав­ны 9E F4. По счастью, метод шиф­рования дан­ных — обыч­ный XOR по клю­чу.

Счи­таем новые зна­чения этих двух бай­тов:

129E XOR 2B XOR 17 = A2F4 XOR 09 XOR 2A = D7

Ме­няем эти два бай­та на новые зна­чения и на вся­кий слу­чай про­веря­ем пра­виль­ность замены, еще раз нат­равив на исправ­ленный модуль NetReactorSlayer.

Теперь и вправ­ду декоди­рован­ное тело метода содер­жит толь­коreturn true, что и под­твержда­ет запуск прог­раммы — лицен­зия под­ходит! Таким обра­зом, мы получи­ли не толь­ко полез­ный рас­ширя­емый и совер­шенс­тву­емый инс­тру­мент для ревер­са при­ложе­ний, защищен­ных .NET Reactor (в том чис­ле нес­тандар­тных), но и быс­трый спо­соб пат­ча подоб­ных при­ложе­ний без пол­ного ревер­са и перес­борки прог­раммы.

Полезные ссылки:


the Matrix

Наши проекты:

- Канал о хакинге: /me Hacker
- Канал о кодинге: Minor Code | IT
- Канал об IT Tech: TechGod | IT

Report Page