Се­год­ня мы раз­берем PyArmor — популяр­ный обфуска­тор для Python-при­ложе­ний. На при­мере демовер­сии реаль­ной прог­раммы с надо­едли­выми вотер­марка­ми мы выяс­ним, почему ста­рые методы деоб­фуска­ции боль­ше не работа­ют, поборем­ся с дву­хуров­невым AES-шиф­ровани­ем байт‑кода и най­дем спо­собы пат­ча кода пря­мо в памяти — без пол­ного вос­ста­нов­ления исходни­ков.

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

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

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

По тра­диции зап­рашива­ем у Detect It Easy его мне­ние о при­роде нашего при­ложе­ния и получа­ем ответ:

PE64
Operation system: Windows(Vista)[AMD64, 64-bit, GUI]
Linker: Microsoft Linker(14.50.35222)
Compiler: Microsoft Visual C/C (19.50.35222)[C]
Language: Python(3.10)
Tool: Microsoft Visual Studio(2026, 18.00)
Packer: PyInstaller[overlay]
(Heur)Packer: Generic[Section #5 (".rsrc") compressed Strange overlay]
Overlay: Binary(Store)[Offset=0x00049c00,Size=0x01720493]
Archive: Raw Deflate stream[@02h]
Data: ZLIB data[ZLIB compression best]

На пер­вый взгляд, перед нами клас­сичес­кий слу­чай исполь­зования пакета PyInstaller, завер­нутого в исполня­емый exe-модуль. Мы такие раз­бирали в статье «Зме­иная ана­томия. Вскры­ваем и пот­рошим PyInstaller». Если ты забыл или про­пус­тил эту статью, напом­ню, что для раз­бора подоб­ных пакетов на сос­тавля­ющие фай­лы я совето­вал при­менить ути­литу PyInstaller Extractor или луч­ше Pydumpck, который вдо­бавок пыта­ется по мере воз­можнос­ти деком­пилиро­вать .pyc-модули в исходный питонов­ский код.

Pydumpck (выдав попут­но мно­жес­тво пре­дуп­режде­ний) впол­не справ­ляет­ся с пос­тавлен­ной задачей, рас­паковы­вает из нашего exe-фай­ла целый каталог с раз­нооб­разны­ми биб­лиоте­ками.

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

ImportError: cannot import name '__pyarmor__' from 'pyarmor_runtime_000000' (unknown location)

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

from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n...')

На этом эта­пе уже оче­вид­но, что нуж­но уси­лен­но копать в сети на пред­мет инс­трук­ций по ревер­су это­го при­ложе­ния. И их дей­стви­тель­но великое мно­жес­тво! Как при­нято у молодых амби­циоз­ных хакеров, в основном это юту­бов­ские виде­оро­лики, раз­дра­жающие меня край­ним неудобс­твом подачи тех­ничес­кой информа­ции. Поэто­му выбира­ем в качес­тве базово­го источни­ка статью с «Гит­хаба», как самую тол­ковую, хотя и нес­коль­ко вин­тажную: четыре года прош­ло с тех пор, как про­ект перес­тал обновлять­ся. Она пред­лага­ет аж целых три метода вос­ста­нов­ления кода, обфусци­рован­ного PyArmor. Два из них пред­полага­ют дамп запущен­ного при­ложе­ния, тре­тий вро­де как поз­воля­ет вытащить код без запус­ка ана­лизи­руемой прог­раммы.

Од­нако, поп­робовав при­менить эти спо­собы на прак­тике, мы обна­ружи­ваем, что не все так шоколад­но, как казалось. Проб­лема воз­ника­ет сра­зу же: ути­лита Process Hacker 2, рекомен­дуемая авто­ром для инъ­екции кода в при­ложе­ние, за про­шед­шее вре­мя прев­ратилась в тык­ву под наз­вани­ем System Informer, при этом ее жес­токо кас­три­рова­ли, убрав имен­но тот инс­тру­мент, который нам был нужен, — Inject DLL. Разуме­ется, на тор­рентах еще остался исходный вари­ант это­го инс­тру­мен­та, но в ито­ге и он нам не помога­ет. То ли фор­мат инъ­екции PyInjector уста­рел (ему тоже пять лет), то ли PyArmor таки научил­ся с подоб­ными вещами бороть­ся, но при попыт­ке инъ­екции прог­рамма избавля­ется от внед­ренно­го PyInjector мак­сималь­но быс­тро, никако­го дам­па сде­лать не уда­ется.

Пер­вые два метода для нас не годят­ся, не годит­ся и тре­тий. Как выяс­няет­ся, автор слу­кавил, ска­зав, что запус­кать он ничего не будет: по фак­ту он все‑таки пыта­ется запус­тить счи­тан­ный из .pyc-фай­ла мар­шализи­рован­ный объ­ект через exec. Естес­твен­но, ничего у него не выходит, по при­чине того, что (как я уже говорил выше) модулю для нор­маль­ной заг­рузки не хва­тает внеш­ней фун­кции __pyarmor__. Судя по опи­санию, в ран­них вер­сиях она содер­жалась в модуле _pytransform.pyd, который вхо­дит в стан­дар­тный дис­три­бутив PyArmor и рас­паковы­вает­ся из пакета PyInstaller. Сей­час же она находит­ся в модуле pyarmor_runtime_000000/pyarmor_runtime.pyd, который, похоже, тоже сме­нил свой фор­мат и поэто­му работать вмес­то _pytransform.pyd не хочет.

Не под­ходят в качес­тве замены и вхо­дящие в акту­аль­ный дис­три­бутив PyInstaller модули pyarmor_runtime.pyd и pytransform3.pyd, которые мы тоже про­буем исполь­зовать для очис­тки совес­ти. Прос­мотр па­ры ро­ликов укрепля­ет наши опа­сения: судя по все­му, за про­шед­шую пятилет­ку PyArmor если не радикаль­но, то весь­ма серь­езно сме­нил свой алго­ритм, что­бы пред­лага­емые ранее методы его лечения не прос­то не работа­ли, но даже не были базой, от которой мож­но отталки­вать­ся для допили­вания деоб­фуска­тора.

По­это­му не теря­ем вре­мени на пог­ружение в чужую логику дам­па и деоб­фуска­ции (у нас, пов­торяю, цель дру­гая — ана­лиз и патч кон­крет­ного при­ложе­ния) и сле­дуем сво­им собс­твен­ным путем, начало которо­го я ука­зал в статье «Зме­иная ана­томия. Вскры­ваем и пот­рошим PyInstaller». Берем наш любимый отладчик x64dbg, атта­чим­ся им к запущен­ному про­цес­су и пре­рыва­ем его (бла­го прог­рамма любез­но пре­дос­тавля­ет нам модаль­ный MessageBox для удобс­тва это­го дей­ствия). Про­ана­лизи­руем стек воз­вра­тов.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

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

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

    Подписаться

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