WinPcap权威指南(四):UDP与DNS欺骗

       上一节介绍了ARP协议之后,群里面有朋友说ARP欺骗不是很简单么?实际上,实现ARP欺骗是很简单的,难点在于数据转发的速度。2004年我们做隔山打牛的时候,曾经去过一家网吧测试,刚开始是开了ARP欺骗几分钟就大量主机掉线,客人骂声一片(幸好是朋友的网吧,否则估计会被老板丢出去)。当然,那时候网上还没有ARP欺骗的介绍,更加没有什么ARP防火墙,ARP欺骗的防范是几年后的事情了。后来我们改进了算法,经过实际测试,250台电脑,原来的ICMP速度是多少(ping),开启ARP欺骗后就是多少,没有任何延迟,机器更加不会掉线。因为我们的代码仅出于演示目的,所以捕获线程和分析线程共用了一个List,然后加锁,如果你直接拿去网吧用,百分之百是被网吧老板抓住狂揍一顿的。

        群里还有朋友说cain一开,整个局域网会掉线,这个实际上是cain处理的不好:它启动ARP欺骗后,并没有对现有连接的seq和ack等做转换,另外还有一种解决方法是被动欺骗。不过实际上,数据修改并不一定要上ARP欺骗这种中间人方式,不使用ARP欺骗而达到数据修改,至少有三种方法,后面我们会介绍一两种,至于第三种,因为命中率百分之百,为了造成不良后果,以后有机会再说。

        上一节我们说了,网络通信的第一步是通过ARP协议获取目标电脑的物理地址—局域网内通信是获取目标电脑的,互联网是获取网关的,那第二步呢?答案当然是IP地址。所有基于IP的网络协议(例如icmp、tcp、udp)都必须拥有这个,但是因为ip难以记忆,而且有可能改变,所有实际应用中一般使用域名协议。例如,我们打开cmd,然后ping 一下我们的网站www.138soft.com,结果如下图:

xwinpcap8

        从上图可以看到,ICMP协议会先获取www.138soft.com对应的ip地址,然后再跟它收发数据,用过浏览器的朋友们,实际上当你们输入域名的时候,浏览器也会有这个过程。写过网络程序的朋友就清楚的多,因为这个就是gethostbyname函数。这个过程,实质上就是DNS协议。

        DNS协议的规范定义这里不作介绍了,我们只从程序的角度来说一下。DNS协议一般使用UDP,而且端口一般是53。DNS数据包分为查询包和应答包,其中查询包包含了这个包的ID(用于区别其它的查询包)、包的类型(这里为查询)、查询的类型(比如说:IP地址是A记录、MX发信地址是MX记录),DNS服务器的反馈包结构,前面是这个请求包(但包类型修改为反馈包),后面紧跟着具体的应答包。下面我们通过自己写的一个程序来重现这个过程(实际上,这个就是系统的gethostbyname函数的实现)。首先,我们使用iphlp库来获取本机的DNS服务器IP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function GetDNSServerIP: AnsiString ;
var
   dwBuffSize: Cardinal ;
   pFinxedInfo: PFIXED_INFO;
   pIPAddr: PIP_ADDR_STRING;
begin
   Result := '' ;
 
   if (GetNetworkParams( nil , dwBuffSize) <> ERROR_BUFFER_OVERFLOW) then Exit;
   GetMem(pFinxedInfo, dwBuffSize);
   if pFinxedInfo = nil then Exit;
   if (GetNetworkParams(pFinxedInfo, dwBuffSize) <> ERROR_SUCCESS) then
   begin
     FreeMem(pFinxedInfo);
     Exit;
   end ;
   pIPAddr := @pFinxedInfo^.DnsServerList;
   while (pIPAddr <> nil ) do
   begin
     if Result = '' then Result := Format( '%s' , [pIPAddr^.IpAddress . S]);
     pIPAddr := pIPAddr^.Next;
   end ;
   FreeMem(pFinxedInfo);
end ;

        有个DNS地址,就可以构造请求包发送和接收了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
procedure TForm1 . Button1Click(Sender: TObject);
//这里省略部分内部函数,具体看代码,函数来源于Indy
var
   AUDPSocket: TSocket;
   SockAddrIn: TSockAddrIn;
   strHostName: AnsiString ;
   nDNSPort: Integer ;
   QueryID: Word ;
var
   nLen, nRet: Integer ;
begin
   (Sender as TButton).Enabled := False ;
   try
     if Trim(Edit_Host . Text) = '' then
     begin
       ShowMessage( '请输入域名!' );
       Edit_Host . SetFocus;
       Exit;
     end ;
 
     if Trim(Edit_DNSServer . Text) = '' then
     begin
       ShowMessage( '请输入DNS服务器IP!' );
       Edit_DNSServer . SetFocus;
       Exit;
     end ;
 
     if Trim(Edit_DNSPort . Text) = '' then
     begin
       ShowMessage( '请输入DNS服务器端口!' );
       Edit_DNSPort . SetFocus;
       Exit;
     end ;
 
     strHostName := AnsiString (Trim(Edit_Host . Text));
 
     nDNSPort := StrToIntDef(Trim(Edit_DNSPort . Text), 53 );
 
     AUDPSocket := socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if AUDPSocket = INVALID_SOCKET then Exit;
     SockAddrIn . sin_family := AF_INET;
     SockAddrIn . sin_port := htons(nDNSPort);
     SockAddrIn . sin_addr . s_addr := inet_addr( PAnsiChar ( AnsiString (Trim(Edit_DNSServer . Text))));
 
 
     Randomize;
     QueryID := Random( 65535 );
     Buffer . Count := 0 ;
     Buffer . ID := Swap(QueryID); //查询的ID
     Move(Header_Option[ 1 ], Buffer . Option, 10 );
     StringToLabel(strHostName, SubLabel);
     Move(SubLabel[ 1 ], Buffer . Data[ 0 ], Length(SubLabel));
     Buffer . Count := Length(SubLabel);
     QType := # 0 # 1 ; //A记录
     Move(QType[ 1 ], Buffer . Data[Buffer . Count], 2 );
     Buffer . Count := Buffer . Count + 2 ;
     Move(QClass_Internet, Buffer . Data[Buffer . Count], 2 );
     Buffer . Count := Buffer . Count + 14 ; // 2 + 12
 
     if sendto(AUDPSocket, PAnsiChar (@Buffer . ID)^, Buffer . Count, 0 , SockAddrIn, sizeof(SockAddrIn)) = SOCKET_ERROR then
     begin
       CloseSocket(AUDPSocket);
       ShowMessage( '数据发送失败!' );
       Exit;
     end ;
 
     nLen := sizeof(SockAddrIn);
     nRet := recvfrom(AUDPSocket, PAnsiChar (@Buffer . ID)^, sizeof(Buffer), 0 , SockAddrIn, nLen);
     if nRet = SOCKET_ERROR then
     begin
       CloseSocket(AUDPSocket);
       ShowMessage( '数据接收失败!' );
       Exit;
     end ;
     CloseSocket(AUDPSocket);
     //==========================================================================
     if Buffer . ID = Swap(QueryID) then
     begin
       i := 0 ;
       while Buffer . Data[i] <> # 0 do Inc(i);
       i := i + 5 ;
       while (Buffer . Data[i] <> # 32 ) and (i < nRet) do
       begin
         LabelToString(@Buffer . Data, i, RRDomain);
         Inc(i);
         RRDataType := Ord(Buffer . Data[i]);
         i := i + 9 ;
         case RRDataType of
           QType_CNAME: Memo1 . Lines . Add(GetCNAME(Buffer, i));
           QType_NS: Memo1 . Lines . Add(GetNS(Buffer, i));
           QType_A: Memo1 . Lines . Add(GetA(Buffer, i));
           QType_SOA: Memo1 . Lines . Add(GetSOA(Buffer, i));
           QType_PTR: Memo1 . Lines . Add(GetPTR(Buffer, i));
           QType_MX: Memo1 . Lines . Add(GetMX(Buffer, i));
         else
           begin
             uShort . B[ 1 ] := Buffer . nData[i - 2 ];
             uShort . B[ 0 ] := Buffer . nData[i - 1 ];
             i := i + uShort . Value;
           end ;
         end ; //end for "case"
       end ; //end for "while"
     end ; //end for "if"
   finally
     (Sender as TButton).Enabled := True ;
   end ;
end ;

        程序运行界面如下:

xwinpcap9

        对应的,我们再来实现一个DNS服务器,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const
     HOSTIP= '127.0.0.1' ; //需要返回的IP地址
 
procedure TForm1 . ReadData( var Message: TMessage);
var
   client_addr: TSockAddrIn;
   szBuffer: array [ 0..4095 ] of AnsiChar ;
   len: integer ;
   flen: integer ;
   Event: word ;
var
   strRecvData: AnsiString ;
 
   ID, Code, QDCount: Word ;
   BitCode: TDNSBitCode;
   i: integer ;
   APos: Integer ;
   DomainName: AnsiString ;
   QueryClass: Word ;
   QueryType: Word ;
 
   strSend: AnsiString ;
begin
   flen := sizeof(client_addr);
   Event := WSAGetSelectEvent(Message . LParam);
   if Event = FD_READ then
   begin
     len := recvfrom(m_UDPSocket, szBuffer, sizeof(szBuffer), 0 , client_addr, flen);
     if len <= 0 then Exit;
     if len > 512 then Exit;
 
     SetLength(strRecvData, len);
     Move(szBuffer[ 0 ], strRecvData[ 1 ], len);
 
     if Length(strRecvData) < sizeof(TDNSHeader) then Exit;
     ID := TwoCharToWord(strRecvData[ 1 ], strRecvData[ 2 ]);
     Code := TwoCharToWord(strRecvData[ 3 ], strRecvData[ 4 ]);
     BitCode := GetDNSBitCode(Code);
     if BitCode . QR <> 0 then Exit; //0表示查询,1表示反馈
     QDCount := TwoCharToWord(strRecvData[ 5 ], strRecvData[ 6 ]);
     if QDCount <= 0 then Exit;
 
     APos := 13 ; // DNS头恒为12字节,我们从下一字节开始分析
     for i := 1 to QDCount do
     begin
       DomainName := DNSStrToDomain(strRecvData, APos);
       if DomainName = '' then Exit;
       QueryType := TwoCharToWord(strRecvData[APos], strRecvData[APos + 1 ]);
       Inc(APos, 2 );
       QueryClass := TwoCharToWord(strRecvData[APos], strRecvData[APos + 1 ]);
       Inc(APos, 2 );
       Memo1 . Lines . Add(DomainName);
       (*
       m_strInfo := 'DNS查询包' + #$D#$A;
       m_strInfo := m_strInfo + SourceIP + ':' + IntToStr(SourcePort) + '--->' + DestIP + ':' + IntToStr(DestPort) + #$D#$A;
       m_strInfo := m_strInfo + 'DNSID:' + IntToStr(ID) + #$D#$A;
       m_strInfo := m_strInfo + 'Domain:' + DomainName + #$D#$A;
       m_strInfo := m_strInfo + 'QueryType:' + GetDNSTypeStr(QueryType) + #$D#$A;
       m_strInfo := m_strInfo + 'QueryClass:' + GetDNSClassStr(QueryClass) + #$D#$A;
       m_strInfo := m_strInfo + '===================================================';
       Synchronize(ShowInfo);
       *)
     end ;
     strSend := BuilderDNSResponse(szBuffer, len, HOSTIP); //构造DNS应答包
     sendto(m_UDPSocket, strSend[ 1 ], Length(strSend), 0 , client_addr, flen);
   end ;
end ;

        程序写的很简单,就是根据查询包,分析出客户端需要查询的域名,在Memo1显示出来,然后构造一个应答包返回。这里出于演示目的,我们简单的全部返回127.0.0.1,对于真正的DNS服务器,这里会使用gethostbyname之类往上一级查询,或直接在数据库和缓存里面查找,根据不同的域名返回对应的IP。我们先运行程序,然后把另外一台机器的DNS服务器指向这个IP:

xwinpcap10

        然后随便Ping 一下www.qq.com:

xwinpcap11

        可以看到,返回的IP地址是127.0.0.1。同时我们的DNS服务器的界面如下:

xwinpcap12

        最后,我们把DNS服务端的代码移植到我们的Demo代码里面,就实现了DNS欺骗。需要注意的地方是,基于IP的数据包都有一个校验和的,所以如果你修改了数据包,一定要重新计算一次校验和,具体过程见CheckSum函数。程序运行效果如下:

xwinpcap13

        DNS欺骗利用的原理是:DNS客户端发送请求包后,只要返回包里面的ID跟发送的一致,它就认为合法。如果有多个返回包,则它使用最前面的,这个是跟ARP刚好相反的(ARP会使用最新的覆盖原来的)。几年前,360云查杀的时候,我做过一个试验,就是在应用层开启RAWSOCKET来捕获数据,发现是请求360云服务器的话,则直接创建一个UDP SOCKET然后往请求地址发送一个回复,360就无法连接真正的云服务器了。实际上,不只是DNS协议如此,对于TCP协议,也是一样的。只要你捕获到它的数据包,就可以实现网页插代码、下载内容替换等等。这里再回头前面的话题:不用ARP欺骗就修改数据。方法之一就是在路由的镜像口接一台电脑,比如说,想欺骗TCP,只要你回复的数据包的SEQ和ACK按照顺序,客户端就会老老实实的被欺骗。因为你的程序工作在路由旁边,比真正的服务器回复的速度是更快的,甚至你可以再伪造回复的同时,顺便发送一个RST包给真正的服务器,让它断开连接。如果你无法接触路由,又不想使用ARP怎么办?那就用方法二或方法三了。

附件下载:
本节代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值