TFBOYSer的文章

  • 使用WNetEnumResource函数实现枚举工作组内的主机及其IP

    背景之所以会学习到这方面的知识,是因为那段时间正在帮一个游戏工作室开发一个游戏自动登录并创建角色的游戏脚本。当时,我就是使用VS去开发。因为它要求要有一个控制端可以所有的控制端,所以,就分别写了一个客户端程序和控制端程序。客户端都运行在虚拟机内,和控制端在同一网段里。
    当时,我就想让客户端在虚拟机里运行,主动去扫描工作组内的主机,那么它工作组内就会有两个主机,一个是虚拟机自己,另一个就是外面的主机。主机上运行则控制端,所以,这样就可以获取主机的IP地址,并自动建立反向连接,传输数据。
    所以,当时使用扫描方法,就是使用本文介绍的这个使用 WNetEnumResource 函数的方法。现在,我就把实现原理和过程写成文档,分享给大家。
    函数介绍WNetOpenEnum 函数
    WNetOpenEnum函数启动网络资源或现有连接的枚举。 您可以通过调用WNetEnumResource函数继续枚举。
    函数声明
    DWORD WNetOpenEnum( _In_ DWORD dwScope, _In_ DWORD dwType, _In_ DWORD dwUsage, _In_ LPNETRESOURCE lpNetResource, _Out_ LPHANDLE lphEnum);
    参数

    dwScope [in]枚举范围。 此参数可以是以下值之一:



    VALUE
    MEANING




    RESOURCE_CONNECTED
    枚举所有当前连接的资源。 该函数忽略dwUsage参数。


    RESOURCE_CONTEXT
    仅枚举调用者网络上下文中的资源。 为“网络邻居”视图指定此值。 该函数忽略dwUsage参数


    RESOURCE_GLOBALNET
    枚举网络上的所有资源


    RESOURCE_REMEMBERED
    枚举所有记住(持久)连接。 该函数忽略dwUsage参数




    dwType [in]要枚举的资源类型。 此参数可以是以下值的组合:



    VALUE
    MEANING




    RESOURCETYPE_ANY
    所有资源。 此值不能与RESOURCETYPE_DISK或RESOURCETYPE_PRINT组合


    RESOURCETYPE_DISK
    所有磁盘资源


    RESOURCETYPE_PRINT
    所有打印资源




    dwUsage [in]要枚举的资源使用类型。 此参数可以是以下值的组合:



    VALUE
    MEANING




    0
    所有资源


    RESOURCEUSAGE_CONNECTABLE
    所有可连接的资源


    RESOURCEUSAGE_CONTAINER
    所有容器资源


    RESOURCEUSAGE_ATTACHED
    如果用户未通过身份验证,设置此值将强制WNetOpenEnum失败。 即使网络允许枚举而不进行身份验证,该功能也会失败


    RESOURCEUSAGE_ALL
    设置此值相当于设置RESOURCEUSAGE_CONNECTABLE,RESOURCEUSAGE_CONTAINER和RESOURCEUSAGE_ATTACHED




    pNet资源[in]指向指定要枚举的容器的NETRESOURCE结构。 如果dwScope参数不是RESOURCE_GLOBALNET,则此参数必须为NULL。
    lphEnum [out]指向可以在随后调用WNetEnumResource中使用的枚举句柄的指针。

    返回值

    如果函数成功,返回值为NO_ERROR。如果函数失败,则返回值是系统错误代码。

    WNetEnumResource 函数
    WNetEnumResource功能继续列出通过调用WNetOpenEnum函数启动的网络资源。
    函数声明
    DWORD WNetEnumResource( _In_ HANDLE hEnum, _Inout_ LPDWORD lpcCount, _Out_ LPVOID lpBuffer, _Inout_ LPDWORD lpBufferSize);
    参数

    hEnum [in]标识枚举实例的句柄。这个句柄必须由WNetOpenEnum函数返回。lpcCount [in,out]指向指定所请求条目数的变量的指针。如果所请求的号码为-1,则该函数返回尽可能多的条目。如果函数成功,返回此参数指向的变量包含实际读取的条目数。lpBuffer [out]指向接收枚举结果的缓冲区的指针。结果作为NETRESOURCE结构的数组返回。请注意,您分配的缓冲区必须足够大以容纳结构,加上其成员指向的字符串。有关详细信息,请参阅以下备注部分。lpBufferSize [in,out]指向变量的指针,该变量指定lpBuffer参数的大小(以字节为单位)。如果缓冲区太小而不能接收一个条目,则此参数将接收所需的缓冲区大小。
    返回值

    执行成功,返回 NO_ERROR 或者 ERROR_NO_MORE_ITEMS;否则,错误。

    gethostbyname 函数
    gethostbyname函数从主机数据库中检索与主机名对应的主机信息。
    函数声明
    struct hostent* FAR gethostbyname( _In_ const char *name);
    参数

    name[in]
    指向要解析的主机的以NULL结尾的名称的指针。

    返回值

    如果没有发生错误,gethostbyname返回一个指向上述主机结构的指针。 否则,它返回一个空指针,并且可以通过调用WSAGetLastError来检索特定的错误号。

    inet_ntoa 函数
    inet_ntoa函数将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串。
    函数声明
    char* FAR inet_ntoa( _In_ struct in_addr in);
    参数

    in[in]
    一个表示Internet主机地址的in_addr结构。

    返回值

    如果没有发生错误,则inet_ntoa返回一个字符指针,指向包含标准“。”表示法中的文本地址的静态缓冲区,否则返回NULL。

    实现原理本文要实现的功能就是遍历网络邻居,获取工作组内的所有在线主机名以及根据主机名获取的IP地址。实现过程如下:

    首先,我们通过 WSAStartup 函数完成对 Winsock 服务的初始化,因为下面我们会使用到 Socket 函数。然后,我们使用 WNetOpenEnum 设置枚举的范围为本工作组内 RESOURCE_CONTEXT,并获取枚举句柄。接着,我们便调用 WNetEnumResource 函数按照设置的范围去枚举资源,并获取枚举结果。然后,遍历枚举结果,并设置过滤标志 RESOURCEUSAGE_CONTAINER 和 RESOURCETYPE_ANY。通过过滤之后,可以根据远程主机名调用函数gethostbyname 去获取IP地址信息,并通过 inet_ntoa 函数将(Ipv4) Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串。最后,释放内存以及枚举句柄。
    这样,整个程序的实现原理和流程就结束了。需要特别注意一点就是,在函数开头一定要先初始化 Winsock 服务!
    编码实现加载库函数#include <Winnetwk.h>#pragma comment(lib, "Mpr.lib")#pragma comment(lib, "Ws2_32.lib")
    枚举工作组内的网络资源BOOL EnumNetResource(){ NETRESOURCE *NetResource = NULL; HANDLE hEnum; unsigned int i; char szHostName[MAX_PATH] = { 0 }; hostent *host = NULL; char *lpszIP = NULL; // 通过WSAStartup函数完成对Winsock服务的初始化 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 指定枚举范围, 获取枚举句柄 ::WNetOpenEnum(RESOURCE_CONTEXT, NULL, NULL, NULL, &hEnum); if (hEnum) { DWORD Count = 0xFFFFFFFF; DWORD BufferSize = 2048; BYTE *pBuffer = new BYTE[2048]; // 根据设置的枚举返回, 获取枚举信息 ::WNetEnumResource(hEnum, &Count, pBuffer, &BufferSize); NetResource = (NETRESOURCE*)pBuffer; for (i = 0; i < BufferSize / sizeof(NETRESOURCE); i++, NetResource++) { // 判断资源类型是否是所有资源 以及 判断资源使用类型是否是容器资源 if (NetResource->dwUsage == RESOURCEUSAGE_CONTAINER && NetResource->dwType == RESOURCETYPE_ANY) { if (NetResource->lpRemoteName) { // 获取远程主机名 ::RtlZeroMemory(szHostName, MAX_PATH); ::lstrcpy(szHostName, (char *)((DWORD64)NetResource->lpRemoteName + 2)); // 根据主机名获取IP地址信息 host = ::gethostbyname(szHostName); if (host == NULL) { printf("Error Code:%d\n", ::GetLastError()); continue; } // 将(Ipv4)Internet网络地址转换为Internet标准点分十进制格式的ASCII字符串 lpszIP = ::inet_ntoa(*(in_addr *)host->h_addr_list[0]); // 显示 printf("NetResource->lpRemoteName = %s\n", NetResource->lpRemoteName); printf("NetResource->lpLocalName = %s\n", NetResource->lpLocalName); printf("ComputerName = %s\n", szHostName); printf("ComputerIP = %s\n", lpszIP); } } } // 释放内存并关闭句柄 delete[]pBuffer; pBuffer = NULL; ::WNetCloseEnum(hEnum); } return TRUE;}
    程序测试我们在 main 函数中直接调用上述封装好的函数,得到显示内容如下所示。成功获取到本工作组内所有在线的主机及其IP地址。

    总结这个程序的关键就是要理解WIN32 API函数各个参数的定义,这样,才能更好地理解它的使用方式。
    要注意的是,一定要在使用枚举网络资源函数之前调用 WSAStartup 函数来初始化 Socket 库环境,因为函数会调用到 Socket 函数。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-11-26 09:38:16

发送私信

让你的风铃晃动的,是风;让你的生命灵动的,是爱

6
文章数
12
评论数
eject