Files
ospab.network/ostp/src/mimicry.rs

175 lines
5.3 KiB
Rust

//! Dynamic SNI & TLS Mimicry Engine
use std::collections::HashMap;
/// Geo-based SNI target selection
pub struct MimicryEngine {
geo_sni_map: HashMap<String, Vec<String>>,
}
impl MimicryEngine {
pub fn new() -> Self {
let mut geo_sni_map = HashMap::new();
// Default geo-SNI mappings for contextual mimicry
geo_sni_map.insert(
"RU".into(),
vec![
"gosuslugi.ru".into(),
"sberbank.ru".into(),
"yandex.ru".into(),
],
);
geo_sni_map.insert(
"NO".into(),
vec!["bankid.no".into(), "vipps.no".into(), "altinn.no".into()],
);
geo_sni_map.insert(
"DE".into(),
vec![
"sparkasse.de".into(),
"deutsche-bank.de".into(),
"bund.de".into(),
],
);
geo_sni_map.insert(
"US".into(),
vec![
"apple.com".into(),
"microsoft.com".into(),
"amazon.com".into(),
],
);
geo_sni_map.insert(
"CN".into(),
vec!["qq.com".into(), "baidu.com".into(), "taobao.com".into()],
);
Self { geo_sni_map }
}
/// Select SNI based on geo-location
pub fn select_sni(&self, country_code: &str) -> Option<&str> {
self.geo_sni_map
.get(country_code)
.and_then(|list| list.first().map(|s| s.as_str()))
}
/// Get random SNI from geo list for anti-fingerprinting
pub fn random_sni(&self, country_code: &str) -> Option<&str> {
self.geo_sni_map.get(country_code).and_then(|list| {
if list.is_empty() {
None
} else {
let idx = rand::random::<usize>() % list.len();
Some(list[idx].as_str())
}
})
}
/// Add custom geo-SNI mapping
pub fn add_mapping(&mut self, country: String, domains: Vec<String>) {
self.geo_sni_map.insert(country, domains);
}
}
impl Default for MimicryEngine {
fn default() -> Self {
Self::new()
}
}
/// TLS ClientHello builder for REALITY-like mimicry
pub struct TlsHelloBuilder {
sni: String,
random_session_id: bool,
}
impl TlsHelloBuilder {
pub fn new(sni: impl Into<String>) -> Self {
Self {
sni: sni.into(),
random_session_id: true,
}
}
/// Build minimal TLS 1.3 ClientHello-like header
pub fn build(&self) -> Vec<u8> {
let mut hello = Vec::with_capacity(256);
// TLS record header
hello.push(0x16); // Handshake
hello.extend_from_slice(&[0x03, 0x01]); // TLS 1.0 for compat
// Placeholder for length (will update)
let len_pos = hello.len();
hello.extend_from_slice(&[0x00, 0x00]);
// Handshake header
hello.push(0x01); // ClientHello
let hs_len_pos = hello.len();
hello.extend_from_slice(&[0x00, 0x00, 0x00]); // length placeholder
// Client version (TLS 1.2 presented, 1.3 in extensions)
hello.extend_from_slice(&[0x03, 0x03]);
// Random (32 bytes)
let random: [u8; 32] = rand::random();
hello.extend_from_slice(&random);
// Session ID (32 bytes if random)
if self.random_session_id {
hello.push(32);
let session_id: [u8; 32] = rand::random();
hello.extend_from_slice(&session_id);
} else {
hello.push(0);
}
// Cipher suites (TLS 1.3 suites)
hello.extend_from_slice(&[0x00, 0x06]); // 3 suites
hello.extend_from_slice(&[0x13, 0x01]); // TLS_AES_128_GCM_SHA256
hello.extend_from_slice(&[0x13, 0x02]); // TLS_AES_256_GCM_SHA384
hello.extend_from_slice(&[0x13, 0x03]); // TLS_CHACHA20_POLY1305_SHA256
// Compression (null)
hello.extend_from_slice(&[0x01, 0x00]);
// Extensions with SNI
let ext_start = hello.len();
hello.extend_from_slice(&[0x00, 0x00]); // ext length placeholder
// SNI extension
hello.extend_from_slice(&[0x00, 0x00]); // type: server_name
let sni_bytes = self.sni.as_bytes();
let sni_ext_len = sni_bytes.len() + 5;
hello.extend_from_slice(&(sni_ext_len as u16).to_be_bytes());
hello.extend_from_slice(&((sni_bytes.len() + 3) as u16).to_be_bytes());
hello.push(0x00); // host_name type
hello.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
hello.extend_from_slice(sni_bytes);
// supported_versions extension
hello.extend_from_slice(&[0x00, 0x2b]); // type
hello.extend_from_slice(&[0x00, 0x03]); // length
hello.push(0x02); // versions length
hello.extend_from_slice(&[0x03, 0x04]); // TLS 1.3
// Update lengths
let ext_len = hello.len() - ext_start - 2;
hello[ext_start] = (ext_len >> 8) as u8;
hello[ext_start + 1] = ext_len as u8;
let total_len = hello.len() - 5;
hello[len_pos] = (total_len >> 8) as u8;
hello[len_pos + 1] = total_len as u8;
let hs_len = hello.len() - hs_len_pos - 3;
hello[hs_len_pos] = 0;
hello[hs_len_pos + 1] = (hs_len >> 8) as u8;
hello[hs_len_pos + 2] = hs_len as u8;
hello
}
}