Нетипичная змея. Реверсим приложение на Python c кастомным интерпретатором

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

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

warning

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

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

То, что это клас­сичес­кое Python-при­ложе­ние, вид­но с пер­вого взгля­да, даже без запус­ка Detect It Easy.

Пос­коль­ку рядом с исполня­емым модулем лежат питонов­ские биб­лиоте­ки, а под­катало­ги запол­нены фай­лами с рас­ширени­ем .pyc, никаких сом­нений в исполь­зовании Python не оста­ется. Что это за фай­лы и с чем их едят, я под­робно рас­ска­зывал в статье «Зме­иная ана­томия. Вскры­ваем и пот­рошим PyInstaller». В двух сло­вах пояс­ню для тех, кто не читал эту пуб­ликацию: упо­мяну­тые бинар­ные фай­лы содер­жат пре­ком­пилиро­ван­ный байт‑код, подава­емый непос­редс­твен­но на вход интер­пре­тато­ра для уско­рения обра­бот­ки. В этой же статье при­веде­ны и извес­тные деком­пилято­ры и дизас­сем­бле­ры фай­лов подоб­ного фор­мата (uncompyle6, pycdc, pydasm…).

На этой опти­мис­тичной ноте мож­но было бы и закон­чить статью, но нет. Нес­мотря на валид­ный заголо­вок .pyc-фай­ла с сиг­натурой вер­сии 3.10 (выделе­но крас­ным), ни один из стан­дар­тных деком­пилято­ров или дизас­сем­бле­ров не ест такие фай­лы, выдавая ошиб­ку типа CreateObject: Got unsupported type 0x5D. При прос­мотре фай­ла в HEX-редак­торе мы видим, что он весь, собс­твен­но, и сос­тоит из явно зашиф­рован­ного объ­екта нес­тандар­тно­го типа (выделе­но жел­тым, 0xDD & 0x7F = 0x5D).

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

По­искав в натив­ных биб­лиоте­ках явный фраг­мент такого шаб­лона (нап­ример, 25 02 39 04), мы тут же натыка­емся в фай­ле python311.dll на шаб­лон (выделе­но крас­ным) и код, рас­шифро­выва­ющий дан­ные сра­зу пос­ле чте­ния из фай­ла.

Чуть поиг­равшись с отладчи­ком x64dbg, мы обна­ружи­ваем и мес­то вызова это­го кода: Py_Main -> PyRun_SimpleFileObject -> PyMarshal_ReadLastObjectFromFile, начиная с которо­го в отладчи­ке мож­но сле­дить за рас­шифров­кой и интер­пре­таци­ей байт‑кода.

Поп­робу­ем для начала добить­ся какой‑то более‑менее понят­ной деком­пиляции .pyc-фай­лов. Для это­го напишем прос­тень­кий рас­шифров­щик, рас­ксо­рива­ющий объ­ект 0x5D получен­ной мас­кой. Бла­года­ря это­му исходный файл уда­ется при­вес­ти в гораз­до более чита­емый вид.

К сожале­нию, этот код, хоть и силь­но похож на нас­тоящий, все рав­но не работа­ет. На этот раз вылеза­ет ошиб­ка CreateObject: Got unsupported type 0x0. У меня воз­никло силь­ное подоз­рение на нес­тандар­тный мар­шалинг объ­ектов внут­ри .pyc-фай­ла.

Источник: xakep.ru

Ответить

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