
Привет, Хабр!
С недавних пор в качестве пет-проекта я разрабатываю Lagoscope — программу, которая помогает заметить и определить причину лагов во время игры. Идея простая: собрать телеметрию за игровую сессию с минимальным оверхедом по ресурсам, обнаружить проблемные места и связать с тем, что происходило с системой рядом по времени. Для реализации этой схемы проект разделен на 2 части:
Клиент — тонкое Windows-приложение, запускается на компьютере пользователя, собирает телеметрию из различных источников (тут стоит добавить, что информацию берем только из того, что открыто предоставляет нам система, не лезем в процесс игры и тем самым не вызываем подозрений у различного рода античитов) и отправляет анонимные данные на сервер, создавая минимум фоновой нагрузки.
Сервер — крутится где-то на удаленном компьютере и выполняет «тяжелую» часть работы: получает сырые данные от клиента, анализирует их, ищет проблемные моменты, сопоставляет их с потенциальными причинами и формирует подробный отчет.
Небольшая ремарка для понимания некоторых решений: так как клиент скачивается из интернета и не имеет подписи издателя (это стоит денег, а проект, вероятно, никому, кроме меня, не будет нужен), то хотелось при выпуске каждой новой версии не начинать копить репутацию Windows SmartScreen с нуля. А если подписи нет, репутация копится, по сути, на хеше запускаемого файла, поэтому было решено использовать широко распространенную схему, которой пользуются, например, Chrome или Steam: пользователь качает с сайта некий «загрузчик» — его хеш стараемся держать постоянным, — а он, в свою очередь, скачивает и устанавливает актуальную версию клиента.
Понимая, что пользователь не должен слепо доверять рандомному .exe из интернета без подписи и репутации, я сразу решил встроить в пайплайн сборки клиента автоматическую проверку на VirusTotal: каждая новая версия билдилась, упаковывалась в установщик с помощью Inno Setup и отправлялась в VT. ПО требует прав администратора, работает в фоне и что-то отправляет в интернет — срабатывания были неизбежны, поэтому я установил порог в 5 детектов из ~70 доступных проверок: если он не превышен, то новая версия клиента публикуется для скачивания, уже работающие клиенты получают ее с автообновлением, а на сайте появляется ссылка на отчет сканирования.
В общем, я все это собрал, опубликовал и рассказал о проекте на DTF как на наиболее подходящем по тематике ресурсе. Конечно же, в комментариях возникли сомнения в безопасности установки клиента, поэтому я написал вторую статью. В ней кратко изложил то же, что описано выше, расписал устройство клиента и пообещал сделать разбор отчета VirusTotal, чтобы снять как можно больше сомнений.
Еще одна ремарка по поводу устройства клиента: про историю с загрузчиком я вам уже рассказал, далее он скачивает установщик актуальной версии, а тот, в свою очередь, распаковывает все необходимые компоненты, их 4:
Непосредственно клиент, который выполняет большую часть работы.
PresentMon — утилита от Intel, распространяемая через GitHub. Нужна для получения счетчика кадров, оттуда frame time и FPS, а оттуда уже детект лагов в игре. Версия фиксирована, хеш проверяемый.
Два хелпера — DPC sidecar и ADLX helper — нужны для работы с драйверами и задержками.
Подробнее можно прочитать в оригинальной статье Поговорим о безопасности Lagoscope.
Поворот не туда
В общем, я сел писать обещанный разбор отчета и обнаружил два неприятных момента:
Следите за руками: пользователь качает с сайта загрузчик, а на проверку уходит установщик — это 2 разных приложения с разными хешами. И получается, что и по загрузчику нет репорта, и хеш установщика заранее не проверить.
Как я уже сказал ранее, установщик собирался с помощью Inno Setup с флагом компрессии, и получилось, что проверяемая часть — стандартный стартовый модуль Inno Setup: 10 секций из 11 совпадают с официальным
SetupLdr.e32. А оставшиеся 74,2% файла — собственно оверлей, полезная нагрузка клиента, но она сжата, энтропия 7,99992, и там может быть вообще что угодно.
Посмотрел я на это и принял решение переделать релизный флоу. Сказано — сделано, и теперь в VT на проверку отправляются по отдельности:
Загрузчик — то, что пользователь качает с сайта и может сравнить хеш.
Установщик — то, что качает загрузчик. Установщик также доступен по прямой ссылке (вроде
lagoscope.app/releases/0.0.12/Lagoscope-Setup-0.0.12.exe), так что при желании его можно скачать без загрузчика и сверить хеш с отчетом до запуска. Кроме того, теперь он собирается без компрессии, а значит, заплатив объемом, мы получили открытость, и теперь можно заглянуть внутрь.Сам клиент Lagoscope — то, что запускает пользователь.
Два хелпера, которые поставляются в комплекте и запускаются в фоне при необходимости.
PresentMon я сканировать не стал — это сторонняя утилита от известной компании из открытого источника.

И по каждому из этих компонентов публикуется отчет.

Теперь вводная часть, наконец, заканчивается, и мы можем перейти к тому, ради чего писалась эта статья: разберем один из отчетов VT максимально подробно, чтобы понять, что мы там можем увидеть и на что обратить внимание. В качестве препарируемого я выбрал исполняемый файл клиента Lagoscope — он является главным героем всего приключения, ходит в сеть, собирает метрики и не содержит в себе сторонних программ, как, например, установщик и Inno Setup, но в целом то, что описано в статье, применимо к любому отчету. Разбор получился достаточно длинным и с большим количеством технических деталей, именно поэтому я принес его на Хабр, а не на DTF. Теперь давайте посмотрим, что там у нас в отчете.
Кто ты такой?

Первое, что нужно проверить, — что мы смотрим отчет именно о том, о чем хотели. Предположим, что клиент уже скачан и установлен, и в папке программы у вас уже есть lagoscope-client.exe версии 0.0.12. В таком случае нам нужно выполнить команду:
> Get-FileHash lagoscope-client.exe
Она выведет нам SHA-256 хеш файла, и он должен совпасть с тем, что мы видим в отчете. В случае с данной версией это 662c4135c48df8440b1e4ed9227330f06e366825299fa5ea723698b68179392d. После сверки мы уже точно уверены, что отчет принадлежит проверяемому файлу и мы можем ему доверять.
Еще в шапке отчета есть поле Last Analysis Date с датой последней проверки. VT периодически может перепроверять файлы, отчего результат проверки может меняться. В данной статье описаны результаты, актуальные на момент написания статьи.
Detection — срабатывания антивирусов
Вкладка Detection — результат проверки файла различными антивирусами. Список может меняться от файла к файлу, во-первых, потому что не все антивирусы могут быть предназначены для проверки данного типа файлов, а во-вторых, проверка может не пройти по техническим причинам, например, таймаут. В любом случае нас это не касается, работаем с тем, что есть. В нашем случае мы видим 2 срабатывания. Для начала смотрим, что за антивирусы нас заподозрили. Сейчас это:
Gridinsoft (GridinSoft LLC) — украинская компания из Киева, делает антивирус Gridinsoft Anti-Malware. На VirusTotal она с октября 2020 года.
Trapmine — эстонская компания из Таллина. Продукт — защита рабочих станций (EDR/EPP), по описанию построенная на машинном обучении.
В списке есть крупные игроки с огромным кредитом доверия, но они посчитали наш файл безопасным. Сработали 2 продукта небольших компаний. Мы, конечно же, не списываем их со счетов, но это позволяет относиться к результатам с некоторой долей скепсиса.
Далее мы смотрим, в чем нас подозревают:
Trojan.Heur!.02016023Malicious.moderate.ml.score
Ни одна метка не называет конкретный вирус: обе — эвристика, то есть догадка по признакам файла, а не совпадение с базой известных угроз. В первом случае нам прямо написали Heur и номер, который наверняка обозначает правило, по которому произошел детект. Во втором случае в метке мы имеем ml.score, что означает, что какая-то ML-модель насчитала достаточно попугаев, чтобы посчитать файл опасным.
Мы знаем, что наша программа что-то в фоне собирает на компьютере и отправляет куда-то в интернет. Похоже на поведение трояна? Похоже. Так что срабатывания можем считать справедливыми, но ложными.
Details — что внутри
Эта вкладка подробно описывает, что у нашего исполняемого файла под капотом. Здесь очень много информации, разберем самые интересные и самые полезные для нас моменты.
Basic properties
Набор свойств, хешей и фактов о нашем exe. Из интересного разве что:
TrID: Rust Windows 64bit Executable (97%) Win64 Executable (generic) (1%) Win16 NE executable (generic) ...
DetectItEasy: PE64 Compiler: Rust (x86_64-pc-windows-msvc) Compiler: Microsoft Visual C/C++ (19.36.36260) [C] Linker: Microsoft Linker (14.36.36260)...
В основном Rust, немного приправленный линкером Microsoft. Все верно.
History
Тут глаз цепляется за тот факт, что наш файл, оказывается, был создан в далеком будущем.
Creation Time 2102-08-07 19:41:41 UTC
Но на самом деле, тут тоже нет ничего странного. Если бы при компиляции использовалось реальное время создания файла, то хеш менялся бы при каждой пересборке. Чтобы этого избежать, существует специальный флаг линкера /Brepro. При его использовании в поле времени создания кладется стабильное значение, основанное на содержимом файла, а не на времени.
Sections
Exe-файл делится на секции — блоки с разным содержимым: в .text лежит код, в .rdata и .data — данные, в .rsrc — значки и прочие ресурсы. Windows оперирует каждой секцией по-разному, например, код можно выполнять, данные — только читать или записывать.

Здесь можно обратить внимание на колонку Entropy. Энтропия — мера «случайности» байтов от 0 до 8: чем ближе к 8, тем больше блок похож на архив или шифр. У обычного исполняемого кода она держится в районе 6. Помните значение из начала статьи — 7,99992? Оно означало, что данные выглядят как абсолютно случайный набор, а это подозрительно.
Тут же мы видим, что в целом они не превышают значение 6,44, что вполне себе естественно. Есть одно исключение — .rsrc — 7,4, но это секция с ресурсами, там лежат иконки в PNG, а это формат со сжатием, так что это нормально.
В остальном ничего подозрительного: странных имен, пустых или, наоборот, неестественно огромных секций нет.
Imports
Здесь можно увидеть, какие функции и из каких библиотек импортирует клиент. По ним можно сделать вывод о логике того, что происходит у него под капотом. Перечислять все смысла нет, в целом там все нужное для нормальной работы клиента:
| Список процессов и нагрузка от них. |
| Список окон в панели задач для ручного выбора игры. Читаются только владелец окна и заголовок |
| Шифрование для токена устройства |
| Сеть и TLS для отправки данных |
| пинг, Wi-Fi, USB, счётчики |
Также тут хочется заметить, чего в импортах нет: WriteProcessMemory, ReadProcessMemory, CreateRemoteThread, VirtualAllocEx, SetWindowsHookEx, GetAsyncKeyState — клиент не лезет внутрь ваших процессов, не следит за клавиатурой и в целом ведет себя хорошо.
Contained Resources by Type
Ресурсы: 10 иконок, 2 группы и манифест. Все. Больше в файле ничего не спрятано.
Relations
Здесь находятся связи проверяемого клиента Lagoscope с другими объектами в базе VirusTotal — какие адреса, домены и IP он открывал при запуске в песочнице, из каких частей состоит, какие файлы создал.
Contacted URLs, Contacted Domains, Contacted IP addresses
Все 3 эндпоинта, которые вызывал клиент, принадлежат домену lagoscope.app. По ним клиент запрашивает с сервера текущую конфигурацию (сведения об обновлении и актуальной версии, доступные фичи и т. д.), регистрирует устройство по токену и передает данные. У всех трех адресов 0 срабатываний из 93 — в сомнительных делах они замечены не были. Могут вызвать сомнения коды ответов 401 и 404, но это объясняется тем, что клиент работает на вебсокетах, а сканер проверял их обычным HTTPS-запросом. Для /ingest еще и нужен токен, так что он отдает 401.
В секции про Domains мы видим домен и сабдомен lagoscope.app и atm.teams.mira.tm.svc.cloud.microsoft — он не имеет отношения к клиенту, скорее всего, какой-то фон самой песочницы.
Ниже список конкретных IP-адресов. Аналогично: один IP относится к серверу Lagoscope, все остальное — подсеть Microsoft.
Bundled Files, PE Resource Children
По сути все то же самое, что мы видели на предыдущей вкладке в разделе Sections, — части самого exe, код, данные, манифест. Из интересного: секцию .fptable VirusTotal определил как ISO-образ, хотя она весит 512 байт и целиком состоит из нулей.
Behavior
VirusTotal запускает проверяемый файл в нескольких изолированных песочницах и смотрит, как он себя ведет. На этой вкладке собраны отчеты о поведении программы, подозрительные паттерны и прочее.
В данном случае мы видим, что программу запускали в 4 песочницах, и при этом мы имеем по 0 в Detections, IDS Rules (правила сетевого трафика), Sigma rules (правила системных событий). Зато у нас есть Mitre Signatures. MITRE — это американская некоммерческая организация, которая ведёт известные базы по кибербезопасности. В отчёте идет речь о её базе — MITRE ATT&CK: открытом каталоге приёмов, которыми пользуются злоумышленники. Список довольно большой, я хотел разобрать из него только Medium, но тут у меня возникли вопросы, на которые я не смог ответить.

Дело в том, что из 4 medium signatures, о которых написано в саммари, в детальной таблице с фильтром по Medium есть только 2 техники, при этом одна является подмножеством другой. Итак, нас обвиняют в Timestomp (Indicator Removal). Timestomp — подмена дат файла (создания, изменения). Злодеи так прячут свои файлы среди системных, чтобы по дате было сложнее отличить. Но в коде клиента нет ни одного места, где менялись бы даты файлов. Зато есть импорт SetFileTime, и приехал он из стандартной библиотеки Rust.
Скрытый текст
В Rust 1.99 функция открытия файла File::open после CreateFileW вызывает SetFileTime, но только если при открытии задан флаг «не обновлять дату последнего доступа или записи». Эти флаги экспериментальные, а клиент собирается стабильным Rust 1.99 и включить их не может. В коде клиента их тоже нет. Но параметры открытия файла хранятся в обычных полях структуры, поэтому компилятор не может доказать, что флаги всегда выключены. Ветка с SetFileTime остаётся в файле, а функция — в импортах.
В блоке про сеть ничего нового нет, все это мы уже видели ранее. А вот в разделе Memory Pattern Domains есть пара моментов. Здесь перечислены домены, которые сканер обнаружил в памяти процесса, а не те, которые он вызывал. Кроме уже привычного lagoscope.app мы можем видеть:
*.lencr.org— служебные адреса из сертификата Let's Encrypt. Они попадают в память клиента при установке TLS-соединения.basement.studio— тут я сначала очень удивился, а потом понял. В клиенте используется шрифтGeist, а в его лицензии написано «Vercel, in collaboration with basement.studio», в общем, это метаданные шрифта.http://127.0.0.1— в клиенте есть небольшой сервер для работы с GSI — это когда игра сама сообщает о том, что происходит внутри нее. Например, такой функционал есть в Dota 2 и включается флагом-gamestateintegration. Для Lagoscope эта информация полезна тем, что можно сопоставить просадки FPS с какими-то внутриигровыми событиями, например ожесточенными сражениями с кучей эффектов или внутриигровой стадией и точным временем. На мой взгляд это очень крутой функционал, планирую развивать клиент в эту сторону и расширять список поддерживаемых игр.
В разделе действий с файлами видно, что клиент читал и куда писал. А читал он только файлы из своей собственной папки и файлы библиотек. Писал только в свою папку. Никаких подозрительных или опасных файлов клиент на диск не пишет — только токен устройства, конфигурацию с сервера и буферы перед отправкой на сервер. В списке удаленных файлов можно заметить файлы .tmp — это обычная безопасная запись: сначала пишем во временный файл, затем переименовываем. Также есть интересный раздел Files Dropped — это то, что программа оставила в системе после себя. Так можно определить загрузчики всякого вредоноса, которые скачивают и оставляют в системе исполняемые файлы. В нашем же случае мы здесь видим только данные приложения.
В реестре открыто 204 ключа, а записано — ни одного. Но из них по коду клиент читает только HKCU\Software\Valve\Steam, чтобы найти игру в библиотеке Steam. Все остальное читает сама Windows для запуска клиента: кодеки для изображений, сеть, отрисовка, проверки совместимости и т. д.
Дочерних процессов подопытный не создавал. Справедливости ради стоит отметить, что в реальности дочерний процесс был бы запущен — та самая утилита PresentMon для подсчета кадров. Но так как в песочнице ее рядом не было, запускать было нечего. Кроме того, есть связанные запуски wmiprvse.exe и wmiadap.exe — это системные процессы Windows Management Instrumentation, технологии, которая поставляет клиенту большую часть информации о состоянии операционной системы. Еще есть %TEMP%\ACQ65R5UXFHBE896.exe — честно говоря, без понятия, что это, ставлю на временное имя самого клиента, которое ему дала одна из песочниц.
Ну и остались мелочи вроде мьютекса Local\Lagoscope-client для защиты от запуска второго экземпляра клиента и Highlighted actions: RtlWow64GetCurrentMachine и RtlWow64IsWowGuestMachineSupported — проверки архитектуры процессора. В импортах клиента их нет, их вызывает сама Windows при запуске.
Итоги
Итак, отчёт по клиенту Lagoscope мы прошли целиком: опасного в нём не нашли, а всё странное нашло объяснение. Но главное, на мой взгляд, не это. Теперь у вас есть короткий план, как читать любой отчёт VirusTotal: в шапке сверить хеш и дату проверки, в Detection посмотреть не только число срабатываний, но и кто и что написал, в Details — энтропию и импорты, в Relations — куда программа ходила, а в Behavior — что она делала в песочнице.

И последнее: ни один отчёт не доказывает, что программа безопасна. Он показывает, что о ней можно узнать, не запуская её у себя. А дальше решать вам — теперь уже не вслепую.
Спасибо за внимание к этому лонгриду. Если остались вопросы — пишите в комментариях, постараюсь ответить на все. Если хотите присоединиться к тестированию Lagoscope или у вас есть идеи и предложения по его развитию — тоже пишите, или присоединяйтесь к обсуждениям в телеграм-канале, там я планирую публиковать новости разработки и получать обратную связь.
