Отладка PHP кода защищен­ного упа­ков­щиком SourceGuardian

hacking tools logo

Как видишь, интер­пре­татор инс­трук­ций мож­но заг­рузить в отладчик и трас­сировать шитый код с его помощью. В нашем слу­чае он встро­ен в Apache HTTP Server, находя­щий­ся в модуле httpd.exe. Нель­зя прос­то так взять и запус­тить его из отладчи­ка. Более того, к его про­цес­су нель­зя даже при­соеди­нить­ся пря­мым спо­собом — он работа­ет в фоновом режиме и скрыт в спис­ке активных про­цес­сов для при­соеди­нения x64dbg. Но есть малень­кая хит­рость: если зай­ти в парамет­ры x64dbg и в самой даль­ней, обыч­но скры­той вклад­ке «Про­чее» выб­рать режим «Уста­новить x64dbg опе­ратив­ным отладчи­ком (JIT)», то мож­но отла­живать даже скры­тые про­цес­сы. Для это­го в окне дис­петче­ра задач жмем пра­вой кноп­кой мыши на Apache HTTP Server и выбира­ем «Отладка».

Итак, мы влез­ли в самое ядро вир­туаль­ной машины Zend. Во вклад­ке «Отла­доч­ные сим­волы» в спис­ке заг­ружен­ных биб­лиотек мы видим модуль php7ts.dll, в котором рас­полага­ется эта машина, и модуль ixed.7.2ts.win, реали­зующий ее рас­ширение защиты SourceGuardian. Мес­то внед­рения защиты выг­лядит так:

Здесь рас­шифро­ван­ный и рас­пакован­ный код из стро­ки‑аргу­мен­та уже забот­ливо пре­обра­зован в пос­ледова­тель­ность инс­трук­ций и подан на вход интер­пре­тато­ру, реали­зован­ному фун­кци­ей zend_execute.

Для удобс­тва нач­нем с того, что сос­тавим спи­сок команд нашей вир­туаль­ной машины. В интерне­те гуг­лится мно­го раз­ных вари­антов, силь­но и не очень отли­чающих­ся друг от дру­га, но нам нуж­на имен­но наша, кон­крет­но ском­пилиро­ван­ная для нас вер­сия. Для это­го мы откры­ваем дизас­сем­блер IDA и при помощи него ищем в биб­лиоте­ке php7ts.dll фун­кцию zend_get_opcode_name. Фун­кция сов­сем прос­тая — по опко­ду Zend-инс­трук­ции воз­вра­щает имя. Реали­зация ее тоже пре­дель­но прос­та, она все­го‑нав­сего берет имя из сле­дующе­го мас­сива строк (не буду при­водить весь спи­сок пол­ностью, каж­дый может лег­ко сге­нери­ровать его самос­тоятель­но):

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

Итак, спи­сок команд у нас есть, мож­но прис­тупить к отладке. Что­бы не возить­ся с обвязкой интер­пре­тато­ра, ста­вим точ­ку оста­нова пря­мо в основной цикл интер­пре­тато­ра шитого кода внут­ри фун­кции execute_ex:

Как вид­но из это­го фраг­мента, в дан­ной реали­зации интер­пре­татор устро­ен пре­дель­но прос­то. На вхо­де в RDI у нас ука­затель на струк­туру сле­дующе­го вида:

В этой струк­туре нам инте­рес­ны счет­чик команд opline и ука­затель на мас­сив кон­стант literals. Во вре­мя цик­ла исполне­ния счет­чик команд заг­ружен в регистр rbx, из него в регистр rax извле­кает­ся хэн­длер текущей инс­трук­ции, по которо­му и дела­ется вызов. При этом в коман­ду в регис­тре rcx в качес­тве парамет­ра переда­ется ука­затель на струк­туру _zend_execute_data.

Есть еще один полез­ный малодо­кумен­тирован­ный момент. В струк­туре zend_execute_data есть параметр func (в моем слу­чае по отно­ситель­ному сме­щению 0x18). На самом деле, он ука­зыва­ет на порож­дающую струк­туру

Порождающая структураПо­рож­дающая струк­тура

Как видишь, струк­тура содер­жит мно­го полез­ной информа­ции, к при­меру, c ее помощью мож­но ори­енти­ровать­ся, какой модуль исполня­ется в текущий момент. Но про­дол­жим про­цесс изу­чения нашего веб‑при­ложе­ния. Рас­смот­рим фраг­мент шитого кода по ука­зате­лю opline.
Фрагмент шитого кода по указателю oplineФраг­мент шитого кода по ука­зате­лю opline

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

Каж­дая запись занима­ет 0x20 байт, поэто­му на рисун­ке раз­биение на инс­трук­ции выг­лядит дос­таточ­но наг­лядно. Зеленым под­чер­кнут хэн­длер инс­трук­ции, то есть абсо­лют­ный 64-бит­ный адрес исполня­емо­го кода обра­бот­чика, крас­ным обве­дены опко­ды, а сбо­ку при­веде­на их рас­шифров­ка исхо­дя из выше­опи­сан­ной таб­лицы. Синим отме­чены дей­ству­ющие опе­ран­ды инс­трук­ции и их типы:

Су­дя по име­ни модуля и номерам строк, это откры­тый незашиф­рован­ный код, пред­шес­тву­ющий sg_load. Поп­робу­ем пот­рениро­вать­ся на нем в вос­ста­нов­лении логики прог­раммы, пос­коль­ку у нас есть воз­можность срав­нить его с исходным нес­компи­лиро­ван­ным тек­стом скрип­та. С име­нами инс­трук­ций мы уже разоб­рались, поп­робу­ем разоб­рать­ся с парамет­рами. Пер­вая инс­трук­ция ZEND_INIT_FCALL, вто­рой опе­ранд у нее — кон­стан­та (тип IS_CONST=1) име­ет зна­чение 0, суть его — сме­щение в таб­лице литера­лов, на которую ука­зыва­ет literals.
Таблица литераловТаб­лица литера­лов

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

Выг­лядит этот код страш­новато, но реаль­но струк­тура име­ет три поля: собс­твен­но value (оно в PHP7 64-бит­ное), 32-бит­ное type_info, пред­став­ляющее собой тип зна­чения, и третье 32-бит­ное поле широко­го при­мене­ния, которое в дан­ный момент нас не инте­ресу­ет.
Ти­пы зна­чений в нашем слу­чае быва­ют сле­дующи­ми:

В нашей таб­лице литера­лов содер­жатся в основном стро­ковые кон­стан­ты (IS_STRING=6) и длин­ные целочис­ленные (IS_LONG=4). Иско­мая кон­стан­та, на которую ука­зыва­ет наш опе­ранд, — стро­ковая, а зна­чит, 64-бит­ное value интер­пре­тиру­ется как ука­затель на zend_string. Обе­щаю, что это пос­ледняя внут­ренняя струк­тура, опи­сыва­емая в сегод­няшней статье. Если ты обра­тил вни­мание, она уже упо­мина­лась выше: это базовая струк­тура для хра­нения строк в дан­ной вир­туаль­ной машине — в час­тнос­ти, она слу­жит для хра­нения пол­ного име­ни скрип­та и исполня­емой фун­кции. Итак, ткнем­ся в отладчи­ке в эту ссыл­ку.
Продолжаем отладкуПро­дол­жаем отладку

На­конец‑то появи­лась какая‑то наг­лядность — вмес­то сле­пых цифр вид­ны осмыслен­ные стро­ки. Для пущего понима­ния сно­ва смот­рим опи­сание струк­туры:

Струк­тура пре­дель­но прос­тая: пер­вые 8 байт ее занима­ет объ­ект refcounted, его вве­ли недав­но для сис­темно­го сбор­щика мусора и для нас он инте­реса не пред­став­ляет. Так же, как и сле­дующие 8 байт — 64-бит­ный хеш. А вот даль­ше идет собс­твен­но стро­ка с 64-бит­ным счет­чиком: в нашем слу­чае это function_exists.

С кон­стан­тами разоб­рались, но как быть с перемен­ными? А с перемен­ными дело обсто­ит весь­ма сурово. Дело в том, что все типы перемен­ных, как ком­пилиро­ван­ных, так и вре­мен­ных, хра­нят­ся вo фрей­ме сте­ка пря­мо сле­дом за струк­турой zend_execute:

Спер­ва адре­суют­ся аргу­мен­ты фун­кции, затем ком­пилиру­емые перемен­ные, затем вре­мен­ные. Лег­ко уви­деть, что опе­ран­ды, соот­ветс­тву­ющие перемен­ным в инс­трук­ции, — суть сме­щения отно­ситель­но фрей­ма сте­ка. Беда зак­люча­ется в том, что в этой области, условно раз­мечен­ной под 16-бай­товые струк­туры zval, нет ука­заний, какой имен­но перемен­ной она при­над­лежит. При­мер­ное пред­став­ление об этом мож­но получить, вспом­нив, что в порож­дающей струк­туре zend_op_array есть поле vars (отме­чено на рисун­ке), которое пред­став­ляет собой ука­затель на мас­сив ука­зате­лей имен перемен­ных.

Ра­зуме­ется, это каса­ется толь­ко ком­пилиро­ван­ных перемен­ных CV (тип 0x10), ибо у вре­мен­ных перемен­ных типов 2 и 4 никаких имен нет. Соот­ветс­твен­но, эмпи­ричес­кий спо­соб получе­ния име­ни перемен­ной из опе­ран­да инс­трук­ции выг­лядит так: берем сме­щение (в слу­чае при­веден­ной на рисун­ке инс­трук­ции ZEND_ASSIGN пер­вый параметр 0x50), отни­маем от него раз­мер струк­туры zend_execute_data (в нашем слу­чае тоже 0x50), резуль­тат делим на раз­мер струк­туры zval (0x10). Получен­ное чис­ло исполь­зуем как индекс в таб­лице имен vars — перей­дя по соот­ветс­тву­ющей ссыл­ке, получа­ем имя нулевой ком­пилиру­емой перемен­ной «__v». Cоот­ветс­твен­но, опе­ранд 0x60 сле­дующей коман­ды ZEND_ASSIGN будет перемен­ной с индексом 1 в этой таб­лице и наз­вани­ем «__x» и так далее.

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

Смот­рим на то, как это выраже­ние выг­лядит в исходном скрип­те:

Бин­го! Путем титани­чес­ких уси­лий мы поч­ти вос­ста­нови­ли логику малень­кой час­ти уже извес­тной нам стро­ки кода. Одна­ко мы это сде­лали руками без вся­ких дам­перов, средс­тва­ми отладчи­ка x64dbg, попут­но получив пред­став­ление о фун­кци­они­рова­нии вир­туаль­ной машины PHP пря­мо внут­ри нее.

Ка­ковы наши даль­нейшие дей­ствия? Разуме­ется, пошаго­во тащить­ся по извес­тно­му нам коду доволь­но скуч­но, поэто­му мы вре­мен­но бло­киру­ем точ­ку оста­нова на execute_ex и уста­нав­лива­ем ее непос­редс­твен­но перед вызовом рас­шифро­ван­ного кода внут­ри модуля ixed.7.ts2.win. Как толь­ко оста­нов­ка нас­тупит, бло­киру­ем этот брек­поинт и сно­ва вклю­чаем точ­ку оста­нова внут­ри вир­туаль­ной машины. Теперь мы находим­ся в самом начале рас­шифро­ван­ного фраг­мента и видим все его инс­трук­ции, кон­стан­ты, перемен­ные. Мож­но сдам­пить это все на диск и написать пар­сер, мож­но мед­ленно, но вер­но тащить­ся пошаго­во до про­вер­ки нуж­ного нам усло­вия, все зависит от тво­ей усид­чивос­ти и кон­крет­ной задачи.

Поп­робую дать совет по опти­миза­ции даль­нейше­го про­цес­са. Метод прос­той и уни­вер­саль­ный для всех вир­туаль­ных машин — по сути, нас инте­ресу­ют толь­ко узло­вые момен­ты вет­вле­ния алго­рит­ма. Cта­вим точ­ки оста­нова имен­но в этих мес­тах, то есть на хэн­дле­рах инс­трук­ций условных перехо­дов ZEND_JMPZ, ZEND_JMPNZ, ZEND_JMPZNZ, ZEND_JMPZ_EX, ZEND_JMPNZ_EX, ZEND_CASE. Реаль­но оста­нав­ливать­ся в этих точ­ках не обя­затель­но, дос­таточ­но писать про­хож­дение дан­ной точ­ки в лог. Вдо­бавок неп­лохо бы писать в лог наз­вания вызыва­емых фун­кций, бла­го инс­трук­ций с уста­нов­кой их имен нем­ного: ZEND_INIT_FCALL_BY_NAME, ZEND_INIT_FCALL, ZEND_INIT_NS_FCALL_BY_NAME, ZEND_INIT_METHOD_CALL, ZEND_INIT_STATIC_METHOD_CALL, ZEND_INIT_USER_CALL, ZEND_INIT_DYNAMIC_CALL.

По­лучен­ные тре­ки мож­но ана­лизи­ровать на пред­мет мест для пат­ча на лету. Пос­коль­ку вир­туаль­ная машина не ком­пилиру­ет код, а исполня­ет его пошаго­во, пат­чер мож­но вешать непос­редс­твен­но на вход в execute_ex. А воз­можно, эта статья натол­кнет тебя на написа­ние сво­его собс­твен­ного уни­вер­саль­ного дам­пера или даже пря­мого анпа­кера закоди­рован­ных PHP? Ведь выше­опи­сан­ный спо­соб годит­ся для рас­паков­ки и отладки скрип­тов, защищен­ных не толь­ко SourceGuardian, но и дру­гими ана­логич­ными сис­темами защиты.

По­доб­ных сис­тем великое мно­жес­тво: сре­ди них — AROHA PHPencoder, BCompiler, ByteRun Protector for PHP, ByteScrambler, CNCrypto, CodeCanyon PHP Encoder, CodeLock, CodeTangler… и это лишь малая часть спис­ка. При­чем для боль­шинс­тва из них, нап­ример для того же SourceGuardian или небезыз­вес­тно­го ionCube, не сущес­тву­ет пуб­личных декоде­ров — лишь плат­ные онлайн‑сер­висы. Пос­мотрев на прайс подоб­ных сер­висов, мож­но убе­дить­ся в том, нас­коль­ко зна­ния матери­аль­ны.

ВКонтакте
OK
Telegram
WhatsApp
Viber

Один комментарий

  1. Сергей

    Так, а как в итоге-то получить из опкодов нормальный файл с кодом?) Не уже ли самому пилить сопостовитель опкода к php коду? Если да, то как это примерно сделать, чтобы не помереть от вечных тестов и багов от кривого своего кода, потому что не хватает знаний в этом…

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *