Категория > Новости > Реверсинг .NET. Как искать JIT-компилятор в приложениях - «Новости»

Реверсинг .NET. Как искать JIT-компилятор в приложениях - «Новости»


7-03-2021, 00:00. Автор: Wallace
Боль­ше не эниг­ма. Лома­ем защиту при­ложе­ний Enigma x64 акту­аль­ных вер­сий» и «Три­аль­ный конь. Как сло­мать trial, защищен­ный Enigma Protector».

C лег­кой руки Microsoft одной из самых популяр­ных плат­форм для прог­рамми­рова­ния в нас­тоящее вре­мя ста­ла .NET. Огромное количес­тво инс­тру­мен­тов, биб­лиотек и докумен­тации обес­печива­ют прос­тоту вхож­дения даже для самых начина­ющих кодеров, а кросс‑плат­формен­ность и все более совер­шенная опти­миза­ция кода дела­ют ее одним из основных стан­дартов написа­ния ком­мерчес­кого соф­та. Как следс­твие, инс­тру­мен­тов для взло­ма и реверс‑инжи­нирин­га под эту плат­форму тоже успе­ли соз­дать немало. Сре­ди них dnSpy, ILspy, ILdasm, Dile, SAE и мно­гие дру­гие, имя им — леги­он!


За­дача для ревер­серов упро­щает­ся тем, что по умол­чанию ском­пилиро­ван­ная прог­рамма фак­тичес­ки содер­жит свой исходник: име­на сим­волов хра­нят­ся в явном виде, а кросс‑плат­формен­ный IL-псев­докод лег­ко вос­ста­нав­лива­ется до исходных син­такси­чес­ких конс­трук­ций C# или VB, из которых он был получен при ком­пиляции. Соот­ветс­твен­но, взлом такой прог­раммы для начина­юще­го хакера — одно удо­воль­ствие: дос­таточ­но заг­рузить ее в dnSpy, и вот она, на блю­деч­ке в сво­их исходни­ках, для удобс­тва даже окра­шен­ных в при­ятные цве­та. Отла­живай и правь как хочешь, как буд­то сам эту прог­рамму и написал!


 

Немного теории


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



  • сок­рытие (шиф­рование, ком­прес­сия и так далее) .NET-метадан­ных и IL-кода с вос­ста­нов­лени­ем толь­ко в крат­кий миг JIT-ком­пиляции;

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

  • ком­бинация двух пре­дыду­щих катего­рий.


Се­год­ня мы погово­рим о методах из пер­вой катего­рии. В прин­ципе, наибо­лее прос­той и дубовый спо­соб огра­дить прог­рамму от ILDasm — ском­пилиро­вать ее с атри­бутом SupressIldasmAttribute. Понят­ное дело, это защита от чес­тных людей, пос­коль­ку такая сбор­ка пре­вос­ходно детек­тиру­ется как .NET-при­ложе­ние, деком­пилиру­ется дру­гими инс­тру­мен­тами, а дан­ный атри­бут с пол­пинка сни­мает­ся в CFFexplorer или, при изрядной сно­ров­ке, в прос­том HEX-редак­торе. Более инте­рес­но «завер­нуть» метадан­ные в обыч­ное натив­ное при­ложе­ние, фор­миру­ющее и запус­кающее .NET-сбор­ку на лету.


В этом слу­чае никакие детек­торы не рас­позна­ют в ней .NET, если их пред­варитель­но не обу­чили это­му трю­ку, а деком­пилято­ры и отладчи­ки, с ходу не уви­дев­шие в прог­рамме метадан­ных, обло­мают­ся при заг­рузке. С помощью dnSpy мож­но попытать­ся иссле­довать такое при­ложе­ние, одна­ко при пре­рыва­нии он нав­ряд ли смо­жет вос­ста­новить и трас­сировать код даль­ше, что дела­ет такую отладку бес­полез­ной. Как быть в таком слу­чае?


Са­мый прос­той спо­соб — вос­поль­зовать­ся ути­литой MegaDumper (или даже ее более прод­винутой вер­сией ExtremeDumper). Если .NET сфор­мирован и запущен по всем пра­вилам, то он кор­рек­тно рас­позна­ется упо­мяну­тыми ути­лита­ми имен­но как .NET-про­цесс, и при нажатии кно­поч­ки .NET dump дам­пится как стан­дар­тное .NET-при­ложе­ние. Прав­да, вов­се не факт, что оно будет запус­кать­ся. Что­бы при­вес­ти его в запус­каемый вид, при­дет­ся про­делать опре­делен­ные телод­вижения, в зависи­мос­ти от прод­винутос­ти про­тек­тора. Тем не менее метадан­ные .NET и IL в такой сдам­плен­ной сбор­ке будут дос­тупны для деком­пиляции и ана­лиза. Мож­но убе­дить­ся в этом, открыв сбор­ку, нап­ример, в CFFexplorer. Одна­ко я спе­циаль­но сде­лал ого­вор­ку «если». Поп­робу­ем разоб­рать­ся, почему подоб­ное может не сра­ботать.


Для это­го пос­тара­юсь корот­ко в двух сло­вах напом­нить прин­цип фун­кци­они­рова­ния .NET-при­ложе­ния для тех, кто забыл мат­часть. Нес­мотря на то что сбор­ка сос­тоит из метадан­ных и кросс‑плат­формен­ного IL-кода, при выпол­нении при­ложе­ния он не интер­пре­тиру­ется, а ком­пилиру­ется в весь­ма опти­мизи­рован­ный натив­ный код целево­го про­цес­сора и целевой опе­раци­онной сис­темы. Дела­ется это непос­редс­твен­но при заг­рузке бло­ка кода один раз, впос­ледс­твии будет выпол­нять­ся уже ском­пилиро­ван­ный натив­ный код метода. Сам про­цесс называ­ется JIT-ком­пиляция (Just In Time, «вре­мен­ная ком­пиляция на лету»). То есть если прер­вать прог­рамму в про­изволь­ный момент в отладчи­ке типа x64dbg, то про­цесс будет оста­нов­лен имен­но во вре­мя исполне­ния такого вре­мен­но ском­пилиро­ван­ного натив­ного кода.


Трас­сировать, отла­живать и ревер­сировать его, конеч­но, мож­но, но целесо­образность это­го сом­нитель­на. Нас инте­ресу­ет дру­гой под­ход — пой­мать и сдам­пить уже вос­ста­нов­ленный фраг­мент IL-кода перед его JIT-ком­пиляци­ей. Логика под­ска­зыва­ет, что, если мы хотим сде­лать это вруч­ную, нам надо най­ти в отладчи­ке изна­чаль­ную точ­ку вхо­да в JIT-ком­пилятор. Самое прос­тое — отыс­кать метод SystemDomain::Execute в биб­лиоте­ке clr.dll (или mscorwks.dll для более ста­рых вер­сий .NET). Обыч­но для подоб­ных вещей рекомен­дуют исполь­зовать WinDbg и его рас­ширение SOS, но я для при­мера покажу, как это делать в x64dbg.


 

Ищем JIT-компилятор


Итак, заг­рузив нуж­ное при­ложе­ние в отладчик, мы с неп­рият­ным удив­лени­ем обна­ружи­ваем, что биб­лиоте­ка clr.dll отсутс­тву­ет в спис­ке отла­доч­ных сим­волов. Зна­чит, ее при­дет­ся заг­рузить допол­нитель­но, пред­варитель­но отыс­кав глу­боко в нед­рах под­катало­гов сис­темной пап­ки Windows. Най­дя и заг­рузив clr.dll (попут­но заг­рузит­ся нес­коль­ко биб­лиотек), мы сно­ва с раз­дра­жени­ем обна­ружим, что метод SystemDomain::Execute отсутс­тву­ет в пра­вом спис­ке экспор­та. Ну что ж, по счастью, x64dbg пре­дос­тавля­ет прек­расную воз­можность заг­рузить отла­доч­ные сим­волы пря­мо с май­кро­соф­тов­ско­го сер­вера — для это­го нуж­но щел­кнуть пра­вой кла­вишей мыши на clr.dll и выб­рать соот­ветс­тву­ющий пункт в кон­текс­тном меню.


По­дож­дав некото­рое вре­мя, мы уви­дим, что спи­сок в пра­вой час­ти окна отладчи­ка изрядно уве­личил­ся и иско­мый метод SystemDomain::Execute в нем уже при­сутс­тву­ет. Ста­вим на него точ­ку оста­нова и запус­каем прог­рамму. В момент оста­нова на этом методе дот­нетов­ские метадан­ные чаще все­го уже рас­шифро­ваны, рас­пакова­ны и их мож­но дам­пить в файл хоть MegaDumper’ом, хоть Scylla из самого дебаг­гера. Одна­ко это­го тоже может ока­зать­ся недос­таточ­но. Поп­робу­ем коп­нуть чуть глуб­же и вый­ти на исходный JIT-ком­пилятор.



Перейти обратно к новости