Skip to content

HighlyExistant/Packet-Sniffer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Packet Sniffer

This is my very own implementation of a packet sniffer in Rust, although it could very well be implemented in other programming languages like C or C++. The purpose of this repository is to detail how you can create your own packet sniffer, and detail various formats like TCP, UDP and ICMP to better parse information. This is also to teach internet safety, as it can be surprisingly easy to make a very basic packet sniffer, truly showing the importance of encryption protocols like SSH.

Prerequisites

The following tutorial is implemented on Windows 11, but it should be a similar process on other operating systems. A few of the crates we will be using are windows-sys, which you can put in the Cargo.toml file.

[dependencies]
anyhow = "1.0.100"

[dependencies.windows-sys]
version = "0.61.2" # You can put the current version as of writing
features = [
    "Win32_Networking",
    "Win32_Networking_WinSock",
    "Win32_System_IO",
    "Win32_UI_Input_KeyboardAndMouse", # Optional
]

How to Recieve Raw Internet Packets

Before starting, it's important to call WSAStartup at the very beggining of your program and WSACleanup at the very end. To deffer these calls to another structure using RAII I will use the following

use windows_sys::Win32::Networking::WinSock::{WSACleanup, WSADATA, WSAStartup};
#[derive(Clone, Default)]
pub struct WSA {
    _data: WSADATA,
}

impl WSA {
    pub fn new() -> Self {
        unsafe {
            let mut _data = WSADATA::default();
            WSAStartup(2 | (2<<8), &mut _data);
            Self { _data }
        }
    }
}

impl Drop for WSA {
    fn drop(&mut self) {
        unsafe { WSACleanup(); }
    }
}

And then we can place that into our main function at the very beggining.

fn main() -> Result<(), anyhow::Error> {
    let _wsa = WSA::new();
    Ok(())
}

Now lets start by making a structure called IPSocket, where we will store our socket data.

pub struct IPSocket {
    socket: usize,
}

The way we create new socket connections is through the socket function. For more information on how it works, you can look at its documentation, but here will also be a guide.

use std::net::IpAddr;
use windows_sys::Win32::Networking::WinSock::{AF_INET, IN_ADDR, IN_ADDR_0, INVALID_SOCKET, IP_HDRINCL, IPPROTO_IP, RCVALL_OFF, RCVALL_ON, SIO_RCVALL, SOCK_RAW, SOCKADDR, SOCKADDR_IN, SOCKET_ERROR, WSAIoctl, bind, closesocket, recvfrom, setsockopt, socket};


...

impl IPSocket {
    pub fn new() -> Result<Self, std::io::Error> {
        unsafe {
            let socket = socket(
                // This AF_INET Parameter specifies that we 
                // will be using the IPV4 protocol.
                AF_INET as i32, 
                // Allows for the manipulation of the IPV4
                // header.
                SOCK_RAW, 
                // This is a dummy 0 valued constant so that
                // we don't only capture TCP, UDP, or ICMP packets.
                IPPROTO_IP
            );
            if socket == INVALID_SOCKET {
                return Err(std::io::Error::last_os_error());
            }
            Ok(Self { socket })
        }
    }
    /// Error handling function for future use.
    fn sock_result(result: i32) -> Result<(), std::io::Error> {
        if result == SOCKET_ERROR {
            return Err(std::io::Error::last_os_error());
        }
        Ok(())
    }
}
impl Drop for IPSocket {
    fn drop(&mut self) {
        // For every call to socket, we must ensure
        // there is a closesocket call.
        unsafe {
            closesocket(self.socket);
        }
    }
}

Now we need to bind our socket to the network using our local IP Address. For this we will create our own bind function.

impl IPSocket {
...
    fn addr_to_sockaddr_in(addr: IpAddr) -> SOCKADDR_IN {
        match addr {
            IpAddr::V4(v4) => {
                println!("{:x}", v4.to_bits().swap_bytes());
                SOCKADDR_IN { 
                    // Same family as our initialization.
                    sin_family: AF_INET,
                    // This field contains our IP Address.
                    sin_addr: IN_ADDR { 
                        S_un: IN_ADDR_0 {
                            // If you are using C or C++, the swap_bytes
                            // function will be equivalent to using htons.
                            // We do this because the internet uses Big
                            // Endian byte ordering, while most CPU's
                            // use Little Endian.
                            S_addr: v4.to_bits().swap_bytes(),
                        } 
                    },
                    // Raw Sockets have no notion of ports, so this field 
                    // can be ignored.
                    sin_port: 0,
                    ..Default::default()
                }
            }
            IpAddr::V6(_) => { // We aren't implementing IPV6 for this tutorial.
                unimplemented!()
            }
        }
    }
    pub fn bind(&mut self, addr: IpAddr) -> Result<(), std::io::Error> {
        unsafe {
            let addr = Self::addr_to_sockaddr_in(addr);
            let result = bind(
                self.socket, 
                &raw const addr as _, 
                std::mem::size_of::<SOCKADDR_IN>() as _
            );
            Self::sock_result(result)
        }
    }
}

Even though we set SOCK_RAW, we cannot access the raw IP header until we use setsockopt to specify it, using the optname IP_HDRINCL, which stands for Header Include. You can find its documentation here.

impl IPSocket {
    ...
    /// For this function to work, one needs administrative permissions
    pub fn include_ip_header(&mut self) -> Result<(), std::io::Error> {
        let dword_bool = 1u32;
        unsafe {
            let result = setsockopt(
                self.socket, 
                IPPROTO_IP, 
                IP_HDRINCL, 
                &raw const dword_bool as _, 
                4 // Size of the DWORD in bytes
            );
            Self::sock_result(result)
        }
    }
}

We can then instantiate this into our program.

/// This is my local IP Address. You can find yours by using
/// ipconfig in your commandline. That said it is a bit
/// inconvenient to use, especially if it ever changes.
/// For that reason we'll be looking at other methods to
/// get it later on.
const HOST: &'static str = "10.0.0.17";
fn main() -> Result<(), anyhow::Error> {
    let _wsa = WSA::new();

    let ipv4 = Ipv4Addr::from_str(HOST)?;
    let mut socket = IPSocket::new()?;
    socket.bind(
        std::net::IpAddr::V4(
            ipv4
        ), 
    )?;
    socket.include_ip_header()?;
    Ok(())
}

Recieving Internet Traffic From Other Computers

This will only get us our own traffic, but looking as though we are building a packet sniffer, we want all the traffic. To do this we can enter Windows Promiscuous Mode. The WSAIoctl function allows us to send control code that we can perform to the socket. Out of all the control codes, the one we want is SIO_RCVALL, as it "enables a socket to receive all IPv4 or IPv6 packets passing through a network interface".

impl IPSocket {
    ...
    pub fn windows_promiscuous_mode(&mut self, enter: bool) -> Result<(), std::io::Error> {
        unsafe {
            let mut bytesreturned = 0;
            let in_buffer = if enter {
                RCVALL_ON
            } else {
                RCVALL_OFF
            };
            let result = WSAIoctl(
                self.socket as _, 
                SIO_RCVALL, 
                &raw const in_buffer as _, 
                std::mem::size_of::<i32>() as _, // Size of in_buffer 
                std::ptr::null_mut(), // Unused 
                0, // Unused
                &mut bytesreturned, // Unused
                std::ptr::null_mut(), // Optional
                None // Ignored for non-overlapped sockets
            );
            Self::sock_result(result)
        }
    }
}

Now that we've enabled all internet traffic, we can create the recv function to actually recieve it.

impl IPSocket {
    ...

    pub fn recvfrom(&mut self, size: usize) -> Result<Vec<u8>, std::io::Error> {
        let mut sockaddr = SOCKADDR::default();
        let mut fromlen = std::mem::size_of::<SOCKADDR>();
        let mut ret = vec![0u8; size];
        unsafe {
            let result = recvfrom(
                self.socket, 
                ret.as_mut_ptr(), 
                size as _, 
                0, 
                &mut sockaddr, 
                &raw mut fromlen as _,
            );
            Self::sock_result(result)?;
        }
        Ok(ret)
    }
}

We can finally recieve data from all network services

const HOST: &'static str = "10.0.0.17";
fn main() -> Result<(), anyhow::Error> {
    let _wsa = WSA::new();

    let ipv4 = Ipv4Addr::from_str(HOST)?;
    let mut socket = IPSocket::new()?;
    socket.bind(
        std::net::IpAddr::V4(
            ipv4
        ), 
    )?;
    socket.include_ip_header()?;
    socket.windows_promiscuous_mode(true)?;
    loop {
        let get = socket.recvfrom(65535)?;
        // Arbitrary range set, so that it is not filled
        // with a bunch of garbage.
        println!("{:#?}", &get[0..100]);
    }
    socket.windows_promiscuous_mode(false)?;
    Ok(())
}

That's pretty much the end of the tutorial, if you are already familiar with the standard formats, as that is what the rest of this repository will cover.

References

About

Tutorial on how to implement a basic packet sniffer, taking inspiration from a few sources.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages