首页
论坛
课程
招聘
Windows C语言 icmp ping命令探测主机存活 笔记
2020-4-30 01:41 3844

Windows C语言 icmp ping命令探测主机存活 笔记

2020-4-30 01:41
3844

参考学习链接:https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmpsendecho


之前在VS2015 2017 2019一直编译不通过,不知道是一些什么奇奇怪怪的问题,但过了几天,编译又成功了,很奇怪,但至少目前是没问题的,先贴一下MSDN上面给的示例代码吧

#include <winsock2.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <stdio.h>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

int __cdecl main(int argc, char **argv)  {

    // Declare and initialize variables
    
    HANDLE hIcmpFile;
    unsigned long ipaddr = INADDR_NONE;
    DWORD dwRetVal = 0;
    char SendData[32] = "Data Buffer";
    LPVOID ReplyBuffer = NULL;
    DWORD ReplySize = 0;
    
    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s IP address\n", argv[0]);
        return 1;
    }

    ipaddr = inet_addr(argv[1]);
    if (ipaddr == INADDR_NONE) {
        printf("usage: %s IP address\n", argv[0]);
        return 1;
    }
    
    hIcmpFile = IcmpCreateFile();
    if (hIcmpFile == INVALID_HANDLE_VALUE) {
        printf("\tUnable to open handle.\n");
        printf("IcmpCreatefile returned error: %ld\n", GetLastError() );
        return 1;
    }    

    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
    ReplyBuffer = (VOID*) malloc(ReplySize);
    if (ReplyBuffer == NULL) {
        printf("\tUnable to allocate memory\n");
        return 1;
    }    
    
    
    dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), 
        NULL, ReplyBuffer, ReplySize, 1000);
    if (dwRetVal != 0) {
        PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
        struct in_addr ReplyAddr;
        ReplyAddr.S_un.S_addr = pEchoReply->Address;
        printf("\tSent icmp message to %s\n", argv[1]);
        if (dwRetVal > 1) {
            printf("\tReceived %ld icmp message responses\n", dwRetVal);
            printf("\tInformation from the first response:\n"); 
        }    
        else {    
            printf("\tReceived %ld icmp message response\n", dwRetVal);
            printf("\tInformation from this response:\n"); 
        }    
        printf("\t  Received from %s\n", inet_ntoa( ReplyAddr ) );
        printf("\t  Status = %ld\n", 
            pEchoReply->Status);
        printf("\t  Roundtrip time = %ld milliseconds\n", 
            pEchoReply->RoundTripTime);
    }
    else {
        printf("\tCall to IcmpSendEcho failed.\n");
        printf("\tIcmpSendEcho returned error: %ld\n", GetLastError() );
        return 1;
    }
    return 0;
}


这个程序的核心就是一个IcmpSendEcho函数,IcmpSendEcho函数像目标主机发送一个IPv4的ICMP响应请求。这个函数在IcmpAPI.h头文件中定义如下

IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
IcmpSendEcho(
    _In_                HANDLE                   IcmpHandle,
    _In_                IPAddr                   DestinationAddress,
    _In_reads_bytes_(RequestSize)   LPVOID                   RequestData,
    _In_                WORD                    RequestSize,
    _In_opt_              PIP_OPTION_INFORMATION   RequestOptions,
    _Out_writes_bytes_(ReplySize)   LPVOID                   ReplyBuffer,
    _In_range_(>=, sizeof(ICMP_ECHO_REPLY) + RequestSize + 8)
                      DWORD                    ReplySize,
    _In_                DWORD                    Timeout
    );

有点凌乱,MSDN官网上面的更简洁一点

IPHLPAPI_DLL_LINKAGE DWORD IcmpSendEcho(
  HANDLE                 IcmpHandle,
  IPAddr                 DestinationAddress,
  LPVOID                 RequestData,
  WORD                   RequestSize,
  PIP_OPTION_INFORMATION RequestOptions,
  LPVOID                 ReplyBuffer,
  DWORD                  ReplySize,
  DWORD                  Timeout
);


参数介绍

1、IcmpHandle

是一个句柄,HANDLE声明,返回IcmpCreateFile()函数的打开的ICMP文件句柄


2、DestinationAddress

目标IPv4地址,采用IPAddr结构的形式,也就是inet_addr(ip),如inet_addr("127.0.0.1")的返回值,作用是将点分十六进制转换为小端序形式的十六进制数。


3、RequestData

请求数据的地址指针,也就是发送的字符串数组名,一般发啥过去,返回的就是啥


4、RequestSize

RequestData的大小,一般在strlen(RequestData)的基础上+1,不加也可以,如示例代码中直接使用sizeof(RequestData)的返回值


5、RequestOptions

指向请求的IP标头选项的指针,形式为IP_OPTION_INFORMATION结构。在64位平台上,此参数采用IP_OPTION_INFORMATION32结构的形式。如果不需要指定IP标头选项,则此参数可以为NULL。这个需要注意的是,64位平台IP_OPTION_INFORMATION结构是有后32的,而32位的没有

这个结构体定义如下

typedef struct ip_option_information {
  UCHAR  Ttl;
  UCHAR  Tos;
  UCHAR  Flags;
  UCHAR  OptionsSize;
  PUCHAR OptionsData;
} IP_OPTION_INFORMATION, *PIP_OPTION_INFORMATION;

描述了被包括在IP分组的报头选项,也就是ICMP的报头结构,其中OptionData对应发送的数据,size对应大小


6、ReplyBuffer

一个缓冲区,用于保存对请求的所有答复。返回时,缓冲区包含ICMP_ECHO_REPLY结构数组, 后跟答复的选项和数据。缓冲区应足够大,以容纳至少一个 ICMP_ECHO_REPLY结构以及数据的RequestSize字节。

也就是说,回复的数据中包含一个ICMP_ECHO_REPLY结构体和一个RequestData,其中ICMP_ECHO_REPLY结构定义如下

typedef struct icmp_echo_reply {
  IPAddr                       Address;
  ULONG                        Status;
  ULONG                        RoundTripTime;
  USHORT                       DataSize;
  USHORT                       Reserved;
  PVOID                        Data;  struct ip_option_information Options;} ICMP_ECHO_REPLY, *PICMP_ECHO_REPLY;

Data也就是返回的实际数据,这里可以用wireshark抓包验证,结构体中重点要说的是IPAddr,也就是地址,这里返回的是一个小端序数,如下示例代码输出

    char ip[] = "127.0.0.1";
    unsigned long ipHex = inet_addr(ip);
    struct in_addr hex;
    hex.S_un.S_addr = ipHex;
    char ipStr[20];
    memset(ipStr, 0, 20);
    memcpy(ipStr, inet_ntoa(hex), strlen(inet_ntoa(hex)));

    printf("ipHex = %x\n", ipHex);
    printf("ipStr = %s\n", ipStr);

输出如图



7、ReplySize

分配的答复缓冲区大小(以字节为单位)。缓冲区应足够大,以容纳至少一个 ICMP_ECHO_REPLY结构以及数据的RequestSize字节。此缓冲区还应该足够大,以容纳8个更多字节的数据(ICMP错误消息的大小)。


8、Timeout

等待答复的时间(以毫秒为单位)。


到这里,MSDN官网上面的代码介绍基本完毕,稍修改输出便可实现cmd终端的ping功能输出,那么如何修改至用于探测主机存活呢?

首先,在真实环境中,一般不需要考虑IcmpCreateFile创建文件失败或者ReplyBuffer申请内存空间失败,如果遇到这种情况,直接认为主机不存在,不存活,而且ReplyBuffer的大小是可以直接计算出来的,甚至都不需要malloc函数来申请内存空间,其次,只是探测主机是否存活,并非重复造轮子一个ping,所以发送的数据也可以尽可能的简短,输出也不需要,直接修改为一个函数即可,同时为批量探测主机存活提供接口,如下

#include <winsock2.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <stdio.h>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

BOOL icmpProbe(char* ip)
{
    HANDLE hIcmpFile;
    unsigned long ipaddr = INADDR_NONE;
    DWORD dwRetVal = 0;
    char SendData[32] = "icmpTest";
    LPVOID ReplyBuffer = NULL;
    DWORD ReplySize = 0;

    ipaddr = inet_addr(ip);

    hIcmpFile = IcmpCreateFile();
    if (hIcmpFile == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
    ReplyBuffer = (VOID*)malloc(ReplySize);
    if (ReplyBuffer == NULL) {
        return FALSE;
    }

    dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), NULL, ReplyBuffer, ReplySize, 1000);
    if (dwRetVal != 0) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}

int  main(int argc, char** argv)
{
    if (argc == 1)
    {
        printf("Usage: %s IPv4 address\n\n", argv[0]);
        exit(0);
    }

    unsigned long  ipaddr = inet_addr(argv[1]);
    if (ipaddr == INADDR_NONE) {
        printf("[-] %s is not a IPv4 address, please input a valid IPv4 address\n\n", argv[1]);
        exit(0);
    }

    if (icmpProbe(argv[1]))
    {
        printf("[+] %s alive!\n\n", argv[1]);
    }
    else
    {
        printf("[-] %s not alive!\n\n", argv[1]);
    }


    return 0;
}


暂时先到这,vs2019编译是通过的,但还是希望下次编译二次开发不会出啥奇奇怪怪的问题,至于如何用这它来实现批量探测主机是否存活,批量解析IP地址,后续补充。


[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

收藏
点赞0
打赏
分享
最新回复 (1)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
laical1 活跃值 2020-4-30 16:06
2
0
scrapy 如果要爬取整个站的内容,是要把所有页面的URL都获取到队列后才开始提取内容吗
游客
登录 | 注册 方可回帖
返回