use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Decoded {
pub key: Vec<u8>,
pub offset: usize,
pub text: String,
}
impl Decoded {
pub fn key_hex(&self) -> String {
self.key.iter().map(|b| format!("{:02x}", b)).collect()
}
}
pub const KNOWN_MULTI_BYTE_KEYS: &[&[u8]] = &[
&[0xDE, 0xAD, 0xBE, 0xEF],
&[0xDE, 0xDE, 0xFF, 0xBA],
&[0xBA, 0xAD, 0xF0, 0x0D],
&[0x54, 0x76, 0x12, 0x9D],
&[0x37, 0x37, 0x37, 0x37],
&[0x22, 0x22, 0x22, 0x22],
];
#[inline]
fn is_printable(b: u8) -> bool {
(0x20..0x7f).contains(&b) || b == b'\t'
}
pub fn brute_decode(data: &[u8], min_run: usize) -> Vec<Decoded> {
let mut hits: BTreeSet<(String, Vec<u8>)> = BTreeSet::new();
let mut out = Vec::new();
for key in 1u8..=255 {
scan_with_key(data, &[key], min_run, &mut hits, &mut out);
}
for k in KNOWN_MULTI_BYTE_KEYS {
scan_with_key(data, k, min_run, &mut hits, &mut out);
}
out.sort();
out
}
pub fn decode_with_key(data: &[u8], key: &[u8], min_run: usize) -> Vec<Decoded> {
let mut hits: BTreeSet<(String, Vec<u8>)> = BTreeSet::new();
let mut out = Vec::new();
scan_with_key(data, key, min_run, &mut hits, &mut out);
out.sort();
out
}
fn scan_with_key(
data: &[u8],
key: &[u8],
min_run: usize,
hits: &mut BTreeSet<(String, Vec<u8>)>,
out: &mut Vec<Decoded>,
) {
if key.is_empty() {
return;
}
let mut run_start: Option<usize> = None;
let mut i = 0;
while i < data.len() {
let dec = data[i] ^ key[i % key.len()];
if is_printable(dec) {
if run_start.is_none() {
run_start = Some(i);
}
} else if let Some(start) = run_start.take() {
if i - start >= min_run {
emit_run(data, start, i, key, hits, out);
}
}
i += 1;
}
if let Some(start) = run_start {
if data.len() - start >= min_run {
emit_run(data, start, data.len(), key, hits, out);
}
}
}
fn emit_run(
data: &[u8],
start: usize,
end: usize,
key: &[u8],
hits: &mut BTreeSet<(String, Vec<u8>)>,
out: &mut Vec<Decoded>,
) {
let printable_src = data[start..end].iter().filter(|&&b| is_printable(b)).count();
if printable_src * 2 >= end - start {
return;
}
let decoded: Vec<u8> = data[start..end]
.iter()
.enumerate()
.map(|(i, b)| b ^ key[(start + i) % key.len()])
.collect();
let letters = decoded
.iter()
.filter(|&&b| b.is_ascii_alphabetic())
.count();
if letters * 10 < decoded.len() * 7 {
return;
}
let mut seen = [false; 26];
for &b in &decoded {
if b.is_ascii_alphabetic() {
seen[(b.to_ascii_lowercase() - b'a') as usize] = true;
}
}
if seen.iter().filter(|x| **x).count() < 5 {
return;
}
let s = match std::str::from_utf8(&decoded) {
Ok(s) => s.to_string(),
Err(_) => return,
};
let dedupe_key = (s.clone(), key.to_vec());
if hits.insert(dedupe_key) {
out.push(Decoded {
key: key.to_vec(),
offset: start,
text: s,
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_yields_nothing() {
assert!(brute_decode(&[], 8).is_empty());
}
#[test]
fn recovers_xor_string_substring() {
let plain = b"google.com.suffix";
let key = 0x77u8;
let encoded: Vec<u8> = plain.iter().map(|b| b ^ key).collect();
let hits = brute_decode(&encoded, 8);
let found = hits
.iter()
.any(|d| d.key == vec![key] && d.text.contains("google.com.suffix"));
assert!(found, "missed XOR=0x77 plaintext: {:?}", hits);
}
#[test]
fn skips_plaintext_input() {
let plain = b"this is just plaintext that any strings tool finds";
let hits = brute_decode(plain, 8);
assert!(
hits.is_empty(),
"emitted decode for plaintext source: {:?}",
hits
);
}
#[test]
fn min_run_floor_enforced() {
let plain = b"foo";
let key = 0x55u8;
let encoded: Vec<u8> = plain.iter().map(|b| b ^ key).collect();
let mut buf = vec![0x00];
buf.extend_from_slice(&encoded);
buf.push(0x00);
let hits = brute_decode(&buf, 8);
assert!(hits.iter().all(|d| d.text != "foo"));
}
#[test]
fn dedupe_avoids_duplicate_emit_per_key() {
let plain = b"command_handler_v1";
let key = 0xAAu8;
let sep = 0xAAu8;
let encoded: Vec<u8> = plain.iter().map(|b| b ^ key).collect();
let mut buf = Vec::new();
buf.extend_from_slice(&encoded);
buf.push(sep);
buf.extend_from_slice(&encoded);
let hits = brute_decode(&buf, 8);
let n = hits
.iter()
.filter(|d| d.key == vec![key] && d.text.contains("command_handler_v1"))
.count();
assert_eq!(n, 1, "expected single dedupe entry, got {}: {:?}", n, hits);
}
#[test]
fn recovers_mirai_4byte_xor() {
let plain = b"command_handler_attack_table_v1xxx";
let key = [0xDEu8, 0xDE, 0xFF, 0xBA];
let encoded: Vec<u8> = plain
.iter()
.enumerate()
.map(|(i, b)| b ^ key[i % 4])
.collect();
let hits = decode_with_key(&encoded, &key, 8);
let found = hits.iter().any(|d| d.text.contains("command_handler"));
assert!(found, "missed Mirai 4-byte decode: {:?}", hits);
assert_eq!(hits[0].key_hex(), "dedeffba");
}
#[test]
fn known_keys_in_brute_decode() {
let plain = b"shell_command_killer_v2_payload";
let key = [0xBA, 0xAD, 0xF0, 0x0D];
let encoded: Vec<u8> = plain
.iter()
.enumerate()
.map(|(i, b)| b ^ key[i % 4])
.collect();
let hits = brute_decode(&encoded, 8);
let found = hits
.iter()
.any(|d| d.key == key.to_vec() && d.text.contains("shell_command"));
assert!(found, "brute_decode missed multi-byte key: {:?}", hits);
}
}