diff --git a/docs/pcat.mdoc b/docs/pcat.mdoc new file mode 100644 index 0000000..3360ebd --- /dev/null +++ b/docs/pcat.mdoc @@ -0,0 +1,107 @@ +.\" This manual is Copyright 2012 by pi-rho +.\" +.\" This program is free software: you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation, either version 3 of the License, or +.\" (at your option) any later version. +.\" +.\" This package is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program. If not, see . +.\" +.\" On Debian systems, the complete text of the GNU General +.\" Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". +. +.Dd May 23, 2012 +.Dt PCAT 1 +.Os "Network Reverse Engineering Toolkit" 1.1337 +. +.Sh NAME +.Nm pcat +.Nd dump a packet capture in a line-based, parsable format +. +.Sh SYNOPSIS +.Nm pcat +.Op Fl h | Fl v +.Nm pcat +.Op Fl o Ar output.txt +.Op Pa input.pcap +. +.Sh DESCRIPTION +The basic concept for this utility is to dump the contents of a packet capture +in a line-based, parsable format. The general format is as follows: +.Pp +.D1 Sy TIMESTAMP PROTOCOL SOURCE DESTINATION PAYLOAD +.Pp +Each value may have attributes separated by commas. +.Pp +.Bl -tag -width "destination" +.It Sy TIMESTAMP +epoch,nanoseconds +.It Sy PROTOCOL +ARP, TCP4, UDP4, ICMP4, P# (where # is the IP protocol number) +.It Sy SOURCE +.Em ARP No \ : macaddress,ipaddress +.br +.Em ICMP4 : No ipaddress,type,code +.br +.Em TCP4 No : ipaddress,port,sequence +.br +.Em UDP4 No : ipaddress,port +.br +.Em P# No \ \ : ipaddress +.It Sy DESTINATION +.Em ARP No \ : macaddress,ipaddress +.br +.Em ICMP4 : No ipaddress +.br +.Em TCP4 No : ipaddress,port,ack +.br +.Em UDP4 No : ipaddress,port +.br +.Em P# No \ \ : ipaddress +.It Sy PAYLOAD +a hexadecimal representation of the protocol's payload, or '-' if there is no +payload +.El +.Pp +The available options include: +.Pp +.Bl -tag -compact -width "-o output.txt" +.It Fl h +usage information +.It Fl v +the program's version +.It Fl o Ar output.txt +sets the output filename to +.Pa output.txt +.Pq default: Dv stdout +.It Ar input.pcap +the packet capture to read +.Pq default: Dv stdin +.El +. +.Sh EXAMPLES +As a filter: +.D1 Ic $ Nm pmerge Pa one.pcap Pa two.pcap | Nm pcat > Pa output.txt +.Pp +In simple command form: +.D1 Ic $ Nm pcat Fl o Pa output.txt Pa one.pcap +. +.Sh SEE ALSO +.Xr p4split 1 , +.Xr puniq 1 , +.Xr pmerge 1 +. +.Sh AUTHORS +.An Zephyr Aq Ad zephyr@dirtbags.net , +.An pi-rho Aq Ad pi-rho@tyr.cx +. +.Sh BUGS +Bugs may be submitted at +.Aq Ad https://bugs.launchpad.net/netre-tools +.\" vim:ft=mandoc diff --git a/src/pcat.c b/src/pcat.c new file mode 100644 index 0000000..782b1a5 --- /dev/null +++ b/src/pcat.c @@ -0,0 +1,306 @@ +/* ____ __ ____ ______ + * | \ / ] / T| T + * | o )/ / Y o || | + * | _// / | |l_j l_j + * | | / \_ | _ | | | + * | | \ || | | | | + * l__j \____jl__j__j l__j + * + * dump a packet capture in a line-based, parsable format + * + */ + +#include +#include +#include +#include +#include +#include "netre.h" +#include "pcap.h" +#include "stream.h" + +#define IPPROTO_ICMP 1 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define ARP_REQ 0x01 +#define ARP_REP 0x02 +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +int version(bool error) { + fprintf(WHICHOUT(error), "pcat v.%s - %s\n\n", PACKAGE_VERSION, + "dump a packet capture in a line-based, parsable format"); + return error ? EX_USAGE : EX_OK; +} + +int usage(bool error, char *prog) { + int retval = version(error); + fprintf(WHICHOUT(error), "Usage: %s [-o OUTPUT] [FILE]\n", prog); + fprintf(WHICHOUT(error), "\t-o OUTPUT file in which to write output; if not specified,\n"); + fprintf(WHICHOUT(error), "\t output will be written to stdout\n"); + fprintf(WHICHOUT(error), "\tFILE input pcap file\n"); + return retval; +} + +void mac_addr(char *addr_s, uint16_t addr1, uint16_t addr2, uint16_t addr3) { + snprintf(addr_s, 18, "%02x:%02x:%02x:%02x:%02x:%02x", + (addr1 >> 8) & 0xff, + (addr1 >> 0) & 0xff, + (addr2 >> 8) & 0xff, + (addr2 >> 0) & 0xff, + (addr3 >> 8) & 0xff, + (addr3 >> 0) & 0xff); +} + +void ip4_addr(char *addr_s, uint32_t addr) { + snprintf(addr_s, 16, "%u.%u.%u.%u", + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + (addr >> 0) & 0xff); +} + +void print_payload(struct stream *s, FILE *out) { + if (s->len == 0) + fprintf(out, "-"); + else + while (s->len) + fprintf(out, "%02x", read_uint8(s)); +} + +void process_icmp(struct stream *s, char *saddr_s, char *daddr_s, FILE *out) { + uint8_t type = read_uint8(s); + uint8_t code = read_uint8(s); + uint16_t chksum = read_uint16be(s); + + fprintf(out, "ICMP4 %s,%u,%u %s ", saddr_s, type, code, daddr_s); + + if (false && chksum && false) { + /* Placate compiler */ + } +} + +void process_tcp(struct stream *s, char *saddr_s, char *daddr_s, FILE *out) { + uint16_t sport = read_uint16be(s); + uint16_t dport = read_uint16be(s); + uint32_t seq = read_uint32be(s); + uint32_t ack = read_uint32be(s); + uint8_t off = read_uint8(s); + uint8_t hlen = (off >> 4) * 4; + uint8_t flags = read_uint8(s); + uint16_t window = read_uint16be(s); + uint16_t chksum = read_uint16be(s); + uint16_t urgent = read_uint16be(s); + + if (hlen < 20) printf("!"); + + fprintf(out, "TCP4 %s,%u,%u %s,%u,%u ", saddr_s, sport, seq, daddr_s, dport, ack); + + if (false && urgent && chksum && window && flags && ack && seq && false) { + /* Placate compiler */ + } +} + +void process_udp(struct stream *s, char *saddr_s, char *daddr_s, FILE *out) { + uint16_t sport = read_uint16be(s); + uint16_t dport = read_uint16be(s); + uint16_t len = read_uint16be(s); + uint16_t chksum = read_uint16be(s); + + fprintf(out, "UDP4 %s,%u %s,%u ", saddr_s, sport, daddr_s, dport); + + if (false && len && chksum && false) { + /* Placate compiler */ + } +} + +void process_arp(struct stream *s, FILE *out) { + uint16_t htype = read_uint16be(s); + uint16_t ptype = read_uint16be(s); + uint8_t hlen = read_uint8(s); + uint8_t plen = read_uint8(s); + uint16_t op = read_uint16be(s); + + uint16_t sha1 = read_uint16be(s); + uint16_t sha2 = read_uint16be(s); + uint16_t sha3 = read_uint16be(s); + uint32_t spa = read_uint32be(s); + + uint16_t tha1 = read_uint16be(s); + uint16_t tha2 = read_uint16be(s); + uint16_t tha3 = read_uint16be(s); + uint32_t tpa = read_uint32be(s); + + // 12:34:56:78:9A:BC + char sha_s[20]; + char spa_s[20]; + char tha_s[20]; + char tpa_s[20]; + + mac_addr(sha_s, sha1, sha2, sha3); + ip4_addr(spa_s, spa); + mac_addr(tha_s, tha1, tha2, tha3); + ip4_addr(tpa_s, tpa); + + switch(op) { + case ARP_REQ: + fprintf(out, "ARP? "); break; + case ARP_REP: + fprintf(out, "ARP. "); break; + default: + fprintf(out, "ARP! "); break; + } + + fprintf(out, "%s,%s %s,%s -", sha_s, spa_s, tha_s, tpa_s); + + if (false && htype && ptype && hlen && plen && false) { + /* Placate compiler */ + } +} + +void process_ip4(struct stream *s, FILE *out) { + uint8_t vhl = read_uint8(s); + uint8_t ihl = (vhl & 0x0f) * 4; + if (ihl < 20) ihl = 20; + uint8_t tos = read_uint8(s); + uint16_t length = read_uint16be(s); + uint16_t id = read_uint16be(s); + uint16_t off = read_uint16be(s); + uint8_t ttl = read_uint8(s); + uint8_t proto = read_uint8(s); + uint16_t chksum = read_uint16be(s); + uint32_t saddr = read_uint32be(s); + uint32_t daddr = read_uint32be(s); + + char saddr_s[20]; + char daddr_s[20]; + + ip4_addr(saddr_s, saddr); + ip4_addr(daddr_s, daddr); + + DDUMP_d(ihl); + DDUMP_d(length); + + // Ignore options + sskip(s, 20 - ihl); + + // Force stream length to IP payload length + s->len = length - ihl; + + DDUMP_d(proto); + switch (proto) { + case IPPROTO_ICMP: + process_icmp(s, saddr_s, daddr_s, out); break; + case IPPROTO_TCP: + process_tcp( s, saddr_s, daddr_s, out); break; + case IPPROTO_UDP: + process_udp( s, saddr_s, daddr_s, out); break; + default: + fprintf(out, "P%d %s %s ", proto, saddr_s, daddr_s); break; + } + + print_payload(s, out); + + if (false && chksum && id && tos && ttl && off && false) { + /* Placate compiler */ + } +} + +void print_ethernet(struct stream *s, FILE *out) { + uint8_t saddr[6]; + uint8_t daddr[6]; + uint16_t vlan; + uint16_t ethertype; + + sread(s, &saddr, sizeof(saddr)); + sread(s, &daddr, sizeof(daddr)); + ethertype = read_uint16be(s); + + if (ethertype == 0x8100) { // VLAN + vlan = read_uint16be(s); + ethertype = read_uint16be(s); + } + + switch (ethertype) { + case 0x0806: // ARP + process_arp(s, out); break; + case 0x0800: // IPv4 + default: + DDUMP_x(ethertype); + process_ip4(s, out); break; + } + + if (false && vlan && false) { + /* Placate compiler */ + } +} + +void print_frame(struct pcap_pkthdr *hdr, char const *frame, int raw, FILE *out) { + struct stream streambuf; + struct stream *s = &streambuf; + + sinit(s, frame, hdr->caplen); + fprintf(out, "%u.%6.6u ", hdr->ts.tv_sec, hdr->ts.tv_usec); + + if (raw) + process_ip4(s, out); + else + print_ethernet(s, out); + + fprintf(out, "\n"); +} + +int pcat(FILE *f, FILE *out) { + struct pcap_file p; + + if (pcap_open_in(&p, f) == -1) { + return EX_IOERR; + } + + for (;;) { + struct pcap_pkthdr hdr; + char frame[MAXFRAME]; + + if (pcap_read_pkthdr(&p, &hdr) == -1) break; + if (fread(frame, hdr.caplen, 1, f) != 1) break; + + print_frame(&hdr, frame, p.raw, out); + } + + return EX_OK; +} + +int main(int argc, char *argv[]) { + int opt; + FILE *output = stdout; + FILE *input = stdin; + + while ((opt = getopt(argc, argv, "hvo:")) != -1) { + switch (opt) { + case 'o': /* output file */ + output = fopen(optarg, "wb"); + if (!output) { + perror("opening output"); + return EX_OSFILE; + } + break; + case 'v': return version(false); + case 'h': return usage(false, argv[0]); + default: return usage(true, argv[0]); + } + } + + if (optind < argc) { + input = fopen(argv[optind], "rb"); + if (!input) { + perror("opening input"); + return EX_OSFILE; + } + } + + return pcat(input, output); +} diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..3c67aed --- /dev/null +++ b/src/stream.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include "stream.h" + +void sinit(struct stream *s, char const *buf, size_t buflen) { + s->buf = buf; + s->len = buflen; +} + +bool sskip(struct stream *s, size_t count) { + if (count > s->len) { + s->len = 0; + return false; + } + + s->buf += count; + s->len -= count; + return true; +} + +bool sread(struct stream *s, void *buf, size_t count) { + void const *d = s->buf; + + if (!sskip(s, count)) return false; + memcpy(buf, d, count); + + return true; +} + +uint8_t read_uint8(struct stream *s) { + uint8_t *d = (uint8_t *)s->buf; + + if (!sskip(s, 1)) return 0; + return d[0]; +} + +uint16_t read_uint16be(struct stream *s) { + uint8_t *d = (uint8_t *)s->buf; + + if (!sskip(s, 2)) return 0; + return ((d[0] << 8) | (d[1] << 0)); +} + +uint16_t read_uint16le(struct stream *s) { + uint8_t *d = (uint8_t *)s->buf; + + if (!sskip(s, 2)) return 0; + return ((d[0] << 0) | (d[1] << 8)); +} + +uint32_t read_uint32be(struct stream *s) { + uint8_t *d = (uint8_t *)s->buf; + + if (!sskip(s, 4)) return 0; + return ((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3] << 0)); +} + +uint32_t read_uint32le(struct stream *s) { + uint8_t *d = (uint8_t *)s->buf; + + if (!sskip(s, 4)) return 0; + return ((d[0] << 0) | (d[1] << 8) | (d[2] << 16) | (d[3] << 24)); +} diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 0000000..c734c28 --- /dev/null +++ b/src/stream.h @@ -0,0 +1,22 @@ +#ifndef _STREAM_H +#define _STREAM_H + +#include +#include +#include + +struct stream { + char const *buf; + size_t len; +}; + +void sinit(struct stream *s, char const *buf, size_t len); +bool sskip(struct stream *s, size_t count); +bool sread(struct stream *s, void *buf, size_t count); +uint8_t read_uint8(struct stream *s); +uint16_t read_uint16be(struct stream *s); +uint16_t read_uint16le(struct stream *s); +uint32_t read_uint32be(struct stream *s); +uint32_t read_uint32le(struct stream *s); + +#endif