Содержание
Введение
В первом квартале 2024 года специалисты экспертного центра безопасности Positive Technologies (PT Expert Security Center, PT ESC) обнаружили серию атак, направленных на государственные структуры России, Беларуси, Казахстана, Узбекистана, Кыргызстана, Таджикистана, Армении. Связей с уже известными группировками, использующими такие же техники, нам установить не удалось. Основной целью атаки была кража учетных записей от различных сервисов с компьютеров работников государственных структур. Эту группировку мы назвали Lazy Koala из-за простых техник и имени пользователя, который управлял телеграм-ботами с украденными данными. ВПО, которое использовала группа для своих атак, мы назвали LazyStealer из-за простоты реализации, но в тоже время атаки с его использованием оказались продуктивными. Точный вектор заражения нам установить не удалось, однако по всем признакам это был фишинг. Все жертвы были напрямую уведомлены нами о компрометации.
Анализ LazyStealer
Все встреченные нами образцы в качестве упаковщика используют PyInstaller, после снятия которого в главном скрипте весь код накрыт протектором Pyarmor.
Для снятия протектора нам понадобится скрипт bypass.py, установленный модуль Pyarmor, а также файл pytransform.py из этого модуля. Этот файл нужно разместить в той же папке, что и целевой файл с расширением .ру. После снятия протектора в одном из вариантов образцов мы встретили скрипт, все, что он делает, — это занимается подключением Python-модулей.
Выделенные на рисунке модули, которые в файловой системе имеют названия hello.cp39-win_amd64.pyd и pdfbyte.cp39-win_amd64.pyd, являются библиотеками DLL, собранными с помощью Cython. Они будут запущены при их импорте. При компиляции через Cython от первоначального скрипта сохраняются только строковые и числовые константы, а вся логика (например, циклы, операции присвоения, передача аргументов) будет реализована нативно, поэтому получить исходный скрипт не представляется возможным (как в случае с PyInstaller или Pyarmor), но его можно воссоздать близким к оригиналу.
Для начала возьмем pdfbyte.cp39-win_amd64.pyd. Вся логика будет происходить в единственной экспортируемой функции PyInit_pdfbyte. В этом случае постфикс pdfbyte обозначает имя модуля, скомпилированного из pdfbyte.pyx. В зависимости от названия модуля меняться будет и постфикс имени экспортируемой функции.
В PyInit_pdfbyte происходит вызов функции PyModuleDef_Init с аргументом pyx_moduledef, который является структурой определения модуля, содержащего всю информацию, необходимую для создания объекта модуля. Обычно для каждого модуля существует только одна статически инициализированная переменная этого типа.
Аргумент pyx_moduledef будет иметь структуру, представленную ниже, где нас интересует поле -m_slots. Для того чтобы найти функцию, которая отвечает за инициализацию и исполнение кода скрипта, нужно перейти по адресу этого поля.
struct PyModuleDef { PyModuleDef_Base m_base; const char* m_name; const char* m_doc; __int64 m_size; PyMethodDef* m_methods; PyModuleDef_Slot* m_slots; int(__cdecl* m_traverse)(_object*, int(__cdecl*)(_object*, void*), void*); int(__cdecl* m_clear)(_object*); void(__cdecl* m_free)(void*); };
После перехода мы обнаружим массив структуры с PyModuleDef_Slot с двумя функциями.
Структура PyModuleDef_Slot представлена ниже, нас интересует поле value.
struct PyModuleDef_Slot { __int64 slot; void* value; };
Под нулевым индексом массива __pyx_moduledef_slots поле value — функция _pyx_py_mode_create, которая является конструктором модуля. А уже под первым индексом —_pyx_pymod_exec_pdf, которая и является искомой нами функцией. В ней с самого начала происходит инициализация служебных полей _pyx_mstate_global.
Глобальная переменная _pyx_mstate_global является менеджером всех объектов и имеет структуру, представленную ниже, где интересующие нас поля будут начинаться с индекса 6, обозначенными в структуре виде массива n полей _pyx_object.
struct __pyx_mstate { _object* __pyx_d; _object* __pyx_b; _object* __pyx_cython_runtime; _object* __pyx_empty_tuple; _object* __pyx_empty_bytes; _object* __pyx_empty_unicode; _object* __pyx_object[n]; };
Дело в том, что только первые шесть объектов являются постоянными, так как несут служебную информацию для всего модуля, а начиная с седьмого поля варьируются по количеству. Число этих объектов зависит, например, от количества строк, значений, наличия вызываемых функций.
Пример инициализации строковых констант в функции Pyx_CreateStringTabAndInitStrings представлен ниже.
В структуре Pyx_CreateStringTabAndInitStrings, которая представлена ниже, нас интересует поле s — имя строковой константы, которая может быть названием переменной, модуля, метода из модуля, значения строки.
struct __Pyx_StringTabEntry { _object** p; const char* s; const __int64 n; const char* encoding; const char is_unicode; const char is_str; const char intern; };
Пример подключения модулей.
Пример присвоения значения переменной.
Пример вызова метода из подключенного модуля с аргументом.
Зная все это, мы можем воспроизвести скрипт.
Выполнения этого кода приведет к открытию документа в браузере. Набор из четырех разных документов продемонстрирован ниже.
Далее мы рассмотрим нативные конструкции в hello.cp39-win_amd64.pyd, которых не было в pdfbyte.cp39-win_amd64.pyd ввиду простоты и краткости исходного скрипта.
В случае если в исходном скрипте были функции, то инициализация констант для них будет происходить в _Pyx_InitCachedConstants, часть кода которой продемонстрирована ниже.
Другая часть информации о функциях будет находиться последовательно в секции данных.
Структура функции представлена ниже, нас интересуют поля:
- ml_name — имя функции;
- ml_meth — нативная реализация функции.
struct PyMethodDef { const char* ml_name; _object* (__cdecl* ml_meth)(_object*, _object*); int ml_flags; const char* ml_doc; };
Перед вызовом любой функции ее нужно инициализировать, используя информацию, описанную выше.
Имея все это, мы можем воспроизвести скрипт.
Функция этого скрипта заключается в краже логинов и паролей из Google Chrome и в дальнейшей пересылке их телеграм-боту.
Кроме того, был образец, в котором только логика показа документа была написана на Cython, а логика кражи паролей — на чистом Python. Различия заключаются в том, что переменные и функции называются по-разному, а также есть незначительные изменения в структуре программы.
Нам не удалось обнаружить механизм закрепления этого стилера. Это может говорить о том, что он является частью цепочки атаки или что он создан, чтобы не оставлять следов, однако это делает атаку одноразовой.
Атрибуция
Все найденные боты связаны с одним пользователем, который управляет этими ботами и, вероятнее всего, является их создателем. Граф связей между пользователем и ботами представлен ниже.
Зная географическое расположение жертв, а также используемый группировкой Lazy Koala арсенал, можно предположить, что она связана с группировкой YoroTrooper, которая использовала схожие техники и инструменты. Однако прямых пересечений нам обнаружить не удалось.
Жертвы
Жертвами группировки Lazy Koala стали государственные, финансовые, медицинские, образовательные отрасли России, Беларуси, Казахстана, Таджикистана, Кыргызстана, Армении и Узбекистана. Общее количество скомпрометированных учетных записей на момент обнаружения составило 867, из них уникальных — 321. Все жертвы были напрямую уведомлены нами о компрометации.
Вывод
Группировка Lazy Koala демонстрирует, что для успешных атак необязательно использовать сложные инструменты, тактики и техники. Сложно не значит лучше. Достаточно лишь убедить жертву в том, чтобы она запустила нужный файл. Основной их инструмент — примитивный стилер, защита которого позволяет избежать обнаружения, замедлить анализ, получить и отправить все украденные данные во все также набирающий год за годом популярность среди злоумышленников Телеграм. Судьба украденных данных — перепродажа или использование их для дальнейших атак уже на внутренние структуры компаний.
Автор: Владислав Лунин
Индикаторы компрометации
ФАЙЛ | MD5 | SHA-1 | SHA-256 |
33ms.exe | 4f060c5c6813e269f01e6cba1d3ac4cd | 4f0a1831d4d8c09f46e8f5fbe8b17b024daa6eee | 9fd197b7402285ed2a75dac9a5ce3ef499a58342fd0dcefe1c40443a12bc6832 |
Recommendation.exe | 641932b66490630005dde2aef405e5e9 | 9bad63eab92144b8a365428aa68531c80fc2da0f | e419a8158c6fe326dc7ab16dbd5f3b2723dffe8c9561fe835bb16f62a8fa61f5 |
05-1254_Minzrav.exe | 882d63c5ff749f232a3ce70a36c95b83 | cd1f89f3d56df6a775d8694c1cbf588961dc7f06 | a6e68f3066424daae4a54b2e0b01a4474a9a381469ae69daae6fef9a1626fa6d |
- | fe245cf57be8b3daf8cdb3882de99f35 | 40789ef406772e52a0dfc86509cc7617fa8b54a3 | 1db3d0ac68515b5c9876634605ba8492ba558f7df435bff2b20a74239107f3ec |
test.pyc | 8e233b0250d85ae63076af45ee829c55 | ec14cf28fe8764d4f285b95ee7001af49ff0af68 | 5ecdf5efe2a74db93450f2b35e942b91ee6dd1b0f545c04810d2794b748b1dea |
test.pyc | 032a586d08e7f31e2aedbec61d5d0f62 | 51ad91409698d8f4017defbd0a382cce9e69ed6f | 9fc75a6a17238ec3833dce0605b334c03fd84363f56313a5bf58d57ff286a9f9 |
1.pyc | 8cb819b48958540fac07244188508156 | 1f204cfb02df849f935c296a5e4b2f120bfa563b | 7d3733513e0645e66009e3d677af76653baa75c8ddf0d126aa0f270b56183272 |
test.pyc | 2d51a6620c976e1d736448082338e0b1 | 755ade0ddaceaabe9577d22a240e0430375f502f | 216f4e858f84269bee999fdc29dafbd79ec2270575e19a8626e25d5fe72a8f25 |
hello.cp39-win_amd64.pyd | 763eb39787756744b4062336eb945750 | 7685dd23d64fa94bb8d2d54dd2e104fbe5379ec5 | 8246e66ff043374477c06a612602f6e8a2cb487a33d8b046357a6c4870648ed1 |
hello.cp39-win_amd64.pyd | 5b84b516760773c538647bc6e4d26d37 | 3e497222f9bc13d43d6a3e5fbdcae3474b3d2d22 | ef6fb63259eac9f7642e468726a042f5a29576bf9f846b96fa6ded8bf145b64c |
hello.cp39-win_amd64.pyd | 1dedf5772ea1126b79b5e22ca10cefd3 | 140968b7004aca9785a0a1f0a6712322db22fd6c | f2a8088f1a634e62a2d0e5b2d6427d67fae640bf03dd04c8571006e1f31d7992 |
pdfbyte.cp39-win_amd64.pyd | 0f5727bada96b3b62573bba51538e9e3 | 6f54d068423cee9b2cf5ef50b4348025f983e220 | bfa3718f6492dd337c127ccdbd8033b503ca089699ddbff3ac5c45f5f95f01e8 |
pdfbyte.cp39-win_amd64.pyd | c3242bce783d5fa0ab0ce645f1283c64 | 845be44fb0d663636e500187d7d394714e562e08 | 1549114ea6d86198d29f79a009218ca991aa17d215a84b90e3c91ef3268180e4 |
pdfbyte.cp39-win_amd64.pyd | 1cff5f65c85d8cf614beedf8fd5112d7 | c10637e35dfe326bd2c9a92f432d483f2f7591bd | 864a38b028d5b9e41fa0d4eee7cfa3a284d0ab9874b42cc4d50f1e2b2e26e1e5 |
docpdf.cp39-win_amd64.pyd | 98914403f428abeea89c94e0b7edaaa9 | 9866dfedbd311ed2f838ec56947cdf4ccabe8634 | 18e00bb5dee23815a89067258b11ef13d6327bcb3555d70596c906d4875ed8c2 |
Тактики и техники по матрице MITRE ATT&CK
ID | Имя | Описание |
---|---|---|
Execution |
||
T1204.002 | User Execution: Malicious File | Группировка Lazy Koala выдает исполняемый файл за документ |
Defense Evasion |
||
T1140 | Deobfuscate/Decode Files or Information | Группировка Lazy Koala использует упаковщик PyInstaller, протектор Pyarmor и компилятор Cython |
Credential Access |
||
T1555.003 | Credentials from Password Stores: Credentials from Web Browsers | Группировка Lazy Koala получает учетные записи из браузера Google Chrome |
Exfiltration |
||
T1567 | Exfiltration Over Web Service | Группировка Lazy Koala отправляет полученные учетные записи телеграм-боту |
Вердикты продуктов Positive Technologies
PT Sandbox
Suspicious:
- Read.Window.Handle.Enumeration
- Create.Process.Taskkill.TerminateProcess
- Read.Thread.Info.AntiDebug, Write.Thread.Info.AntiDebug
- Read.File.Browser.Credentials
Malware:
- Trojan-Spy.Win32.LazyStealer.a
- Trojan_PSW.Win32.Generic.a
- Trojan.Win32.Generic.a
MaxPatrol SIEM
Run_Masquerading_Executable_File
Suspicious_Connection
Credential_Access_to_Passwords_Storage
PT NAD
tls.server_name == "api.telegram.org"