入侵技术介绍――目标探测

/ns/hk/hacker/data/20020806025208.htm

转帖
  

  计划介绍的内容包括信息收集,探测,渗透,帐号破解,入侵攻击,清除踪迹,后门制作等等(庞大的计划),主要都是从实现上来讲的(使用其他工具的不是计划内),其中的代码都需要自己写。不过我时间有限,写一次需要很长时间,也许一段时间内没时间写。那也怪不得我哦。不过,只希望能够让更多人脱离“工具黑客”,去看看实际到底是什么,然后去自己探索应该是什么。
写这个的目的不是教人怎么入侵怎么“黑”,只是让更多人去学习,去研究。

欢迎交流,Email: refdom@263.net

声明:所有都是属于一种技术上的研究和探讨,如果有人利用下面涉及的技术进行破坏等,与本人无任何关系!
************************************************************************



主机识别技术

作者: Refdom,
EmaiL: Refdom@263.net
2002/1/28

大多数的端口扫描就是让我们能够达到这样的目的:
1、让我们能够大致判断目标是什么操作系统
2、目标到底在运行些什么服务
当然,要得到这些东西还是最后为了让我们能够知道哪些可能拿来利用,可能存在的漏洞。很多工具提供的扫描也可能就直接得到什么操作系统了,或者相对应的端口使用的是什么程序,程序是什么版本的等等。不过,这些都是由那些工具自己做了,不讨论这个,我们应该去想想这些工具到底是怎么去实现的。

正如Fyodor(nmap的作者)在他的《Remote OS detection via TCP/IP Stack FingerPrinting》中讲解的进行主机识别的两个作用,第一,很多系统漏洞是同OS密切相关的,还有就是社会学(social engineering)问题,你能够在非常了解对方的系统之后,冒充软件提供商给目标发送“补丁”。

按照我们上面提到的高级扫描方式,直接进行的端口扫描,能够赋予我们绕过防火墙的能力,而且可以尽可能地隐藏自己等等,但是,我们能够得到的信息也是有限的,也许对是否开放一个端口并不是那么直接地感兴趣,比如一个21端口,我们真正感兴趣的是这个端口被用来作什么了,运行地什么版本的程序,也就是,我们对下面得到地这个东西更感兴趣(关系到IP的地方,我都用X代替了):

C:\>ftp XXX.XXX.XXX.XXX
Connected to XXX.XXX.XXX.XXX.
220 XXXXX X2 WS_FTP Server 1.0.5 (1327846197)
User (XXX.XXX.XXX.XXX:(none)):

其实,这就是一种最简单和最直接的判别方式。我们可以对每个打开的端口进行相应的连接,通常这些服务程序就会非常高兴地显示自己的“banner”,也就让我们能够直接得到他是什么版本了。甚至,我们能够得到更好的东西:

C:\>telnet XXX.XXX.XXX.XXX
Red Hat Linux release 7.1 (Seawolf)
Kernel 2.4.2-2 on an i686
login:

这让我们对操作系统版本一览无余了。正象这些只对80端口感兴趣的“黑客”一样,通过对80端口的连接,我们也能得到足够多的信息。

C:\>telnet XXX.XXX.XXX.XXX 80
HEAD / HTTP/1.1

HTTP/1.1 200 OK
Via: 1.1 ADSL2000
Content-Length: 97
Date: Thu, 24 Jan 2002 13:46:56 GMT
Content-Type: text/html
Server: Apache/1.3.20 (Unix) PHP/4.0.6
Last-Modified: Wed, 26 Dec 2001 09:22:54 GMT
ETag: "8715f-61-3c2996ee"
Accept-Ranges: bytes
Keep-Alive: timeout=15, max=100

可以注意到:Server: Apache/1.3.20 (Unix) PHP/4.0.6

这样直接的连接探测方式,对于这些banner开放的,简直是太容易了,当然,负责的管理员也会屏蔽或者修改掉这些BANNER。

还有一种粗劣而且简单的判别主机操作系统类型的办法就是通过Ping,然后分析得到的TTL值,当然,稍微准确点可以同时在配合Tracert来确定主机原始的TTL值,不过,这种办法很容易被欺骗,比如,在WINDOWS系统中,对注册表的修改:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
Key: DefaultTTL

对主机使用端口的分析,同样也能够进行操作系统识别,一些操作系统使用特殊的端口,比如:WINDOWS的137、139,WIN2K的445,而且一些网络设备比如入侵检测系统、防火墙等等也都有厂商自己的端口开放。

高级的主机识别技术主要分为主动协议识别和被动协议识别,都是利用各种操作系统在网络协议通讯中不同的协议内容,然后进行分析进行的识别。

Nmap这个强大的扫描工具在远程主机判断上也使用了很多技术,来实现更高级的主机系统检测。这主要是通过主动的TCP/IP协议辨识来实现的,每种OS,在TCP交流中总是使用一些具有特性的标志,这些标志在TCP IP数据包的头中。比如window、ACK序号、TTL等等的不同反应,通过大量的数据分析,然后精确地判断主机系统。在之后,Fyodor 和Ofir又分析和收集利用ICMP协议的操作系统特性来进行的主机系统判别,这种主动的识别方式都经过了大量的分析,在《Remote OS detection via TCP/IP Stack FingerPrinting》(http://www.insecure.org/nmap/nmap-fingerprinting-article.html)和Phrack #57《ICMP based remote OS TCP/IP stack fingerprinting techniques》有详细的技术细节,我就不再多嘴了。

和主动的协议识别原理相同,Lance Spitzner在《Passive Fingerprinting》中提出了被动的协议识别,同样用来判别主机系统。这种办法主要集中考虑:
1、TTL的设置
2、WINDOW SIZE:操作系统设置的窗口大小
3、DF:操作系统是否设置分片位
4、TOS:操作系统设置的服务类型

比如多数系统使用DF位设置,但是有些系统如SCO和OPENBSD不使用这个DF标志,这样就可以用来识别一些没有设置DF位的操作系统。被动协议识别也可以用来判断远程代理防火墙,因为代理防火墙重建对客户的连接,它有它自身的特征代码,也可以用这样的办法来分析。在(http://the.wiretapped.net/security/info/papers/security /lance-spitzner/finger.html)可以找到Lance Spitzner的这篇文章。

主动识别方式需要主动发送数据包,因此相对于那些安全设备来说,也比较容易识别这些数据包,同被动识别比较起来,隐蔽性稍微差些。

Reference:
1、《X - Remote ICMP Based OS Fingerprinting Techniques》
2、Phrack #57《ICMP based remote OS TCP/IP stack fingerprinting techniques》
3、Fyodor《Remote OS detection via TCP/IP Stack FingerPrinting》
4、Lance Spitzner《Passive Fingerprinting》


计划介绍的内容包括信息收集,探测,渗透,帐号破解,入侵攻击,清除踪迹,后门制作等等(庞大的计划),主要都是从实现上来讲的(使用其他工具的不是计划内),其中的代码都需要自己写。不过我时间有限,写一次需要很长时间,也许一段时间内没时间写。那也怪不得我哦。不过,只希望能够让更多人脱离“工具黑客”,去看看实际到底是什么,然后去自己探索应该是什么。

写这个的目的不是教人怎么入侵怎么“黑”,只是让更多人去学习,去研究。

欢迎交流,Email: refdom@263.net

声明:所有都是属于一种技术上的研究和探讨,如果有人利用下面涉及的技术进行破坏等,与本人无任何关系!
************************************************************************



2、目标探测

作者: Refdom,
EmaiL: Refdom@263.net
2002/1/17

确定目标并收集相关的周边信息之后,更直接地要接触目标的手段的就是进行探测了。探测一台主机包括是否活动、主机系统、正在使用哪些端口、提供了哪些服务、相关服务的软件版本等等
对这些内容的探测就是为了“对症下药”。

探测也就是大家所说的扫描,应该说扫描是最多使用工具的一个步骤,现在扫描工具也非常的多,从一般的Port Scanner到可以穿透防火墙的Scan 工具,比如强大的nmap,国内的X-Scanner、流光等等。在这里,我不是来介绍工具使用的,而主要是看看这些扫描工具到底是怎么来实现的。如果你喜欢上一个工具的话,也就表示你丧失了一次进步的机会。
这里都是一些简单的小程序,功能非常有限,只是为了说明实现原理而写的。有兴趣的可以自己做更广泛的修改,而实现更多的功能。

ICMP协议――PING是最常用的,也是最简单的探测手段,用来判断目标是否活动。实际上Ping是向目标发送一个要求回显(Type = 8)的ICMP数据报,当主机得到请求后,再返回一个回显(Type = 0)数据报。而且Ping 程序一般是直接实现在系统内核中的,而不是一个用户进程。我们也可以很容易实现一个Ping程序,这里我列出我写的一个简单Ping程序。

#include
#include

typedef struct ip_hdr //定义IP首部
{
unsigned char h_verlen; //4位首部长度,4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度(字节)
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IP_HEADER;

typedef struct icmp_hdr
{
BYTE i_type; // ICMP报文类型
BYTE i_code; // ICMP代码
USHORT i_cksum; // 校验和
USHORT i_id; // 标志符
USHORT i_seq; // 序号
ULONG timestamp; // 时间戳
} ICMP_HEADER;

//CheckSum:计算校验和的子函数
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1)
{
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size )
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}

void useage()
{
printf("******************************************\n");
printf("ICMPPing\n");
printf("\t Written by Refdom\n");
printf("\t Email: refdom@263.net\n");
printf("Useage: ICMPPing.exe Target_ip \n");
printf("*******************************************\n");
}

void DecodeHeader(char*, int);

void main(int argc, char* argv[])
{
ICMP_HEADER icmpHeader;
int rect;
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in,addr_from;
char recvbuf[1024];

useage();
if (argc!=2)
{ exit(0); }

if (WSAStartup(MAKEWORD(2,2), &WSAData) != 0 )
{ printf ("WSAStartup Error!\n");
exit(0);
}

sock= socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
int nTimeOut = 2000;
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOut, sizeof(nTimeOut));
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeOut, sizeof(nTimeOut));
memset(&addr_in, 0, sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_addr.S_un.S_addr = inet_addr(argv[1]);

if (addr_in.sin_addr.S_un.S_addr == INADDR_NONE)
{
struct hostent *host = NULL;
if ((host = gethostbyname(argv[1])) != NULL)
{
memcpy(&(addr_in.sin_addr), host->h_addr, host->h_length);
}
}
//
memset(&icmpHeader, 0, sizeof(icmpHeader));
icmpHeader.i_type = 8;
icmpHeader.i_code = 0;
icmpHeader.i_cksum = 0;
icmpHeader.i_id = (USHORT)GetCurrentProcessId();
icmpHeader.i_seq = 0;
icmpHeader.timestamp = GetTickCount();
icmpHeader.i_cksum = checksum((USHORT*)&icmpHeader, sizeof(icmpHeader));

rect = sendto(sock, (char*)&icmpHeader, sizeof(icmpHeader), 0, (sockaddr*)&addr_in, sizeof(addr_in));
int addr_from_len;
addr_from_len = sizeof(addr_from);
rect = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&addr_from, &addr_from_len);
DecodeHeader(recvbuf, rect);
closesocket(sock);
WSACleanup();
}

void DecodeHeader(char* buf, int len)
{
ICMP_HEADER *icmpHeader;
IP_HEADER *ipHeader;
IN_ADDR addr;
icmpHeader = (ICMP_HEADER*)(buf+20);
DWORD Time1;

Time1 = GetTickCount();
ipHeader = (IP_HEADER*)malloc(20);
memcpy(ipHeader, buf, 20);
addr.S_un.S_addr = ipHeader->sourceIP;
if (icmpHeader->i_type != 0)
{ printf("No replay!\n"); }
if (icmpHeader->i_id != (USHORT)GetCurrentProcessId())
{ printf("other pocket!\n"); }
printf("Reply from %s: Bytes= %d ", inet_ntoa(addr), len);
printf("TTL = %d Time= %d ms.\n", ipHeader->ttl, Time1-icmpHeader->timestamp );
}
(其中去掉了一些错误判断的地方,呵呵,节约空间)
PING得到的结果包括字节数、反应时间、以及生存时间。Ping程序通过在ICMP报文数据中存放发送请求的时间来计算返回时间。当应答返回时,根据现在时间减去报文中存放的发送时间就得到反应时间了。生存时间(TTL),本来就存放在IP数据报的头部,直接就能够获取。我们能够根据TTL时间来大致判断目标主机的系统类型,以及我们需要到达目的地需要经过几个路由器等等。
但是普通的Ping命令只能对一台主机进行探测,如果是要判断一个大类的网络是否有哪些活动的机器的话,就需要制作一个用来大面积Ping的工具,当然,你可以改进上面的基本程序,或者使用多线程并发来满足大面积Ping的需要。
还有一个Tracert 命令,也非常有用,可以参考以前写的,这里就不再罗嗦了。
关于ICMP协议的其他使用,比如,ICMP拒绝服务攻击等等,以后再讨论。

由于现在一些防火墙对ICMP进行控制,所以,实际上用ping来做判断已经非常不可靠了。即使是个人防火墙,你也Ping不进去了。
当然,我们有更多探测的方式,我们可以不使用ICMP协议,而使用更普通的TCP协议来进行探测,而且进行对方开放端口的扫描。普通的TCP探测是直接用connect()进行的,但是,在对方有防火墙情况下,Connect() 探测得出的结果也是不能确信的,如果熟悉更多关于TCP/IP协议的内容,我们就可以通过TCP/IP协议的特点尽可能地绕过防火墙。这就是大家所说得半开式扫描,其原理也就是不直接使用Connect进行对方端口得连接,而是发送TCP的标志位来实现,而且这样根本就不用同目标主机进行实际的连接。
在X-Scanner也有使用SYN探测的一项,也就是通过下面这些原理来做的。

SYN PING
SYN位表示一个TCP连接的开始,也就是三次握手的第一步,向远端主机某端口发送一个只有SYN标志位的TCP数据报,如果主机反馈一个SYN || ACK数据包,那么,这个主机正在监听该端口,如果反馈的是RST数据包,说明,主机没有监听该端口。

ACK PING
在三次握手中,ACK来表示确认握手过程,但是,如果根本没有进行SYN的请求,而去确认连接,目标主机就会认为一个错误发生了,而发送RST位来中断会话。发送一个只有ACK标志的TCP数据报给主机,如果主机反馈一个TCP RST数据报来,那么这个主机是存在的。ACK探测更容易通过一些 stateless型的防火墙。

FIN ping
因为FIN标志表示一个结束,但是目标主机根本就没有这个连接记录,所以,根据TCP/IP协议,目标主机就要发送一个RST标志的数据包来中断这个会话。对某端口发送一个TCP FIN数据报给远端主机,如果主机没有任何反馈,那么这个主机是存在的,而且正在监听这个端口;主机反馈一个TCP RST回来,那么说明该主机是存在的,但是没有监听这个端口。

NULL Ping
即发送一个没有任何标志位的TCP包,根据RFC793,如果目标主机的相应端口是关闭的话,应该发送回一个RST数据包。

后两种办法可以绕过一些防火墙,而得到防火墙后面的主机信息。即使在ICMP包被过滤的情况下,一般TCP探测还是能够成功的。当然,是在不被欺骗的情况下的。

下面这段程序就是一个TCP/IP半开探测的例子,当然,并没有做得完美,因为没有接收部分,而在WIN2000下实际就是一个选择性的SNIFFER,呵呵,大家可以使用其他的SNIFFER来实现同样的目的。

#include
#include
#include

#define SOURCE_PORT 7234
#define MAX_RECEIVEBYTE 255

typedef struct ip_hdr //定义IP首部
{
unsigned char h_verlen; //4位首部长度,4位IP版本号
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度(字节)
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceIP; //32位源IP地址
unsigned int destIP; //32位目的IP地址
}IPHEADER;

typedef struct tsd_hdr //定义TCP伪首部
{
unsigned long saddr; //源地址
unsigned long daddr; //目的地址
char mbz;
char ptcl; //协议类型
unsigned short tcpl; //TCP长度
}PSDHEADER;

typedef struct tcp_hdr //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCPHEADER;

//CheckSum:计算校验和的子函数
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while(size >1)
{
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size )
{
cksum += *(UCHAR*)buffer;
}

cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}

void useage()
{
printf("******************************************\n");
printf("TCPPing\n");
printf("\t Written by Refdom\n");
printf("\t Email: refdom@263.net\n");
printf("Useage: TCPPing.exe Target_ip Target_port \n");
printf("*******************************************\n");
}

int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
IPHEADER ipHeader;
TCPHEADER tcpHeader;
PSDHEADER psdHeader;

char szSendBuf[60]={0};
BOOL flag;
int rect,nTimeOver;

useage();

if (argc!= 3)
{ return false; }

if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0)
{
printf("WSAStartup Error!\n");
return false;
}

if ((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
flag=true;
if (setsockopt(sock,IPPROTO_IP, IP_HDRINCL,(char *)&flag,sizeof(flag))==SOCKET_ERROR)
{
printf("setsockopt IP_HDRINCL error!\n");
return false;
}

nTimeOver=1000;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver))==SOCKET_ERROR)
{
printf("setsockopt SO_SNDTIMEO error!\n");
return false;
}
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(atoi(argv[2]));
addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]);

//
//
//填充IP首部
ipHeader.h_verlen=(4<<4 | sizeof(ipHeader)/sizeof(unsigned long));
// ipHeader.tos=0;
ipHeader.total_len=htons(sizeof(ipHeader)+sizeof(tcpHeader));
ipHeader.ident=1;
ipHeader.frag_and_flags=0;
ipHeader.ttl=128;
ipHeader.proto=IPPROTO_TCP;
ipHeader.checksum=0;
ipHeader.sourceIP=inet_addr("本地地址");
ipHeader.destIP=inet_addr(argv[1]);

//填充TCP首部
tcpHeader.th_dport=htons(atoi(argv[2]));
tcpHeader.th_sport=htons(SOURCE_PORT); //源端口号
tcpHeader.th_seq=htonl(0x12345678);
tcpHeader.th_ack=0;
tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0);
tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测 等等
tcpHeader.th_win=htons(512);
tcpHeader.th_urp=0;
tcpHeader.th_sum=0;

psdHeader.saddr=ipHeader.sourceIP;
psdHeader.daddr=ipHeader.destIP;
psdHeader.mbz=0;
psdHeader.ptcl=IPPROTO_TCP;
psdHeader.tcpl=htons(sizeof(tcpHeader));

//计算校验和
memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
memcpy(szSendBuf+sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
tcpHeader.th_sum=checksum((USHORT *)szSendBuf,sizeof(psdHeader)+sizeof(tcpHeader));

memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
memcpy(szSendBuf+sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
memset(szSendBuf+sizeof(ipHeader)+sizeof(tcpHeader), 0, 4);
ipHeader.checksum=checksum((USHORT *)szSendBuf, sizeof(ipHeader)+sizeof(tcpHeader));

memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));

rect=sendto(sock, szSendBuf, sizeof(ipHeader)+sizeof(tcpHeader),
0, (struct sockaddr*)&addr_in, sizeof(addr_in));
if (rect==SOCKET_ERROR)
{
printf("send error!:%d\n",WSAGetLastError());
return false;
}
else
printf("send ok!\n");

closesocket(sock);
WSACleanup();

return 0;
}

上面这个程序不光是一个Ping的程序,也可以更改成为一个比connect()更优秀的端口扫描器。

上面这个程序实际上就是通过原始套接字发送自己构造的TCP和IP数据头,如果对这个程序作修改,也很容易改为一个SYN FLOOD的工具。SYN位前面说了,是三次握手过程的第一步,表示发出一个连接请求,这时目标主机同意的时候就会返回一个ACK SYN的数据报,来确认客户端的连接,然后需要客户端进行最后一次ACK的确认,来建立这个连接。但是,如果客户步发送一个ACK来确认,那么,服务器端就有一个缓冲来等待这个确认。当有大量的SYN缓冲等待的时候,正常的连接请求就会被放在一大堆队列中,从而影响正常用户的正常连接。同样相似的,就是RST拒绝服务了。

进行上面的探测应该是探测的第一步,比如主机活动状态,端口。接来下应该针对相应端口进行更详细的判别,以及探测主机系统。