Pages

Thursday, December 3, 2009

epcap: An Erlang Packet Sniffer

In the last entry, I talked about using erlang ports to pass messages between a Unix and an Erlang process. The data was encoded using the ei library.

As we saw, parsing the packets in C isn't hard: we cast the packet to the header structures. For example, to get the source address of the packet, we would do:
struct *iph = NULL;
const u_char *pkt; /* the packet given to us by pcap */
struct in_addr src;

iph = (struct ip *)(pkt + sizeof(struct ether_header));

src = iph->ip_src;
Erlang has a reputation for elegant and robust binary parsing. Is that true?

Parsing the ethernet header from a packet looks like this:
ether(<<Dhost:6/bytes, Shost:6/bytes, Type:2/bytes, Packet/binary>>)
We specify bytes for convenience (the default is bits). Each variable corresponds to the C structure found in net/ethernet.h.
#define ETHER_ADDR_LEN      6

struct  ether_header {
    u_char  ether_dhost[ETHER_ADDR_LEN];
    u_char  ether_shost[ETHER_ADDR_LEN];
    u_short ether_type;
};
The binary is stored in a record:
-record(ether, {
dhost, shost, type, crc
}).
We return a tuple containing the header and the payload.
ether(<<Dhost:6/bytes, Shost:6/bytes, Type:2/bytes, Packet/binary>>) ->
Len = byte_size(Packet) - 4,
<<Payload:Len/bytes, CRC:4/bytes>> = Packet,
{#ether{
dhost = Dhost, shost = Shost,
type = Type, crc = CRC
}, Payload}.
We follow the same pattern for IPv4, TCP, UDP and ICMP. I took the code from an old project to write an Erlang TCP/IP stack, so the implementation is incomplete and very likely buggy, but it's good enough for demonstration purposes. I'll clean it up over time.

Finally, we have a port, a port driver and a parser. We can put the pieces together quickly.
start(L) ->
epcap:start(L),
loop().

loop() ->
receive
[{time, Time},{packet, Packet}] ->
try epnet:decapsulate(Packet) of
P -> dump(Time, P)
catch
error:Error ->
io:format("~s *** Error decoding packet (~p) ***~n~p~n", [timestamp(Time), Error, Packet])
end,
loop();
stop ->
ok
end.

timestamp(Now) when is_tuple(Now) ->
iso_8601_fmt(calendar:now_to_local_time(Now)).

iso_8601_fmt(DateTime) ->
{{Year,Month,Day},{Hour,Min,Sec}} = DateTime,
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B",
[Year, Month, Day, Hour, Min, Sec])).


dump(Time, [Ether, IP, #tcp{} = Hdr, Payload]) ->
io:format("~s =======================================~n", [timestamp(Time)]),
io:format("SRC:~p:~p (~s)~nDST:~p:~p (~s)~nFlags [~p] seq ~p, ack ~p, win ~p, length ~p~n", [
IP#ipv4.saddr, Hdr#tcp.sport, string:join(epnet:ether_addr(Ether#ether.shost), ":"),
IP#ipv4.daddr, Hdr#tcp.dport, string:join(epnet:ether_addr(Ether#ether.dhost), ":"),
epnet:tcp_flags(Hdr),
Hdr#tcp.seqno, Hdr#tcp.ackno, Hdr#tcp.win,
byte_size(Payload)
]),
io:format("~s~n", [epnet:payload(Payload)]).
Note that I hardcoded the chroot directory and the default options. Running the above from the shell will display something like this:
2009-12-02 22:14:46 =======================================
SRC:{207,97,227,239}:80 (0:16:B6:xx:xx:xx)
DST:{192,168,1,100}:59341 (0:1C:B3:xx:xx:xx)
Flags ["ack"] seq 3306458405, ack 3526830431, win 46, length 492
HTTP/1.1 301 Moved Permanently..Server: nginx/0.7.61..Date: Thu, 03 Dec 2009 03:14:46 GMT..Content-Type: text/html; charset=utf-8..Connection: close..Status: 301 Moved Permanently..Location: http://github.com/dashboard..X-Runtime: 0ms..Content-Length: 93..Set-Cookie: _github_ses=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--884981fc5aa85daf318eeff084d98e2cff92578f; path=/; expires=Wed, 01 Jan 2020 08:00:00 GMT; HttpOnly..Cache-Control: no-cache
Occasionally, sniff will have an error parsing a packet. I haven't looked too deeply into this as yet. Check github, maybe it's already been fixed. I'll be cleaning up the code over the next few days.

So what's next for epcap? It'd be interesting building a monitoring system around Erlang or an intrustion detection system. Erlang's pattern matching would be awesome for signatures. Combining epcap with an Erlang libnet port could be the basis of a very cool vulnerability scanning engine; combined with erlang's seamless distribution, excellent web servers and frameworks, and distributed databases, it could certainly be the basis for something remarkable.

No comments:

Post a Comment