Pages

Showing posts with label fun with raw sockets. Show all posts
Showing posts with label fun with raw sockets. Show all posts

Saturday, April 23, 2011

BPF and Erlang

An Erlang Interface to the Berkeley Packet Filter

The man page about bpf says:

The Berkeley Packet Filter provides a raw interface to data link layers
in a protocol independent fashion.  All packets on the network, even
those destined for other hosts, are accessible through this mechanism.

On BSD systems, bpf can be used for capturing and generating raw network frames. We can use bpf to send and receive network packets from Erlang code in a similar way to using the PF_PACKET socket interface on Linux.

All of the code presented here was run on Mac OS X (Snow Leopard). Hopefully the code is portable to all BSD operating systems. If it isn't, let me know in the comments or email me.

Pre-Requisites for Running the Code Examples

For the Erlang examples below, you'll need a fairly recent version of Erlang and a few libraries. You can download the Erlang source code or check it out from github:

git clone git://github.com/erlang/otp.git

procket is an NIF used to extend the Erlang runtime to make the various system calls. You can check it out here:

git clone git://github.com/msantos/procket.git

See the README, there is a bit of work involving setting up sudo to run a helper app.

Finally, the examples use another small library (pkt) to parse the packets using Erlang binaries and convert packets from Erlang records to binaries. It's available here:

git clone git://github.com/msantos/pkt.git

To compile the example Erlang modules:

erlc -I /path/to/procket/include -I /path/to/pkt/include annoy.erl

The BPF C Interface

The bpf character device is used to transfer raw network frames between user space and the kernel. On Mac OS X, there are a fixed number of available devices, each acting as a communication path for a single process.

Using bpf works as follows:

  • Starting from 0, open the bpf devices. If the the open() system call returns failure (-1) and errno is set to EBUSY, try the next character device, e.g., /dev/bpf1.

    Typical values for errno might be:

    • EPERM: the process does not have permission to access the character device. Either the process can temporarily be given superuser privileges or the permissions of the character device can be modified.

    Since bpf relies on the file permissions of the character device, the act of opening the device is the only operation that requires privileges. Other operations on the file descriptor do not require any special privileges.

    • ENOENT: the bpf device does not exist
  • The open() call returns a file descriptor.

  • ioctl() is used to associate the bpf device with an interface

  • ioctl() is used to retrieve the bpf buffer length

    The bpf device maintains a fixed buffer size. For efficiency, reads performed on the bpf device will block until either the buffer is full or a timeout is reached (by default, infinity). As a consequence, several packets may be returned by a single read.

  • Set some optional attributes that affects the behaviour of the bpf device.

    For example:

    • BIOCSHDRCMPLT: by default, the bpf device will construct a valid packet header for the underlying datalink type. Setting the "header complete" attribute allows the user to set the packet headers themselves.

    • BIOCSEESENT: the bpf device does not return packets sent from the host. This ioctl request can be used to change that behaviour.

    • BIOCIMMEDIATE: causes reads to immediately return after a packet is returned rather than buffering the packets

  • Apply filtering rules using BPF bytecode.

    bpf supports a set of instructions that allow the user to restrict which packets are returned by the device.

See this tutorial for a clear, concise example of capturing packets in C.

I've also put together some simple, runnable code. To compile it:

gcc -g -Wall -o bpf bpf.c

To keep the example from becoming too huge, not much is done except printing out the ethernet header. We'll cover more interesting ways of using bpf later.

The Erlang BPF Interface

To use bpf within Erlang, we'll need to be able to open the bpf device, perform the appropriate ioctl() operations, generate BPF filtering code and read and write from the device.

Opening the BPF Device

procket uses a setuid helper executable to open the bpf character device and pass the file descriptor back to Erlang:

{ok, Socket} = procket:dev("bpf").

Or using the bpf module:

{ok, Socket, Length} = bpf:open("en1").

Controlling the BPF Device

Once we have the file descriptor, we can set the device attributes by using the procket:ioctl/3 NIF.

The bpf header file defines a number of macros for calculating the correct ioctl request. Porting these macros to Erlang is straightforward.

According the to the man page on Mac OS X, the ioctl signature is defined as:

int ioctl(int fildes, unsigned long request, ...);

An ioctl request is an unsigned long, so the size of the command will either be 4 or 8 bytes, depending on whether the platform is 32 or 64-bit. However, the ioctl macros compute the request with the assumption that the command is a word (or 4 bytes): the lower half of the word holds the command and the top half has the length and the direction of the command.

(The number after the colon represents the number of bits in the field.)

  1. Copy argument into kernel:1
  2. Copy argument from kernel:1
  3. No arguments:1
  4. Parameter length:13
  5. Command group:8
  6. Command:8

The fields in order are:

  • IN: if set, the argument (the 3rd argument to ioctl) is read from the user space buffer

  • OUT: if set, the argument is written to the user space buffer

A command that is IN/OUT will have the contents of the buffer read by the kernel and written back.

  • VOID: no arguments are required by the ioctl request

  • Length: the size of the command in bytes

  • Group: the command group acts as namespace for organizing the ioctl requests

  • Command: 1 byte is reserved for the actual command

For example, the BIOCSHDRCMPLT macro in C is:

#define IOCPARM_MASK    0x1fff      /* parameter length, at most 13 bits */
#define _IOC(inout,group,num,len) \
    (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))
#define IOC_IN      (__uint32_t)0x80000000
#define _IOW(g,n,t) _IOC(IOC_IN,    (g), (n), sizeof(t))

#define BIOCSHDRCMPLT   _IOW('B',117, u_int)

The corresponding macro defined in Erlang:

-define(SIZEOF_U_INT, 4).
-define(IOCPARM_MASK, 16#1fff).
-define(IOC_INOUT, ?IOC_IN bor ?IOC_OUT).

-define(BIOCSHDRCMPLT, bpf:iow($B, 117, ?SIZEOF_U_INT)).

ioc(Inout, Group, Num, Len) ->
    Inout bor ((Len band ?IOCPARM_MASK) bsl 16) bor (Group bsl 8) bor Num.

iow(G,N,T) ->
    ioc(?IOC_IN, G, N, T).

To set the "header complete" mode from within Erlang:

procket:ioctl(Socket, ?BIOCSHDRCMPLT, <<1:32/native>>).

Or using the bpf module:

bpf:ctl(Sockt, hdrcmplt, true).

BPF Filtering

bpf has a bytecode language to filter out unwanted packets. The bytecode is generated by a set of macros in the bpf header file. For convenience in porting examples to Erlang, I defined macros wrapping the Erlang functions.

A BPF filtering program consists of an 8 byte instruction:

struct bpf_insn {
    u_short code;
    u_char  jt;
    u_char  jf;
    bpf_u_int32 k;
};
  • code: 2 bytes

The opcodes are a set of instructions for moving within the packet, testing values and control flow. The opcodes are OR'ed together.

  • jt: 1 byte

jump true: if the operation evaluates as true (non-0), jump this many instructions. Instructions are numbered from 0 (the statement following the test is instruction 0).

  • jf: 1 byte

jump false: if the operation evaluates as false (0), jump this many instructions. Instructions use a 0 offset, starting with the following instruction.

  • k: 4 bytes (the man page incorrectly defines this field as a u_long)

A value whose usage depends on the opcode.

An Example in C

I'll illustrate how the filters work by using an example from the man page. This example filters out all packets except reverse proxy requests:

struct bpf_insn insns[] = {
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_REVARP, 0, 3),
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, REVARP_REQUEST, 0, 1),
    BPF_STMT(BPF_RET+BPF_K, sizeof(struct ether_arp) +
        sizeof(struct ether_header)),
    BPF_STMT(BPF_RET+BPF_K, 0),
};
  • BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12)

    The BPF_STMT macro takes an opcode and a k value as arguments.

    • BPF_LD: load value (move to offset)

    • BPF_H: load a half word value (2 bytes)

    • BPF_ABS: use an absolute offset from the beginning of the packet

    • 12: move 12 bytes into the ethernet frame

    An ethernet frame looks like (the numbers are bytes):

    1. Destination MAC Address:6
    2. Source MAC Address:6
    3. Type:2

    A 12 byte offset leaves the program at the ethernet type.

  • BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_REVARP, 0, 3)

    The BPF_JUMP macro arguments are: opcode, k, jt, jf

    • BPF_JMP: A branching operation, depending on whether the test evaluates as true or not

    • BPF_JEQ: the equality of the value at this offset (defined in the previous instruction as a half-word) is tested against the value held in the k field.

      If the value is equal to ETHERTYPE_REVARP (0x8035), the packet is a reverse ARP packet and control drops to the next statement. If the statement is false (for example, it is an IP packet), control jumps to the final statement:

      BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_REVARP, 0, 3),
      0: BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20),
      1: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, REVARP_REQUEST, 0, 1),
      2: BPF_STMT(BPF_RET+BPF_K, sizeof(struct ether_arp) +
          sizeof(struct ether_header)),
      3: BPF_STMT(BPF_RET+BPF_K, 0),
      
  • BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20)

The packet is a reverse arp packet. Move to offset 20. A reverse ARP packet looks like (numbers are bytes):

  1. Hardware Type:2
  2. Protocol Type:2
  3. Hardware Length:1
  4. Protocol Length:1
  5. Operation:2
  6. Sending Hardware Address:6
  7. Sending IP Address:4
  8. Target Hardware Address:6
  9. Target IP Address:4

An offset of 20 (ethernet frame = 14, so 6 bytes into the ARP packet) puts the program at the ARP operation, a 2 byte (half-word) field.

  • BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, REVARP_REQUEST, 0, 1)

If the value of the offset is equal to REVARP_REQUEST (3) move to the next instruction.

Otherwise, jump 1 instruction to the final return statement:

    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, REVARP_REQUEST, 0, 1),
    0: BPF_STMT(BPF_RET+BPF_K, sizeof(struct ether_arp) +
        sizeof(struct ether_header)),
    1: BPF_STMT(BPF_RET+BPF_K, 0),
  • BPF_STMT(BPF_RET+BPF_K, sizeof(struct ether_arp) + sizeof(struct ether_header))

BPF_RET: return the value in the k field. "k" number of bytes of the packet will be returned to the bpf device.

  • BPF_STMT(BPF_RET+BPF_K, 0)

0 bytes is returned to the bpf device. The packet is dropped.

An Example in Erlang

Here is another example from the bpf man page: sniffing finger requests. Yes, the bpf man page appears to have been written in a long ago age, when reverse ARP and finger requests roamed the networks.

First the C version:

struct bpf_insn insns[] = {
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 10),
    BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 23),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 0, 8),
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 20),
    BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, 0x1fff, 6, 0),
    BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 14),
    BPF_STMT(BPF_LD+BPF_H+BPF_IND, 14),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 79, 2, 0),
    BPF_STMT(BPF_LD+BPF_H+BPF_IND, 16),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 79, 0, 1),
    BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    BPF_STMT(BPF_RET+BPF_K, 0),
};

Now the Erlang version (with comments):

-include("bpf.hrl").

-define(ETHERTYPE_IP, 16#0800).
-define(IPPROTO_TCP, 6).

finger() ->
    [   
        % Ethernet
        ?BPF_STMT(?BPF_LD+?BPF_H+?BPF_ABS, 12),                     % offset = Ethernet Type
        ?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, ?ETHERTYPE_IP, 0, 10),  % type = IP

        % IP
        ?BPF_STMT(?BPF_LD+?BPF_B+?BPF_ABS, 23),                     % offset = ip protocol
        ?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, ?IPPROTO_TCP, 0, 8),    % protocol = TCP

        ?BPF_STMT(?BPF_LD+?BPF_H+?BPF_ABS, 20),                     % offset = flags, frag offset
        ?BPF_JUMP(?BPF_JMP+?BPF_JSET+?BPF_K, 16#1fff, 6, 0),        % frag offset: mask the top 3 bits
                                                                    %  and AND with 1's
                                                                    %  If any non-0 value is returned from the
                                                                    %  AND (i.e., frag offset is non-0), jump
                                                                    %  to the end and drop the packet

        ?BPF_STMT(?BPF_LDX+?BPF_B+?BPF_MSH, 14),                    % offset = IP version, IP header length
                                                                    %  Load the header length into the index
                                                                    %  register

        % TCP
        ?BPF_STMT(?BPF_LD+?BPF_H+?BPF_IND, 14),                     % offset = TCP source port
                                                                    %  Move from offset 14 (start of IP packet)
                                                                    %  plus the value held in the index register
                                                                    %  (IP header length). Puts us at the start
                                                                    %  of the TCP packet (at the source port)
        ?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, 79, 2, 0),              % source port = 79
        ?BPF_STMT(?BPF_LD+?BPF_H+?BPF_IND, 16),                     % offset = destination port
        ?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, 79, 0, 1),              % destination port = 79
        ?BPF_STMT(?BPF_RET+?BPF_K, 16#FFFFFFFF),                    % return: entire packet
        ?BPF_STMT(?BPF_RET+?BPF_K, 0)                               % return: drop packet
].

Note that this filter does not check if the packet is IPv4.

Loading the Filter

To load the filter, another ioctl (BIOCSETF) is called. The ioctl takes a structure with a length and a pointer to the instructions:

struct bpf_program {
    u_int bf_len;
    struct bpf_insn *bf_insns;
};

The length field is set to the number of instructions, not the size of the instructions. In the first example (the reverse ARP filter), the length is 6.

In Erlang, the filter is loaded using:

Insn = finger(),
{ok, Code, [Res]} = procket:alloc([
    <<(length(Insn)):4/native-unsigned-integer-unit:8>>,
    {ptr, list_to_binary(Insn)}
]),
case procket:ioctl(Socket, ?BIOCSETF, Code) of
    {ok, _} ->
        procket:buf(Res);
    Error ->
        Error
end.

Or, more simply, using the bpf module:

bpf:ctl(Socket, setf, finger()).

BPF Filter Examples

BPF Packet Capture

Capturing packets is as simple as reading from the bpf device.

To work with file descriptors, procket needs to support the read and write system calls.

{ok, Buf} = procket:read(FD, Length).

The captured packet is not an ethernet frame (or a frame of whatever datalink type you happen to be sniffing): it's a buffer prepended with a header containing information about the packet that follows.

struct bpf_hdr {
    struct timeval bh_tstamp;     /* time stamp */
    u_long bh_caplen;             /* length of captured portion */
    u_long bh_datalen;            /* original length of packet */
    u_short bh_hdrlen;            /* length of bpf header (this struct
                                     plus alignment padding */
};
  • bh_timestamp differs between 32 and 64-bit platforms

    • On a 32-bit platform, struct timeval has a 4 byte sec and usec field.

    • On a 64-bit platform, struct timeval has an 8 byte sec and a 4 byte usec field.

  • bh_caplen is the size of the captured packet that follows

  • bh_datalen is the real packet length. The packet may have been truncated.

  • bh_hdrlen is the real size of the bpf_hdr structure which may be padded due to alignment

To determine the start of the next packet, the bpf header provides a macro. Similarly the Erlang bpf module provides a module to calculate the proper offset:

?BPF_WORDALIGN(Hdrlen + Caplen).

The bpf module will do the calculations for you:

{ok, Length} = bpf:ctl(Socket, blen),
{ok, Buf} = procket:read(Socket, Length),
{bpf_buf, Time, Datalen, Packet, Rest} = bpf:buf(Socket).

Here is a complete example of using the bpf module to dump packets. It can be used with the filt module, if you want to play with the fcode filtering. To start it:

% en1 is the wireless device
% rule = ( src host 10.10.10.10 or dst host 10.10.10.10 ) and ( src port 80 or dst port 80)
dump:start("en1", filt:tcp({10,10,10,10}, 80)).

BPF Packet Generation

Generating crafted packets is even simpler: write the packet to the bpf device. The packet must be valid.

Be careful, crafted packets can have strange effects. On Mac OS X, I found a few odd cases that caused the network interface to go down. For example, sending out ARP replies from a spoofed MAC address or even advertising 0.0.0.0 with the macbook's MAC address.

This example acts as a sort of peer to peer QoS, should you ever need to kick someone off of a local network. This code acts in 2 ways:

  • It continually arps for whatever IP the target advertises. Eventually, the target system will give up and go offline.

  • Since gratuious arps are sent aggressively, the gateway will consider our MAC address to be the MAC address for the target's IP address and will send packets to us, effectively cutting off the target system.

To use the code, you will need to know the target's MAC and IP address.

% our interface: en1
% target:
%  MAC = "00:aa:bb:cc:dd:ee"
%  IP = "10.10.10.10"
annoy:er("en1", "00:aa:bb:cc:dd:ee", "10.10.10.10").

Friday, December 3, 2010

ICMP Ping in Erlang, part 2

I've covered sending ICMP packets from Erlang using BSD raw sockets and Linux's PF_PACKET socket option.

gen_icmp tries to be a simple interface for ICMP sockets using the BSD raw socket interface for portability. It should work on both Linux and BSD's (I've tested on Ubuntu and Mac OS X).

Sending Ping's

To ping a host:
1> gen_icmp:ping("erlang.org").
[{ok,{193,180,168,20},
     {{33786,0,129305},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}}]
The response is a list of 3-tuples. The third element is a 2-tuple holding the ICMP echo request ID, the sequence number, the elapsed time and the payload.

A bad response looks like:
2> gen_icmp:ping("192.168.213.4").
[{{error,host_unreachable},
  {192,168,213,4},
  {{34491,0},
   <<69,0,0,84,0,0,64,0,64,1,14,220,192,168,213,119,192,
     168,213,4,8,0,196,...>>}}]
The argument to gen_icmp:ping/1 takes either a string or a list of strings. For example, to ping every host on a /24 network:
1> gen_icmp:ping([ {192,168,213,N} || N <- lists:seq(1,254) ]).
[{{error,host_unreachable},
  {192,168,213,254},
  {{54370,0},
   <<69,0,0,84,0,0,64,0,64,1,13,226,192,168,213,119,192,
     168,213,254,8,0,82,...>>}},
 {{error,host_unreachable},
  {192,168,213,190},
  {{54370,0},
   <<69,0,0,84,0,0,64,0,64,1,14,34,192,168,213,119,192,168,
     213,190,8,0,...>>}},
gen_icmp:ping/1 takes care of opening and closing the raw socket. This operation is somewhat expensive because Erlang is spawning a setuid executable to get the socket. If you'll be doing a lot of ping's, it's better to keep the socket around and use ping/3:
1> {ok,Socket} = gen_icmp:open().
{ok,<0.308.0>}

2> gen_icmp:ping(Socket, ["www.yahoo.com", "erlang.org", {192,168,213,1}],
    [{id, 123}, {sequence, 0}, {timeout, 5000}]).
[{ok,{193,180,168,20},
     {{123,0,126270},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}},
     {ok,{69,147,125,65},
     {{123,0,29377},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}},
     {ok,{192,168,213,1},
     {{123,0,3586},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}}]

3> gen_icmp:close(Socket).
ok

Creating Other ICMP Packet Types


ICMP destination unreachable and time exceeded packets return at least the first 64 bits of the header and payload of the original packet. Here is an example of generating an ICMP port unreachable for a fake TCP packet sent to port 80.
-module(icmperr).
-export([unreachable/3]).
-include("epcap_net.hrl").

-define(IPV4HDRLEN, 20).
-define(TCPHDRLEN, 20).

unreachable(Saddr, Daddr, Data) ->
    {ok, Socket} = gen_icmp:open(),

    IP = #ipv4{
        p = ?IPPROTO_TCP,
        len = ?IPV4HDRLEN + ?TCPHDRLEN + byte_size(Data),
        saddr = Daddr,
        daddr = Saddr
    },

    TCP = #tcp{
        dport = 80,
        sport = crypto:rand_uniform(0, 16#FFFF),
        seqno = crypto:rand_uniform(0, 16#FFFF),
        syn = 1
    },

    IPsum = epcap_net:makesum(IP),
    TCPsum = epcap_net:makesum([IP, TCP, Data]),

    Packet = <<
        (epcap_net:ipv4(IP#ipv4{sum = IPsum}))/bits,
        (epcap_net:tcp(TCP#tcp{sum = TCPsum}))/bits,
        Data/bits
        >>,

    ICMP = gen_icmp:packet([
        {type, ?ICMP_DEST_UNREACH},
        {code, ?ICMP_UNREACH_PORT}
    ], Packet),

    ok = gen_icmp:send(Socket, Daddr, ICMP),
    gen_icmp:close(Socket).

To create the IPv4 and TCP headers, we make the protocol records and use the epcap_net module functions to encode the headers with the proper checksums. For creating the ICMP packet, we use the gen_icmp:packet/2 function (which again simply calls epcap_net).


ICMP Ping Tunnel


We can tunnel any data we like in the payload of an ICMP packet. In this example, we'll use ICMP echo requests to tunnel an ssh connection between 2 hosts. The ICMP echo replies sent back by the peer OS ensure the data was received, like the ACK in a TCP connection.

The tunnel exports 2 functions:

  • ptun:server(ClientAddress, LocalPort) -> void()

    Types   ClientAddress = tuple()
                LocalPort = 0..65534
    
        ClientAddress is the IPv4 address of the peer represented as a tuple.
    
        The server listens on LocalPort for TCP connections and will close
        the port after a TCP client connects.  Data received on this port
        will be sent to the peer as the payload of the ICMP packets.
    


  • ptun:client(ServerAddress, LocalPort) -> void()

    Types   ServerAddress = tuple()
                LocalPort = 0..65534
    
        ServerAddress is the IPv4 address of the peer.
    
        When the client receives an ICMP echo request, the client opens a
        TCP connection to the LocalPort on localhost, proxying the data.
    
To start the tunnel, you'll need 2 hosts. In this example, 192.168.213.7 is the client and 192.168.213.119 is the server. 192.168.213.7 forwards any tunnelled data it receives to a local SSH server. On 192.168.213.7:
1> ptun:client({192,168,213,119}, 22).
On 192.168.213.119:
1> ptun:server({192,168,213,7}, 8787).
Open another shell and start an SSH connection to port 8787 on localhost:
ssh -p 8787 127.0.0.1
<...>
$ ifconfig eth0 | awk '/inet addr/{print $2}'
addr:192.168.213.7

Tuesday, October 26, 2010

Fun with Raw Sockets in Erlang: Bridging

Hosts connecting to another system on the same network map the protocol address (e.g., IPv4 address) of the destination host to a hardware address (e.g., ethernet MAC address) using the ARP protocol. Clients on different networks can communicate by having a device forward the packets between networks. These devices, usually multi-homed and spanning networks, forward packets by re-writing the packet headers: for example, the ethernet header (a bridge), the IP header (a router) or the IP and TCP headers (a NAT).

An ethernet II header:

  • Preamble:42
  • Start of Field Delimeter:8
  • Destination Host:48
  • Source Host:48
  • Type:16
  • Protocol Payload:N
  • CRC:16

Not all of these fields will be visible to processes running on the operating system.

  • The Preamble starts the Ethernet frame (not available to the OS)
  • The SOF Delimiter marks the start of the destination host address field (not available to the OS)
  • The Destination Host is the 6 byte MAC address of the target host
  • The Source Host is the 6 byte MAC address of the sending host
  • The Type represents the protocol held in the payload. Common types are IPv4 (ETH_P_IP (16#0800)), ARP (ETH_P_ARP (16#0806)) and IPv6 (ETH_P_IPV6 (16#86DD)).
  • A trailing CRC checksum of the packet (frame check sequence) (usually not available to the OS)

    Ethernet frames can apparently include other trailing data, leading to trailing junk when doing packet captures.

    When capturing data off the network, it's important to properly calculate the size of the packet. For example, for an IPv4 TCP packet:

    IP length - (IP header length * 4) - (TCP offset * 4)
    

The ethernet header has fixed size fields and so does not explicitly include a field for length. Interestingly, Ethernet 802.3 frames use the Type field for the length (according to Wikipedia 802.3 packets can be distinguished from Ethernet II packets by:

  1. if the value of the Type field is equal to or less than 1500 bytes (maximum frame length), the frame is Ethernet 802.3 and the value represents the length of the frame
  2. if the value of the Type field is equal to or greather than 1536, the frame is Ethernet II and the value represents the protocol type of the encapsulated packet

    The protocol of 802.3 frames is always IPX.

  3. if the value of the Type field is between 1500 and 1536, the behaviour is undefined)

An Erlang binary representation of an Ethernet II frame:

<<
Dhost:6/bytes,        % destination MAC address
Shost:6/bytes,        % source MAC address
Type:16               % protocol type, usually ETH_P_IP
>>

Using Erlang to Bridge Packets

To go along with the ARP poisoning, we need a sort of "one armed bridging" to forward packets from our spoofing host to the real destinations. Once we have the raw ethernet frames, doing the bridging is quite simple. For the complete code, see herp on GitHub (yes, the herp is what you get when you've been promiscuous. I hear like 80% of adults have it).

I won't include much of the code here because it's so trivial. The bridging process captures packets off the network and pattern matches on the headers:

filter(#ether{shost = MAC}, _, #state{mac = MAC}) ->
    ok;
filter(#ether{type = ?ETH_P_IP}, Packet, State) ->
    {#ipv4{daddr = DA}, _} = epcap_net:ipv4(Packet),
    filter1(DA, Packet, State);
filter(_, _, _) ->
    ok.

filter1(IP, _, #state{ip = IP}) ->
    ok;
filter1(IP, Packet, #state{gw = GW}) ->
    MAC = case packet:arplookup(IP) of
        false -> GW;
        {M1,M2,M3,M4,M5,M6} -> <<M1,M2,M3,M4,M5,M6>>
    end,
    bridge(MAC, Packet).
  1. Check if the frame has our MAC address
  2. Retrieve the IP address from the frame and check the system ARP cache. A real bridge would monitor the network for ARP packets and cache the results.
  3. If the IP exists in our ARP cache, use the MAC address, otherwise, send it to the gateway (it's possible the IP address does not exist in the system ARP cache and will be wrongly forwarded to the gateway)
If the frame should be bridged, we create a new frame with the source hardware address set to our host's MAC address. The IP header and payload are not touched.
handle_call({packet, DstMAC, Packet}, _From, #state{
        mac = MAC,
        s = Socket,
        i = Ifindex} = State) ->

    Ether = epcap_net:ether(#ether{
            dhost = DstMAC,
            shost = MAC,
            type = ?ETH_P_IP
        }),

    packet:send(Socket, Ifindex, list_to_binary([Ether, Packet])),
    {reply, ok, State};

It'd be interesting to experiment with making herp into a traditional network bridge: quite likely very slow, but also redundant, distributed and fault tolerant.

Monday, October 25, 2010

Fun with Raw Sockets in Erlang: ARP Poisoning

On IP ethernet networks, hosts use a peer to peer method called ARP (address resolution protocol) to discover the hardware address of the peer with which they intend to communicate.

IPv4 ethernet ARP packets are specified as:
  • Hardware Type:16
  • Protocol Type:16
  • Hardware Length:8
  • Protocol Length:8
  • Operation:16
  • Sending Hardware Address:48
  • Sending IP Address:32
  • Target Hardware Address:48
  • Target IP Address:32

The numbers after the colon represent the size in bits of the field.

  • The Hardware Type of the network is ethernet, so the value is set to ARPHRD_ETHER (1)
  • The Protocol Type of the network is IPv4, so the value is set to ETH_P_IP (0x0800)
  • The Hardware Length of an ethernet MAC address is 6 bytes
  • The Protocol Length of an IPv4 address is 4 bytes
  • Operation is usually an ARP request (ARPOP_REQUEST (1)) or reply (ARPOP_REPLY (2))
  • The Sending Hardware Address is the MAC address of the host sending the ARP packet
  • The Sending IP Address is the IPv4 address of the host sending the ARP packet
  • The Target Hardware Address is the MAC address of the host receiving the ARP packet

    The target address may be the ethernet broadcast address (FF:FF:FF:FF:FF:FF or 00:00:00:00:00:00) which results in all hosts receiving the ARP packet.

  • The Target IP Address is the IPv4 address of the host sending the ARP packet
The corresponding Erlang representation of an IPv4 ethernet ARP packet is:
<<Hrd:16, Pro:16,
Hln:8, Pln:8, Op:16,
Sha:48, Sip:32,
Tha:48, Tip:48>>

Behaviour of the ARP Cache

ARP caches are key/value stores holding a mapping of the protocol address to the hardware address. To prevent caching of stale data, entries eventually expire. The expiry timeout varies; for example, on MS Windows, arp entries are kept for 2 minutes if another session to the remote host is not initiated. If a session is initiated within the 2 minute period, the ARP cache expiry time is extended to 10 minutes.

ARP is opportunistic and trust-based. If a host sees an ARP request or reply for which it is not the target, the host may cache the information. However, caching all requests would be pointless, since arp lookup would be slow on a network with a large number of peers with which the host might never communicate.

Gratuitous ARPs

ARPs are gratuitous when no request was made for the information. Gratuitous ARPs are useful for:
  • discovering IP conflict
  • IP take over: in a high availability cluster of servers, one of the hosts is active (holding the service IP address). In the event of a failure of the active node, one of the slave nodes can assume the service IP address, sending a gratuitous ARP to inform the other nodes and the gateway that the IP address is associated with a new MAC address
An Erlang binary representing a gratuitous ARP looks like:
<<
1:16,                                 % hardware type
16#0800:16,                           % protocol type
6:8,                                  % hardware length
4:8,                                  % protocol length
2:16,                                 % operation: ARPOP_REPLY
0,1,2,3,4,5,                          % sending MAC address
192,168,1,100,                        % sending IPv4 address
16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,  % target MAC address: ethernet broadcast
192,168,1,100                         % target IPv4 address: set to sending address
>>
Behaving badly by gratuitously arp'ing for all the IP addresses on the network will DoS the other hosts, eventually forcing them to report a network error condition and go offline.

Sending an ARP Reply

To send an ARP packet from Erlang, we'll use the procket module on GitHub. The functions in procket used for these examples are unfortunately Linux-only. For this example, the binaries are manually specified. The epcap_net module on GitHub has convenience functions for creating and decomposing ARP packets into a record structure. If the network was set up like:
  • arping Erlang node: 10.11.11.9
  • source host: 10.11.11.10
  • target host (doesn't exist): 10.11.11.11
Login to another host on your network (the source node) and run tcpdump:
tcpdump -n -e arp
In another window, run the code:
$ erl -pa /path/to/procket/ebin
Erlang R14B01 (erts-5.8.2) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.2  (abort with ^G)
1> carp:send({10,11,11,11}). % Use an address on your network
If the MAC address of the Erlang node is 00:aa:bb:cc:dd:ee, then on the other host you should see something like:
00:59:35.051302 00:aa:bb:cc:dd:ee > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 60: arp reply 10.11.11.11 is-at 00:aa:bb:cc:dd:ee
Check the ARP cache on the source node:
arp -an
The ARP entry may not exist. Since ARP caching is opportunistic, it is up to a host to decide whether it will optimize future connections by caching an unsolicited entry. To force an ARP cache entry on the remote host, ping the fake IP address:
ping 10.11.11.11
Then, in the Erlang shell, run carp:send/1. Run "arp -an" again on the remote host. The ARP cache entry for 10.11.11.11 should now be there.
? (10.11.11.11) at 00:aa:bb:cc:dd:ee [ether] on eth0
If you run tcpdump on the host doing the ARP'ing, you should see ICMP traffic for 10.11.11.11:
01:13:36.075040 IP 10.11.11.10 > 10.11.11.11: ICMP echo request, id 35604, seq 17, length 64
01:13:36.088794 IP 10.11.11.10 > 10.11.11.11: ICMP echo request, id 35604, seq 18, length 64
01:13:36.106572 IP 10.11.11.10 > 10.11.11.11: ICMP echo request, id 35604, seq 19, length 64
Since this IP address is not bound to any interface on your host, there will, of course, be no reply.

Poisoning the ARPs

On old networks using hubs or open networks using 802.11 access points, data is broadcasted to all the hosts. Running a packet sniffer displays the network activity for everyone, not just yourself.

Switched networks learn the MAC address of the host connected to the port of the switch and send data only to the recipient. ARP poisoning or spoofing fools other hosts on the network to send data to the host under your control. If your host spoofs the gateway, you'll also see the internet traffic. Obviously, if your host is not somehow routing, the packets will go nowhere, performing a denial of service on your network. But you will be able to see the data other hosts are sending.

Using the code we ran earlier, we can test ARP spoofing. You'll need 3 hosts: one doing the poisoning and two victims, a source and a target. The process is the same as above. For example, if 10.11.11.11 were a real host, we would have convinced 10.11.11.10 to send its data through our host.

I've put my experiment in progress with ARP poisoning, farp, on GitHub. farp works by replying to ARP requests with our host's MAC address. It can also optionally send out gratuitous arps as the gateway to speed up the process.

Tuesday, August 31, 2010

Dumping Payloads with epcap and procket

epcap and procket allow Erlang code to sniff data off of a network. With either, once the packets are in the Erlang VM, manipulating the contents is straight forward using pattern matching.

Which to use?

epcap and procket sort of overlap in functionality. The main differences, at the moment, are:
  • portability

    epcap: should work on any Unix with pcap installed

    procket: for sniffing, procket uses Linux's PF_PACKET socket option, so Linux only. I plan to add support for BPF someday, so maybe in the future procket will support BSD as well.

  • safety

    epcap: runs as a separate system process. Any bugs in epcap will not affect the Erlang VM.

    procket: linked into the Erlang VM using the NIF interface. Bugs may stall or crash the VM.

  • packet generation

    epcap: can only sniff packets

    procket: can generate whole packets. Again, currently Linux only, but should work under BSD's, like Mac OS X, when BPF is supported.

    Raw sockets (for example, generating ICMP echo packets) work under BSD as well.

    In fact, it should be possible to combine the power of procket, epcap, and BSD to send and receive arbitrary TCP or UDP packets now (since TCP/UDP raw sockets can send data only, we need to use epcap to sniff the response).

  • filtering

    epcap: packet filtering rules are processed in C, either in the kernel or in a library.

    procket: all packets are received and must be filtered by an Erlang process

Decapsulating Packets

procket and epcap have different ways of being started and reading packets but once the raw packets are received by an Erlang process, they can be decapsulated with a small module ("epcap_net.erl") distributed with epcap. Say, for example, we wanted to monitor http requests and write out a file containing just the client side: We request a raw socket from procket and then loop, polling the socket every 10ms for data. We look for established connections by matching packets with only the ACK bit set (we ignore connections in progress) and spawn another process to accumulate the data. When we see a packet indicating a connection has been closed, we tell the spawned process to write out its state to a file. The spawned process will also terminate if it reaches an arbitrary timeout. You've probably noticed that this code mimics some of the functionality of OTP behaviours. I wrote it this way for simplicity, but it certainly could be more compactly (and elegantly) written as a gen_server or a gen_fsm.

Matching on Payloads

A similar example with epcap: match on all http requests and write the complete transaction to a file based on the etag. epcap doesn't require polling as with procket. Instead, messages are received from the port similar to gen_tcp in {active, true} mode. The message contains some additional information about the length and time of the captured packet. For this example, we ignore it. We're only interested in the packet contents.

Similar to the procket example, we loop, blocking in receive. When data is received, we check if the connection is in the established state, spawning a process to accumulate the data if we haven't seen this session before.

Finally, we write out the data to the file system when the connection is closed, using the value of the "ETag" header for the file name. For succintness, I used a regular expression to match on the payload. Probably better to write a parser.

Thanks, Zabrane, for suggesting this post!

Thursday, July 1, 2010

Fun with Raw Sockets in Erlang: Finding MAC and IP Addresses

(See the update for versions of some of these functions in standard Erlang).

When working with PF_PACKET raw sockets, the caller needs to provide the source/destination MAC and IP addresses.
Playing with a spoofing DNS proxy, I got tired of hardcoding the addresses, then WTF'ing every time I switched networks. So I added some functions to procket to lookup the system network interface and its MAC and IP addresses.

Retrieving the MAC Address of an Interface


Under Linux, getting the MAC address of an interface involves calling an ioctl() with the request set to SIOCGIFHWADDR and passing in a struct ifreq.

Here is the code to do so in C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <net/if.h>
#include <netinet/ether.h>


int
main(int argc, char *argv[])
{
    int s = -1;

    struct ifreq ifr = {0};
    char *dev = NULL;
    struct sockaddr *sa;

    dev = strdup((argc == 2 ? argv[1] : "eth0"));

    if (dev == NULL)
        err(EXIT_FAILURE, "strdup");

    if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err(EXIT_FAILURE, "socket");

    (void)memcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1);

    if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0)
        err(EXIT_FAILURE, "ioctl");

    sa = (struct sockaddr *)&ifr.ifr_hwaddr;

    (void)printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
            sa->sa_data[0], sa->sa_data[1], sa->sa_data[2], sa->sa_data[3],
            sa->sa_data[4], sa->sa_data[5]);

    free(dev);

    exit (EXIT_SUCCESS);

}

The equivalent in Erlang uses procket:ioctl/2
macaddress(Socket, Dev) ->
    {ok, <<_Ifname:16/bytes,
        ?PF_INET:16,                       % family
        SM1,SM2,SM3,SM4,SM5,SM6,    % mac address
        _/binary>>} = procket:ioctl(Socket,
        ?SIOCGIFHWADDR,
        list_to_binary([
                Dev, <<0:((15*8) - (length(Dev)*8)), 0:8, 0:128>>
            ])),
    {SM1,SM2,SM3,SM4,SM5,SM6}.
Results may differ depending on the endian-ness of your platform.

Retrieving the IP Address of an Interface


The IP address of an interface can be obtained by another ioctl() with a request value of SIOCGIFADDR. In C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <net/if.h>


int
main(int argc, char *argv[])
{
    int s = -1;

    struct ifreq ifr = {0};
    char *dev = NULL;
    struct sockaddr_in *sa;

    dev = strdup((argc == 2 ? argv[1] : "eth0"));

    if (dev == NULL)
        err(EXIT_FAILURE, "strdup");

    if ( (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        err(EXIT_FAILURE, "socket");

    (void)memcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1);
    ifr.ifr_addr.sa_family = PF_INET;

    if (ioctl(s, SIOCGIFADDR, &ifr) < 0)
        err(EXIT_FAILURE, "ioctl");

    sa = (struct sockaddr_in *)&ifr.ifr_hwaddr;

    (void)printf("%s\n", inet_ntoa(sa->sin_addr));

    free(dev);

    exit (EXIT_SUCCESS);

}

And the Erlang version:
ipv4address(Socket, Dev) ->
    {ok, <<_Ifname:16/bytes,
        ?PF_INET:16/native, % sin_family
        _:16,               % sin_port 
        SA1,SA2,SA3,SA4,    % sin_addr
        _/binary>>} = procket:ioctl(Socket,
            ?SIOCGIFADDR,
            list_to_binary([
                Dev, <<0:((15*8) - (length(Dev)*8)), 0:8>>,
                <<?PF_INET:16/native,       % family
                0:112>>
            ])),
    {SA1,SA2,SA3,SA4}.

Looking Up an IP Address in the ARP Cache


ARP cache lookups can be done by using:
ioctl(socket, SIOCGARP, struct arpreq);
But utilities on Linux just seem to parse /proc/net/arp:
arplookup({IP1,IP2,IP3,IP4}) ->
    {ok, FD} = file:open("/proc/net/arp", [read,raw]),
    arploop(FD, inet_parse:ntoa({IP1,IP2,IP3,IP4})).

arploop(FD, Address) ->
    case file:read_line(FD) of
        eof ->
            file:close(FD),
            not_found;
        {ok, Line} ->
            case lists:prefix(Address, Line) of
                true ->
                    file:close(FD),
                    M = string:tokens(
                        lists:nth(?HWADDR_OFF, string:tokens(Line, " \n")), ":"),
                    list_to_tuple([ erlang:list_to_integer(E, 16) || E <- M ]);
                false -> arploop(FD, Address)
            end
    end.

Getting a List of Interfaces


To get the list of interfaces on a system, yet another ioctl() is used, this time passing in SIOCGIFCONF and this structure as arguments:
struct ifconf
{
    int ifc_len;            /* Size of buffer.  */
    union
    {
        __caddr_t ifcu_buf;
        struct ifreq *ifcu_req;
    } ifc_ifcu;
};
Here is an example of retrieving the interface list in C.

The ioctl() takes, as an argument, a structure using a length and a pointer to a buffer. procket doesn't have a way of allocating a piece of memory though it could be modified to have an NIF that allocates a binary and returns the address of the binary as an integer. Functions could then pass in the memory address but a buggy piece of Erlang code might pass in the wrong value and crash the VM. (Edit: The erl_nif interface in Erlang R14A supports safely passing a reference to a block of memory between functions using enif_alloc_resource() to create a "Resource Object".)

Instead, I simply parse the output of /proc/net/dev. For example, here is the output on my laptop:

$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:  526441    3620    0    0    0     0          0         0   526441    3620    0    0    0     0       0          0
  eth0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
 wifi0:10960093   26897    0    0    0  1578          0         0   734661    4876    0    0    0     0       0          0
  ath0:14422892   12536    0    0    0     0          0         0   576599    4706    0    0    0     0       0          0
Even nastier than the arp cache lookup, since I resorted to using regular expressions.
iflist() ->
    {ok, FD} = file:open("/proc/net/dev", [raw, read]),
    iflistloop(FD, []).

iflistloop(FD, Ifs) ->
    case file:read_line(FD) of
        eof ->
            file:close(FD),
            Ifs;
        {ok, Line} ->
            iflistloop(FD, iflistmatch(Line, Ifs))
    end.

iflistmatch(Data, Ifs) ->
    case re:run(Data, "^\\s*([a-z]+[0-9]+):", [{capture, [1], list}]) of
        nomatch -> Ifs;
        {match, [If]} -> [If|Ifs]
    end.

Finding the Default Interface


In spood, I took the easy way and just sort of guessed. A proper solution would check the routing table. Instead I look for the first interface without a local IP address:
device() ->
    {ok, S} = procket:listen(0, [{protocol, udp}, {family, inet}, {type, dgram}]),
    [Dev|_] = [ If || If <- packet:iflist(), ipcheck(S, If) ],
    procket:close(S),
    Dev.

ipcheck(S, If) ->
    try packet:ipv4address(S, If) of
        {127,_,_,_} -> false;
        {169,_,_,_} -> false;
        _ -> true
    catch
        error:_ -> false
    end.

Update: Using the inet Module

After having gone through all the above, I discovered that the inet module, which is part of the Erlang standard library, is able to retrieve information about the local interfaces.

inet has 2 functions:
  • getiflist/0: retrieve a list of all the local interfaces, e.g., {ok, ["eth0", "eth1"]}
  • ifget/2: retrieve interface attributes. The arguments can be:
    • addr: IP address of interface
    • hwaddr: the MAC address of the interface. Works on Linux, doesn't work on Mac OS X (returns an empty list).
      (Update: I've submitted a patch to get the MAC address on Mac OS X)
    • dstaddr
    • netmask
    • broadcast
    • mtu
    • flags: returns the interface status, e.g., [up, broadcast, running, multicast]

For example:
  • To get a list of interfaces:
    1> inet:getiflist().
    {ok,["lo","eth0","eth1"]}
    
  • To retrieve the IP and MAC addresses of an interface:
    3> inet:ifget("eth0", [addr, hwaddr]).
    {ok,[{addr,{192,168,1,11}},{hwaddr,[0,11,22,33,44,55]}]}
    

Update2: Using inet:getifaddrs/0

As of R14B01, Erlang has a supported, cross-platform method for retrieving interface attributes. The functions returns a list holding the interface information. For example:
1> inet:getifaddrs().
{ok,[{"lo",
      [{flags,[up,loopback,running]},
       {hwaddr,[0,0,0,0,0,0]},
       {addr,{127,0,0,1}},
       {netmask,{255,0,0,0}},
       {addr,{0,0,0,0,0,0,0,1}},
       {netmask,{65535,65535,65535,65535,65535,65535,65535,
                 65535}}]}]

Thursday, June 24, 2010

Fun with Raw Sockets in Erlang: A Spoofing DNS Proxy

UDP Header

UDP headers are specified as:
  • Source Port:16
  • Destination Port:16
  • Length:16
  • Checksum:16

  • The Source Port is a 2 byte value representing the originating port
  • The Destination Port is the 2 byte value specifying the target port
  • The Length is the size of the UDP header and packet in bytes. The size of the UDP header is 8 bytes.
  • The Checksum algorithm is the same as for TCP, involving the creation of an IP pseuduoheader.
    If the length of the UDP packet is an odd number of bytes, the packet is zero padded with an additional byte only for checksumming purposes.
The equivalent UDP header in Erlang is:
<<SourcePort:16,
DestinationPort:16,
Length:16,
Checksum:16>>
The pseudo-header used for checksumming is:
  • Source Address:32
  • Destination Address:32
  • Zero:8
  • Protocol:8
  • UDP Packet Length:16
  • UDP Header and Payload1:8
  • ...
  • UDP Header and PayloadN:8
  • optional padding:8
  • The Source Address from the IP header
  • The Destination Address from the IP header
  • The Protocol for UDP is 17
  • The UDP Packet Length in bytes, for both the UDP header and payload. The length of the IP pseudo-header is not included.
  • The UDP Header and Payload is the full UDP packet
  • If the UDP packet length is odd, an additional zero'ed byte is included for checksumming purposes. The extra byte is not used in the length computation or sent with the packet.
  • The length of the UDP packet is included in both the IP pseudo-header and the UDP header.
  • The checksum field of the UDP header is set to 0.
In Erlang, the pseudo-header is represented as:
<<SA1:8,SA2:8,SA3:8,SA4:8,  % bytes representing the IP source address
DA1:8,DA2:8,DA3:8,DA4:8,    % bytes representing the IP destination address
0:8,                        % Zero
6:8,                        % Protocol: TCP
UDPlen:16                % UDP packet size in bytes

SourcePort:16,
DestinationPort:16,
UDPlen:16,
0:16,
Data/binary,
0:UDPpad>>                  % UDPpad may be 0 or 8 bits

A Spoofing DNS Proxy

What?

spood is a spoofing DNS proxy with a vaguely obscene name. spood works by accepting DNS queries on localhost and then spoofing the source IP address of the DNS request using the Linux PF_PACKET interface. DNS replies are sniffed off the network and returned to localhost.

Why?

Maybe for using with IP over DNS tunnels?

How?

spood works with procket and epcap.

This Will Probably Be the First Page Returned For Searches For "Erlang Promiscuity"

While spood can run by spoofing its own IP address, it's more fun running it on a hubbed or public wireless network. To allow spood to sniff the network, I added support for setsockopt() to procket as an additional NIF. Promiscuous mode, under Linux, can be activated/deactivated globally by using an ioctl() or per application by using setsockopt(). To enable promiscuous mode, the application needs to call:
setsockopt(socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq))
Though obtaining a PF_PACKET socket requires root privileges, performing the setsockopt() call on the socket does not. mreq is a struct packet_mreq:
struct packet_mreq {
    int            mr_ifindex;    /* interface index */
    unsigned short mr_type;       /* action */
    unsigned short mr_alen;       /* address length */
    unsigned char  mr_address[8]; /* physical layer address */
};
  • mr_ifindex is the interface index returned by doing an ioctl() in host endian format
  • mr_type is set to PACKET_MR_PROMISC in host endian format
  • the reminder of the struct is zero'ed
The Erlang version looks like:
-define(SOL_PACKET, 263).
-define(PACKET_ADD_MEMBERSHIP, 1).
-define(PACKET_DROP_MEMBERSHIP, 2).
-define(PACKET_MR_PROMISC, 1).

promiscuous(Socket, Ifindex) ->
    procket:setsockopt(Socket, ?SOL_PACKET, ?PACKET_ADD_MEMBERSHIP, <<
        Ifindex:32/native,              % mr_ifindex: interface index
        ?PACKET_MR_PROMISC:16/native,   % mr_type: action
        0:16,                           % mr_alen: address length
        0:64                            % mr_address[8]:  physical layer address
        >>).

Sniffing Packets

Sniffing packets involves running procket:recvfrom/2 in a loop. Erlang's pattern matching makes filtering the packets simple. One trick is retrieving the default nameservers in Erlang.
{ok, PL} = inet_parse:resolv(
    proplists:get_value(resolv_conf, inet_db:get_rc(), "/etc/resolv.conf")),
NS = proplists:get_value(nameserver, PL).
inet_db:get_rc() will return the path to the system resolv.conf file. inet_parse has an undocumented function to parse resolv.conf and return the attributes as list of key/value pairs.

Spoofing Packets

Spoofing packets is done by constructing a packet consisting of the Ethernet, IP and UDP header and payload.
dns_query(SourcePort, Data, #state{
    shost = {SM1,SM2,SM3,SM4,SM5,SM6},
    dhost = {DM1,DM2,DM3,DM4,DM5,DM6},
    saddr = {SA1,SA2,SA3,SA4},
    daddr = {DA1,DA2,DA3,DA4}
    }) ->

    Id = 1,
    TTL = 64,

    UDPlen = 8 + byte_size(Data),
    IPlen = 20 + UDPlen,

    IPsum = epcap_net:makesum(
        <<
        % IPv4 header
        4:4, 5:4, 0:8, IPlen:16,
        Id:16, 0:1, 1:1, 0:1,
        0:13, TTL:8, 17:8, 0:16,
        SA1:8, SA2:8, SA3:8, SA4:8,
        DA1:8, DA2:8, DA3:8, DA4:8
        >>
    ),

    UDPpad = case UDPlen rem 2 of
        0 -> 0;
        1 -> 8
    end,

    UDPsum = epcap_net:makesum(
        <<
        SA1:8,SA2:8,SA3:8,SA4:8,
        DA1:8,DA2:8,DA3:8,DA4:8,
        0:8,
        17:8,
        UDPlen:16,

        SourcePort:16,
        53:16,
        UDPlen:16,
        0:16,
        Data/binary,
        0:UDPpad
        >>),

    <<
    % Ethernet header
    DM1:8,DM2:8,DM3:8,DM4:8,DM5:8,DM6:8,
    SM1:8,SM2:8,SM3:8,SM4:8,SM5:8,SM6:8,
    16#08, 16#00,

    % IPv4 header
    4:4, 5:4, 0:8, IPlen:16,
    Id:16, 0:1, 1:1, 0:1,
    0:13, TTL:8, 17:8, IPsum:16,
    SA1:8, SA2:8, SA3:8, SA4:8,
    DA1:8, DA2:8, DA3:8, DA4:8,

    % UDP header
    SourcePort:16,
    53:16,
    UDPlen:16,
    UDPsum:16,
    Data/binary
    >>.

Running spood

Setup isn't all automatic yet (but see the README, maybe this has changed). After everything is compiled, find the MAC and IP address of your client and name server. Then run:
erl -pa ebin deps/*/ebin
1> spood:start("eth0",
    {{16#00,16#aa,16#bb,16#cc,16#dd,16#ee}, {list, [{192,168,100,100}, {192,168,100,101}]}},
    {{16#00,16#11,16#22,16#33,16#44,16#55}, {192,168,100,1}}).
Where:
  • The first argument is your interface device name
  • The second argument is a 2-tuple composed of your source MAC address and a representation of what should be used for your client IP address. Unless you're ARP spoofing or have published the ARP entries yourself, the IP's should be of clients on the network.

    The second argument can be a tuple or a string representing an IP or a tuple consisting of the keyword "list" followed a list of IP addresses. The source IP for each query will be randomly chosen from the list.

  • The third argument is the name server MAC and IP address
Then test it:
$ nslookup
> server 127.0.0.1
Default server: 127.0.0.1
Address: 127.0.0.1#53
> www.google.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
www.google.com  canonical name = www.l.google.com.
Name:   www.l.google.com
Address: 173.194.33.104
If you happen to be running the sods client, you can use the DNS proxy by using the "-r" option:
sdt -r 127.0.0.1 sshdns.s.example.com
Update: Well, I've tested spood in the wild now and made a few changes. By default, spood will discover the IP addresses on your network and add them to the list of source addresses to spoof. spood now takes a proplist as an argument. However, if no argument is passed, spood will try to figure out your network by:
  • guessing which interface device to use
  • finding the MAC and IP address assigned to the device
  • looking up the MAC address of the name server in the ARP cache
The arguments to spood:start/1 is a proplist consisting of:
  • {dev, string() | undefined}
  • {srcmac, tuple()}
  • {dstmac, tuple()}
  • {saddr, tuple() | string() | discover | {discover, list()} | {list, list()}}
  • {nameserver, tuple() | undefined}

Or call spood:start() to use the defaults.