use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use serde::Serialize;
use crate::Result;
const AF_PACKET: u16 = 17;
const SOCK_RAW: u16 = 3;
const IFF_PROMISC: u32 = 0x100;
#[derive(Debug, Clone, Serialize)]
pub struct RawSocketInfo {
pub pid: u32,
pub comm: String,
pub socket_type: String,
pub protocol: u16,
pub is_promiscuous: bool,
pub is_suspicious: bool,
}
pub use crate::heuristics::classify_raw_socket;
pub fn walk_raw_sockets<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<RawSocketInfo>> {
let init_task_addr = match reader.symbols().symbol_address("init_task") {
Some(a) => a,
None => return Ok(vec![]),
};
let tasks_offset = match reader.symbols().field_offset("task_struct", "tasks") {
Some(o) => o,
None => return Ok(vec![]),
};
if reader
.symbols()
.field_offset("task_struct", "files")
.is_none()
{
return Ok(vec![]);
}
let head_vaddr = init_task_addr + tasks_offset;
let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
let mut results: Vec<RawSocketInfo> = Vec::new();
collect_raw_sockets_for_task(reader, init_task_addr, &mut results);
for &task_addr in &task_addrs {
collect_raw_sockets_for_task(reader, task_addr, &mut results);
}
results.sort_by_key(|r| r.pid);
Ok(results)
}
fn collect_raw_sockets_for_task<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
task_addr: u64,
out: &mut Vec<RawSocketInfo>,
) {
let pid: u32 = match reader.read_field(task_addr, "task_struct", "pid") {
Ok(v) => v,
Err(_) => return,
};
let comm = reader
.read_field_string(task_addr, "task_struct", "comm", 16)
.unwrap_or_default();
let files_ptr: u64 = match reader.read_field(task_addr, "task_struct", "files") {
Ok(v) => v,
Err(_) => return,
};
if files_ptr == 0 {
return;
}
let fdt_ptr: u64 = match reader.read_field(files_ptr, "files_struct", "fdt") {
Ok(v) => v,
Err(_) => return,
};
if fdt_ptr == 0 {
return;
}
let fd_array_ptr: u64 = match reader.read_field(fdt_ptr, "fdtable", "fd") {
Ok(v) => v,
Err(_) => return,
};
if fd_array_ptr == 0 {
return;
}
for fd_index in 0u64..256 {
let file_slot_addr = fd_array_ptr + fd_index * 8;
let file_ptr_raw = match reader.read_bytes(file_slot_addr, 8) {
Ok(b) => b,
Err(_) => break,
};
let file_ptr = u64::from_le_bytes(match file_ptr_raw.try_into() {
Ok(b) => b,
Err(_) => break,
});
if file_ptr == 0 {
continue;
}
if let Some(info) = try_read_raw_socket(reader, pid, &comm, file_ptr) {
out.push(info);
}
}
}
fn try_read_raw_socket<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
pid: u32,
comm: &str,
file_ptr: u64,
) -> Option<RawSocketInfo> {
let sock_ptr: u64 = reader.read_field(file_ptr, "file", "private_data").ok()?;
if sock_ptr == 0 {
return None;
}
let sock_type: u16 = reader.read_field(sock_ptr, "socket", "type").ok()?;
let sk_ptr: u64 = reader.read_field(sock_ptr, "socket", "sk").ok()?;
if sk_ptr == 0 {
return None;
}
let sk_family: u16 = reader.read_field(sk_ptr, "sock", "sk_family").ok()?;
let protocol: u16 = reader
.read_field::<u16>(sk_ptr, "sock", "sk_protocol")
.unwrap_or(0);
let socket_type_str = if sk_family == AF_PACKET {
"AF_PACKET"
} else if sock_type == SOCK_RAW {
"SOCK_RAW"
} else {
return None; };
let is_promiscuous = try_read_promisc(reader, sk_ptr);
let is_suspicious = classify_raw_socket(comm, socket_type_str, is_promiscuous);
Some(RawSocketInfo {
pid,
comm: comm.to_string(),
socket_type: socket_type_str.to_string(),
protocol,
is_promiscuous,
is_suspicious,
})
}
fn try_read_promisc<P: PhysicalMemoryProvider>(reader: &ObjectReader<P>, sk_ptr: u64) -> bool {
let prot_hook_offset = match reader.symbols().field_offset("packet_sock", "prot_hook") {
Some(o) => o,
None => return false,
};
let dev_in_hook = match reader.symbols().field_offset("packet_type", "dev") {
Some(o) => o,
None => return false,
};
let dev_ptr_addr = sk_ptr + prot_hook_offset + dev_in_hook;
let dev_raw = match reader.read_bytes(dev_ptr_addr, 8) {
Ok(b) => b,
Err(_) => return false,
};
let dev_ptr = u64::from_le_bytes(match dev_raw.try_into() {
Ok(b) => b,
Err(_) => return false,
});
if dev_ptr == 0 {
return false;
}
let flags: u32 = match reader.read_field(dev_ptr, "net_device", "flags") {
Ok(v) => v,
Err(_) => return false,
};
(flags & IFF_PROMISC) != 0
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::object_reader::ObjectReader;
use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
#[test]
fn classify_raw_socket_promiscuous_is_suspicious() {
assert!(
classify_raw_socket("tcpdump", "AF_PACKET", true),
"promiscuous mode must always be suspicious regardless of comm"
);
}
#[test]
fn classify_raw_socket_af_packet_unknown_comm_suspicious() {
assert!(
classify_raw_socket("malware", "AF_PACKET", false),
"AF_PACKET socket held by unknown process must be suspicious"
);
}
#[test]
fn classify_raw_socket_tcpdump_benign() {
assert!(
!classify_raw_socket("tcpdump", "AF_PACKET", false),
"non-promiscuous AF_PACKET socket by tcpdump must not be suspicious"
);
}
#[test]
fn classify_raw_socket_ping_benign() {
assert!(
!classify_raw_socket("ping", "SOCK_RAW", false),
"SOCK_RAW socket by ping must not be suspicious"
);
}
#[test]
fn classify_raw_socket_sock_raw_unknown_comm_suspicious() {
assert!(
classify_raw_socket("implant", "SOCK_RAW", false),
"SOCK_RAW socket held by unknown process must be suspicious"
);
}
fn make_reader_no_init_task() -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn walk_raw_sockets_missing_init_task_returns_empty() {
let reader = make_reader_no_init_task();
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing init_task must yield empty results (graceful degradation)"
);
}
#[test]
fn classify_raw_socket_unknown_type_benign() {
assert!(
!classify_raw_socket("someproc", "UNKNOWN_TYPE", false),
"unknown socket type, not promiscuous must not be suspicious"
);
}
#[test]
fn classify_raw_socket_wireshark_af_packet_benign() {
assert!(
!classify_raw_socket("wireshark", "AF_PACKET", false),
"wireshark AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_dumpcap_af_packet_benign() {
assert!(
!classify_raw_socket("dumpcap", "AF_PACKET", false),
"dumpcap AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_dhclient_af_packet_benign() {
assert!(
!classify_raw_socket("dhclient", "AF_PACKET", false),
"dhclient AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_dhcpcd_af_packet_benign() {
assert!(
!classify_raw_socket("dhcpcd", "AF_PACKET", false),
"dhcpcd AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_arping_af_packet_benign() {
assert!(
!classify_raw_socket("arping", "AF_PACKET", false),
"arping AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_ping_af_packet_benign() {
assert!(
!classify_raw_socket("ping", "AF_PACKET", false),
"ping AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_ping6_af_packet_benign() {
assert!(
!classify_raw_socket("ping6", "AF_PACKET", false),
"ping6 AF_PACKET must not be suspicious"
);
}
#[test]
fn classify_raw_socket_traceroute_sock_raw_benign() {
assert!(
!classify_raw_socket("traceroute", "SOCK_RAW", false),
"traceroute SOCK_RAW must not be suspicious"
);
}
#[test]
fn classify_raw_socket_traceroute6_sock_raw_benign() {
assert!(
!classify_raw_socket("traceroute6", "SOCK_RAW", false),
"traceroute6 SOCK_RAW must not be suspicious"
);
}
#[test]
fn classify_raw_socket_arping_sock_raw_benign() {
assert!(
!classify_raw_socket("arping", "SOCK_RAW", false),
"arping SOCK_RAW must not be suspicious"
);
}
#[test]
fn classify_raw_socket_ping6_sock_raw_benign() {
assert!(
!classify_raw_socket("ping6", "SOCK_RAW", false),
"ping6 SOCK_RAW must not be suspicious"
);
}
#[test]
fn classify_raw_socket_uppercase_comm_not_benign() {
assert!(
!classify_raw_socket("TCPDUMP", "AF_PACKET", false),
"TCPDUMP (uppercase) AF_PACKET must not be suspicious (case-folded)"
);
}
#[test]
fn classify_raw_socket_promisc_overrides_benign_comm() {
assert!(
classify_raw_socket("wireshark", "AF_PACKET", true),
"promiscuous wireshark must still be suspicious"
);
}
fn make_reader_with_init_task_no_tasks() -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_symbol("init_task", 0xFFFF_FFFF_8260_0000)
.add_struct("task_struct", 512)
.add_field("task_struct", "pid", 0, "int")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn walk_raw_sockets_missing_tasks_offset_returns_empty() {
let reader = make_reader_with_init_task_no_tasks();
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing task_struct.tasks offset must yield empty results"
);
}
fn make_reader_with_tasks_no_files() -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_symbol("init_task", 0xFFFF_FFFF_8260_0000)
.add_struct("task_struct", 512)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "pointer")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn walk_raw_sockets_missing_files_field_returns_empty() {
let reader = make_reader_with_tasks_no_files();
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing task_struct.files field must yield empty results"
);
}
#[test]
fn walk_raw_sockets_symbol_present_files_null_returns_empty() {
use memf_core::test_builders::flags as ptf;
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
let tasks_offset: u64 = 0x10;
let sym_vaddr: u64 = 0xFFFF_8800_0090_0000;
let sym_paddr: u64 = 0x0090_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", 0x30, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"null files ptr → no fd table → no raw sockets"
);
}
#[test]
fn walk_raw_sockets_fdt_ptr_null_returns_empty() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_0091_0000;
let sym_paddr: u64 = 0x0091_0000;
let files_vaddr: u64 = 0xFFFF_8800_0092_0000;
let files_paddr: u64 = 0x0092_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let files_page = [0u8; 4096];
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"fdt_ptr == 0 → early return → no raw sockets"
);
}
#[test]
fn walk_raw_sockets_fd_array_null_returns_empty() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_0093_0000;
let sym_paddr: u64 = 0x0093_0000;
let files_vaddr: u64 = 0xFFFF_8800_0094_0000;
let files_paddr: u64 = 0x0094_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_0095_0000;
let fdt_paddr: u64 = 0x0095_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let fdt_page = [0u8; 4096];
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"fd_array_ptr == 0 → early return → no raw sockets"
);
}
#[test]
fn raw_socket_info_serializes() {
let info = RawSocketInfo {
pid: 99,
comm: "sniffer".to_string(),
socket_type: "AF_PACKET".to_string(),
protocol: 0x0300,
is_promiscuous: false,
is_suspicious: true,
};
let cloned = info.clone();
let json = serde_json::to_string(&cloned).unwrap();
assert!(json.contains("\"pid\":99"));
assert!(json.contains("AF_PACKET"));
let dbg = format!("{cloned:?}");
assert!(dbg.contains("sniffer"));
}
#[test]
fn walk_raw_sockets_all_fd_slots_null_returns_empty() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_0096_0000;
let sym_paddr: u64 = 0x0096_0000;
let files_vaddr: u64 = 0xFFFF_8800_0097_0000;
let files_paddr: u64 = 0x0097_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_0098_0000;
let fdt_paddr: u64 = 0x0098_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_0099_0000;
let fd_array_paddr: u64 = 0x0099_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let fd_array_page = [0u8; 4096];
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(result.is_empty(), "all-zero fd slots → no raw sockets");
}
#[test]
fn walk_raw_sockets_af_packet_sock_detected() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_009A_0000;
let sym_paddr: u64 = 0x009A_0000;
let files_vaddr: u64 = 0xFFFF_8800_009B_0000;
let files_paddr: u64 = 0x009B_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_009C_0000;
let fdt_paddr: u64 = 0x009C_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_009D_0000;
let fd_array_paddr: u64 = 0x009D_0000;
let file_vaddr: u64 = 0xFFFF_8800_009E_0000;
let file_paddr: u64 = 0x009E_0000;
let sock_vaddr: u64 = 0xFFFF_8800_009F_0000;
let sock_paddr: u64 = 0x009F_0000;
let sk_vaddr: u64 = 0xFFFF_8800_00C0_0000;
let sk_paddr: u64 = 0x00C0_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.add_struct("file", 0x100)
.add_field("file", "private_data", 0x00, "pointer")
.add_struct("socket", 0x80)
.add_field("socket", "type", 0x00, "unsigned short")
.add_field("socket", "sk", 0x08, "pointer")
.add_struct("sock", 0x100)
.add_field("sock", "sk_family", 0x00, "unsigned short")
.add_field("sock", "sk_protocol", 0x02, "unsigned short")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[0x20..0x28].copy_from_slice(b"sniffer\0");
task_page[0x00..0x04].copy_from_slice(&200u32.to_le_bytes()); task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let mut fd_array_page = [0u8; 4096];
fd_array_page[0..8].copy_from_slice(&file_vaddr.to_le_bytes());
let mut file_page = [0u8; 4096];
file_page[0..8].copy_from_slice(&sock_vaddr.to_le_bytes());
let mut socket_page = [0u8; 4096];
socket_page[0..2].copy_from_slice(&3u16.to_le_bytes()); socket_page[8..16].copy_from_slice(&sk_vaddr.to_le_bytes());
let mut sk_page = [0u8; 4096];
sk_page[0..2].copy_from_slice(&17u16.to_le_bytes()); sk_page[2..4].copy_from_slice(&0x0300u16.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.map_4k(file_vaddr, file_paddr, ptf::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(sock_vaddr, sock_paddr, ptf::WRITABLE)
.write_phys(sock_paddr, &socket_page)
.map_4k(sk_vaddr, sk_paddr, ptf::WRITABLE)
.write_phys(sk_paddr, &sk_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert_eq!(result.len(), 1, "one AF_PACKET socket should be detected");
assert_eq!(result[0].socket_type, "AF_PACKET");
assert_eq!(result[0].pid, 200);
assert!(
!result[0].is_promiscuous,
"promisc false when packet_sock missing"
);
}
#[test]
fn walk_raw_sockets_sk_ptr_null_no_entry() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_00D0_0000;
let sym_paddr: u64 = 0x00D0_0000;
let files_vaddr: u64 = 0xFFFF_8800_00D1_0000;
let files_paddr: u64 = 0x00D1_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_00D2_0000;
let fdt_paddr: u64 = 0x00D2_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_00D3_0000;
let fd_array_paddr: u64 = 0x00D3_0000;
let file_vaddr: u64 = 0xFFFF_8800_00D4_0000;
let file_paddr: u64 = 0x00D4_0000;
let sock_vaddr: u64 = 0xFFFF_8800_00D5_0000;
let sock_paddr: u64 = 0x00D5_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.add_struct("file", 0x100)
.add_field("file", "private_data", 0x00, "pointer")
.add_struct("socket", 0x80)
.add_field("socket", "type", 0x00, "unsigned short")
.add_field("socket", "sk", 0x08, "pointer")
.add_struct("sock", 0x100)
.add_field("sock", "sk_family", 0x00, "unsigned short")
.add_field("sock", "sk_protocol", 0x02, "unsigned short")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let mut fd_array_page = [0u8; 4096];
fd_array_page[0..8].copy_from_slice(&file_vaddr.to_le_bytes());
let mut file_page = [0u8; 4096];
file_page[0..8].copy_from_slice(&sock_vaddr.to_le_bytes());
let mut socket_page = [0u8; 4096];
socket_page[0..2].copy_from_slice(&3u16.to_le_bytes()); socket_page[8..16].copy_from_slice(&0u64.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.map_4k(file_vaddr, file_paddr, ptf::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(sock_vaddr, sock_paddr, ptf::WRITABLE)
.write_phys(sock_paddr, &socket_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"sk_ptr == 0 → returns None → no raw socket entry"
);
}
#[test]
fn walk_raw_sockets_sock_raw_family_detected() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_00E0_0000;
let sym_paddr: u64 = 0x00E0_0000;
let files_vaddr: u64 = 0xFFFF_8800_00E1_0000;
let files_paddr: u64 = 0x00E1_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_00E2_0000;
let fdt_paddr: u64 = 0x00E2_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_00E3_0000;
let fd_array_paddr: u64 = 0x00E3_0000;
let file_vaddr: u64 = 0xFFFF_8800_00E4_0000;
let file_paddr: u64 = 0x00E4_0000;
let sock_vaddr: u64 = 0xFFFF_8800_00E5_0000;
let sock_paddr: u64 = 0x00E5_0000;
let sk_vaddr: u64 = 0xFFFF_8800_00E6_0000;
let sk_paddr: u64 = 0x00E6_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.add_struct("file", 0x100)
.add_field("file", "private_data", 0x00, "pointer")
.add_struct("socket", 0x80)
.add_field("socket", "type", 0x00, "unsigned short")
.add_field("socket", "sk", 0x08, "pointer")
.add_struct("sock", 0x100)
.add_field("sock", "sk_family", 0x00, "unsigned short")
.add_field("sock", "sk_protocol", 0x02, "unsigned short")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[0x20..0x28].copy_from_slice(b"implant\0");
task_page[0x00..0x04].copy_from_slice(&300u32.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let mut fd_array_page = [0u8; 4096];
fd_array_page[0..8].copy_from_slice(&file_vaddr.to_le_bytes());
let mut file_page = [0u8; 4096];
file_page[0..8].copy_from_slice(&sock_vaddr.to_le_bytes());
let mut socket_page = [0u8; 4096];
socket_page[0..2].copy_from_slice(&3u16.to_le_bytes()); socket_page[8..16].copy_from_slice(&sk_vaddr.to_le_bytes());
let mut sk_page = [0u8; 4096];
sk_page[0..2].copy_from_slice(&2u16.to_le_bytes()); sk_page[2..4].copy_from_slice(&255u16.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.map_4k(file_vaddr, file_paddr, ptf::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(sock_vaddr, sock_paddr, ptf::WRITABLE)
.write_phys(sock_paddr, &socket_page)
.map_4k(sk_vaddr, sk_paddr, ptf::WRITABLE)
.write_phys(sk_paddr, &sk_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert_eq!(result.len(), 1, "SOCK_RAW socket should be detected");
assert_eq!(result[0].socket_type, "SOCK_RAW");
assert_eq!(result[0].pid, 300);
assert!(
result[0].is_suspicious,
"unknown comm + SOCK_RAW → suspicious"
);
}
#[test]
fn walk_raw_sockets_not_raw_socket_returns_none() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_00F0_0000;
let sym_paddr: u64 = 0x00F0_0000;
let files_vaddr: u64 = 0xFFFF_8800_00F1_0000;
let files_paddr: u64 = 0x00F1_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_00F2_0000;
let fdt_paddr: u64 = 0x00F2_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_00F3_0000;
let fd_array_paddr: u64 = 0x00F3_0000;
let file_vaddr: u64 = 0xFFFF_8800_00F4_0000;
let file_paddr: u64 = 0x00F4_0000;
let sock_vaddr: u64 = 0xFFFF_8800_00F5_0000;
let sock_paddr: u64 = 0x00F5_0000;
let sk_vaddr: u64 = 0xFFFF_8800_00F6_0000;
let sk_paddr: u64 = 0x00F6_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.add_struct("file", 0x100)
.add_field("file", "private_data", 0x00, "pointer")
.add_struct("socket", 0x80)
.add_field("socket", "type", 0x00, "unsigned short")
.add_field("socket", "sk", 0x08, "pointer")
.add_struct("sock", 0x100)
.add_field("sock", "sk_family", 0x00, "unsigned short")
.add_field("sock", "sk_protocol", 0x02, "unsigned short")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let mut fd_array_page = [0u8; 4096];
fd_array_page[0..8].copy_from_slice(&file_vaddr.to_le_bytes());
let mut file_page = [0u8; 4096];
file_page[0..8].copy_from_slice(&sock_vaddr.to_le_bytes());
let mut socket_page = [0u8; 4096];
socket_page[0..2].copy_from_slice(&1u16.to_le_bytes()); socket_page[8..16].copy_from_slice(&sk_vaddr.to_le_bytes());
let mut sk_page = [0u8; 4096];
sk_page[0..2].copy_from_slice(&2u16.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.map_4k(file_vaddr, file_paddr, ptf::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(sock_vaddr, sock_paddr, ptf::WRITABLE)
.write_phys(sock_paddr, &socket_page)
.map_4k(sk_vaddr, sk_paddr, ptf::WRITABLE)
.write_phys(sk_paddr, &sk_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(
result.is_empty(),
"SOCK_STREAM is not a raw socket → None → empty"
);
}
#[test]
fn walk_raw_sockets_private_data_null_no_entry() {
use memf_core::test_builders::flags as ptf;
let tasks_offset: u64 = 0x10;
let files_offset: u64 = 0x30;
let sym_vaddr: u64 = 0xFFFF_8800_00C1_0000;
let sym_paddr: u64 = 0x00C1_0000;
let files_vaddr: u64 = 0xFFFF_8800_00C2_0000;
let files_paddr: u64 = 0x00C2_0000;
let fdt_vaddr: u64 = 0xFFFF_8800_00C3_0000;
let fdt_paddr: u64 = 0x00C3_0000;
let fd_array_vaddr: u64 = 0xFFFF_8800_00C4_0000;
let fd_array_paddr: u64 = 0x00C4_0000;
let file_vaddr: u64 = 0xFFFF_8800_00C5_0000;
let file_paddr: u64 = 0x00C5_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", sym_vaddr)
.add_struct("list_head", 0x10)
.add_field("list_head", "next", 0x00, "pointer")
.add_struct("task_struct", 0x400)
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "pid", 0x00, "unsigned int")
.add_field("task_struct", "comm", 0x20, "char")
.add_field("task_struct", "files", files_offset, "pointer")
.add_struct("files_struct", 0x100)
.add_field("files_struct", "fdt", 0x00, "pointer")
.add_struct("fdtable", 0x40)
.add_field("fdtable", "fd", 0x00, "pointer")
.add_struct("file", 0x100)
.add_field("file", "private_data", 0x00, "pointer")
.add_struct("socket", 0x80)
.add_field("socket", "type", 0x00, "unsigned short")
.add_field("socket", "sk", 0x08, "pointer")
.add_struct("sock", 0x100)
.add_field("sock", "sk_family", 0x00, "unsigned short")
.add_field("sock", "sk_protocol", 0x02, "unsigned short")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut task_page = [0u8; 4096];
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[files_offset as usize..files_offset as usize + 8]
.copy_from_slice(&files_vaddr.to_le_bytes());
let mut files_page = [0u8; 4096];
files_page[0..8].copy_from_slice(&fdt_vaddr.to_le_bytes());
let mut fdt_page = [0u8; 4096];
fdt_page[0..8].copy_from_slice(&fd_array_vaddr.to_le_bytes());
let mut fd_array_page = [0u8; 4096];
fd_array_page[0..8].copy_from_slice(&file_vaddr.to_le_bytes());
let file_page = [0u8; 4096];
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(files_vaddr, files_paddr, ptf::WRITABLE)
.write_phys(files_paddr, &files_page)
.map_4k(fdt_vaddr, fdt_paddr, ptf::WRITABLE)
.write_phys(fdt_paddr, &fdt_page)
.map_4k(fd_array_vaddr, fd_array_paddr, ptf::WRITABLE)
.write_phys(fd_array_paddr, &fd_array_page)
.map_4k(file_vaddr, file_paddr, ptf::WRITABLE)
.write_phys(file_paddr, &file_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_raw_sockets(&reader).expect("should not error");
assert!(result.is_empty(), "private_data=0 → no raw socket entry");
}
}