主机扫描是网络扫描的基础,通过对目标网络中主机IP地址的扫描,从一堆主机中扫描出存活的主机,然后以他们为目标进行后续的攻击。一般会借助于ICMP、TCP、UDP等协议的工作机制,检查打开的进程,开放的端口号等等。
主机扫描
主机扫描是在可达状态下检测,局域网下的 ARP 扫描和广域网下的 ICMP Echo 扫描、ICMP Sweep 扫描、ICMP Broadcast 扫描、ICMP Non-Echo 扫描都是基本的扫描技术。还有绕过防火墙和网络过滤设备的高级技术。
- ARP 扫描
向目标主机所在的局域网发送 ARP 广播请求,在局域网连通状态下目标主机必定会响应正常的 ARP 广播请求。故而便可获得IP地址和 MAC 地址等信息。
...
int convertNetworkAddr(const char *inString,uint8_t outNetAddr[PROTOCOL_ADDRESS_LENGTH],uint8_t *netPrefix)
{
char *invalidCharPtr;
// 读取3个八位字节
for (int i = 0; i < PROTOCOL_ADDRESS_LENGTH - 1; ++i)
{
unsigned long octet = strtoul(inString, &invalidCharPtr, 10);
if (octet >= 256 || *invalidCharPtr != '.')
{
return 1;
}
inString = invalidCharPtr + 1;
outNetAddr[i] = (uint8_t) octet;
}
// 读取最后一个八位字节
unsigned long octet = strtoul(inString, &invalidCharPtr, 10);
if (octet >= 256 || *invalidCharPtr != '/')
{
return 1;
}
inString = invalidCharPtr + 1;
outNetAddr[PROTOCOL_ADDRESS_LENGTH - 1] = octet;
unsigned long prefix = strtoul(inString, &invalidCharPtr, 10);
if (prefix > 32 || invalidCharPtr == inString)
{
return 1;
}
*netPrefix = prefix;
// 按位AND ip地址和网络掩码
uint8_t hostPartLength = PROTOCOL_ADDRESS_LENGTH * 8 - prefix;
for (int j = 0; j < hostPartLength / 8; ++j)
{
outNetAddr[PROTOCOL_ADDRESS_LENGTH - 1 - j] = 0x00;
}
if (hostPartLength % 8)
{
uint8_t mask = (0xFF >> hostPartLength % 8) << hostPartLength % 8;
outNetAddr[PROTOCOL_ADDRESS_LENGTH - hostPartLength / 8 - 1] &= mask;
}
return 0;
}
...
void printPacket(struct ArpPacket *p)
{
for (int i = 0; i < sizeof(struct ArpPacket); ++i)
{
int c = *((char*)p+i) & 0xFF;
printf("%2x ", c);
if (i%8 == 7)
printf("\n");
}
printf("\n");
}
int readNetworkAddr(const char *string,uint8_t dstAddr[PROTOCOL_ADDRESS_LENGTH],uint8_t *dstPrefix)
{
if (convertNetworkAddr(string, dstAddr, dstPrefix))
{
fprintf(stderr, "Cannot recognize network address from: %s\n", string);
exit(1);
}
DEBUG(
printf("========= RECOGNIZED NETWORK ADDRESS ===========\n");
printf("IP address: ");
for (int j = 0; j < PROTOCOL_ADDRESS_LENGTH; ++j)
{
printf("%i ", (int)dstAddr[j]);
}
printf("\nNet prefix: %i\n", (int)(*dstPrefix) );
)
}
int main(int argc, char **argv)
{
...
if (argc != 3)
{
fprintf(stderr, "Usage: <interface name> <network address>.\n"
"For example: scan eth0 192.168.0.1/24\n");
return 1;
}
// 打开套接字
int s = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
if (s == -1) {
perror("Error in opening socket: ");
return 1;
}
// 读取网络地址
uint8_t netAddr[PROTOCOL_ADDRESS_LENGTH];
uint8_t netPrefix;
readNetworkAddr(argv[2], netAddr, &netPrefix);
DEBUG(
setHostPart(netAddr, 255, 8);
printf("IP address: ");
for (int j = 0; j < PROTOCOL_ADDRESS_LENGTH; ++j)
{
printf("%i ", (int)netAddr[j]);
}
printf("\n");
)
struct sockaddr_ll addr;
prepareSockaddrll(&addr, argv[1]);
struct ArpPacket packet;
prepareArpPacket(&packet, &addr, argv[1]);
DEBUG(
printf("Packet hex dump: \n");
printPacket(&packet);
)
for (int i = 1; i < powl(2, (32 - netPrefix) ) - 1; ++i)
{
setHostPart(netAddr, i, 32 - netPrefix);
setDstIP(netAddr, &packet);
DEBUG(
printf("Sending ARP request to IP ");
printIP(netAddr, stdout);
printf(" ..............");
)
if(sendto(s, &packet, sizeof(packet), 0,(struct sockaddr*) &addr, sizeof(addr)) == -1) {
perror("Error in sending ARP request: ");
fprintf(stderr, "Error in sending ARP request: %s\n", strerror(errno));
return 1;
}
DEBUG(printf("[ DONE ]\n");)
// waiting for response
usleep(200);
struct ArpPacket response;
ssize_t receivedResponseSize;
if (receivedResponseSize = recv(s, &response, sizeof(response), MSG_DONTWAIT | MSG_TRUNC) > 0)
{
DEBUG(
printf("Received %ld bytes of response: \n", (long) receivedResponseSize);
printPacket(&response);
)
if (memcmp(response.senderLogicAddress, packet.senderLogicAddress, sizeof(packet.senderLogicAddress)))
{
printf("IP ");
printIP(response.senderLogicAddress, stdout);
printf(" has MAC: ");
for (int j = 0; j < HARDWARE_ADDRESS_LENGTH; ++j)
{
printf("%x:", (int)response.senderHardwareAddress[j]);
}
printf("\n");
}
}
}
return 0;
}
运行结果:
If you need the complete source code, please add the WeChat number (c17865354792)
tcpdump抓包
- ICMP Echo 扫描
向目标主机发送ICMP Echo Request (type 8)数据包,等待回复的ICMP Echo Reply 包(type 0) 。如果能收到,则表明目标系统可达,否则表明目标系统已经不可达或发送的包被对方的设备过滤掉。
...
/*
获取网络接口的本地信息。
*/
int loadLocalData( LocalData *dst, const char *ifname )
{
struct ifreq nic;
int sock = socket( AF_INET, SOCK_DGRAM, 0 );
strncpy( nic.ifr_name, ifname, IFNAMSIZ-1 );
nic.ifr_name[IFNAMSIZ-1] = '\0';
if( ioctl( sock, SIOCGIFINDEX, &nic ) < 0 ){
close( sock );
return -1;
}
dst->ifindex = nic.ifr_ifindex;
// 分配的IP地址
if( ioctl( sock, SIOCGIFADDR, &nic ) < 0 ){
close( sock );
return -1;
}
memcpy( &dst->ip, nic.ifr_addr.sa_data + 2, INET_ALEN );
// MAC地址
if( ioctl( sock, SIOCGIFHWADDR, &nic ) < 0 ){
close( sock );
return -1;
}
memcpy( &dst->mac, nic.ifr_hwaddr.sa_data, ETH_ALEN );
// 子网掩码
if( ioctl( sock, SIOCGIFNETMASK, &nic ) < 0 ){
close( sock );
return -1;
}
memcpy( &dst->netmask, nic.ifr_netmask.sa_data + 2, INET_ALEN );
close( sock );
return 0;
}
/**
* 为特定协议创建套接字。
*/
int createSocket( SocketType type, int msecs )
{
int sfd;
struct timeval timer;
if( type == ICMPSocket )
sfd = socket( AF_INET, SOCK_RAW, IPPROTO_ICMP );
else
sfd = socket( AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP) );
if( sfd < 0 )
return -1;
// 设置接收数据的最大时间
timer.tv_sec = msecs / 1000;
timer.tv_usec = msecs % 1000 * 1000;
if( setsockopt( sfd, SOL_SOCKET, SO_RCVTIMEO, &timer, sizeof(timer) ) < 0 ){
close( sfd );
return -1;
}
return sfd;
}
...
/**
* 计算增加一个IP地址的结果。
*/
struct in_addr ipAddOne( struct in_addr ip )
{
struct in_addr aux = { ntohl( ip.s_addr ) };
aux.s_addr++;
aux.s_addr = htonl( aux.s_addr );
return aux;
}
/**
* 交换两个IP地址的值
*/
void ipSwap( struct in_addr *a, struct in_addr *b )
{
a->s_addr = a->s_addr ^ b->s_addr;
b->s_addr = a->s_addr ^ b->s_addr;
a->s_addr = a->s_addr ^ b->s_addr;
}
...
int main( int argc, char **argv )
{
...
// 验证接口
if( !interface )
{
fprintf( stderr, "Error: No interface given. Use -h for help\n" );
return -1;
}
// 加载本地数据
if( loadLocalData( &data, interface ) < 0 )
{
perror( interface );
return -1;
}
// 验证是否有特定的IP
if( strFirst )
{
if( !inet_aton( strFirst, &first ) )
{
fprintf( stderr, "%s: Invalid address\n", strFirst );
return -1;
}
if( strLast )
{ // 验证是否有范围
if( !inet_aton( strLast, &last ) )
{
fprintf( stderr, "%s: Invalid address\n", strLast );
return -1;
}
else
{
total = ntohl( last.s_addr ) - ntohl( first.s_addr );
if( total < 0 ){
total = -total;
ipSwap( &first, &last );
}
total++;
}
}
else
{
total = 1;
}
}
else
{ // 如果没有,则从本地网络数据中获取范围。
first.s_addr = data.ip.s_addr & data.netmask.s_addr;
first = ipAddOne( first );
last.s_addr = data.ip.s_addr | ~data.netmask.s_addr;
total = ntohl( last.s_addr ) - ntohl( first.s_addr );
}
// 创建套接字
if( (sfd = createSocket( type, waitTime ) ) < 0 )
{
perror( "Failed to create socket" );
return 2;
}
...
// 扫描周期
for( int i = 1 ; i <= total && running ; i++, first = ipAddOne(first) )
{
printf( "\r(%d%%) Testing %s...", (int)(100.0 / total * i), inet_ntoa(first) );
fflush( stdout );
if( first.s_addr == data.ip.s_addr )
{
printf( " (this host)\n" );
ups++;
}
else
{
switch( isUp(sfd, first, &data) )
{
case -1:
perror( " send request" );
break;
case 1:
puts( " is up" );
ups++;
}
}
}
close( sfd );
printf( "\n%d hosts up\n", ups );
return 0;
}
运行结果:
If you need the complete source code, please add the WeChat number (c17865354792)
tcpdump抓包:
- ICMP Sweep 扫描
总结
这里我们就知道了其实主机扫描很简单,只需要证明其主机存活就好。我们只需要对目标主机发送特定的数据包,如果目标主机有回应,那么我们就认为该主机是存活的;反之如果对方不回应,我们就认为其不是存活主机。
Welcome to follow WeChat official account【程序猿编码】