Birty的文章

  • 编程实现驱动加载器

    背景自己在钻研Windows内核编程的时候,经常需要使用驱动加载器加载自己写的小驱动程序进行测试。刚开始的时候,使用的是别人写的驱动加载器,但是,越用越不顺眼,因为感觉驱动加载器功能包括:创建服务、启动服务、停止服务、删除服务,这四个功能而已。但是,别人写的功能太过复杂,还增加了其它额外的功能,或者是界面太丑,按钮太小了。所以,就决定自己写一个驱动加载器了。
    本文给的这个样例是使用控制台编写的,而我自己使用的加载器,是使用MFC写的,带有界面。但是,核心部分是一样的。现在,我就把驱动加载的实现原理以及实现过程写成文档,分享给大家。
    函数介绍OpenSCManager 函数
    建立了一个到服务控制管理器的连接,并打开指定的数据库。
    函数声明
    SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access type);
    参数

    lpMachineName指向零终止字符串,指定目标计算机的名称。如果该指针为NULL ,或者它指向一个空字符串,那么该函数连接到本地计算机上的服务控制管理器。
    lpDatabaseName指向零终止字符串,指定将要打开的服务控制管理数据库的名称。此字符串应被置为 SERVICES_ACTIVE_DATABASE。如果该指针为NULL ,则打开默认的 SERVICES_ACTIVE_DATABASE数据库。
    dwDesiredAccess指定服务访问控制管理器的权限。在授予要求的权限前,系统会检查调用进程的权限令牌,该令牌针对与服务控制管理器相关的安全描述符的权限控制列表。此外,该函数的调用将隐式地指定SC_MANAGER_CONNECT 的访问权限。此外,下列服务控制管理器对象的访问类型可以被指定:




    VALUE
    MEANING




    SC_MANAGER_ALL_ACCESS
    除了所有此表中列出的访问类型,还包括STANDARD_RIGHTS_REQUIRED


    SC_MANAGER_CONNECT
    可以连接到服务控制管理器


    SC_MANAGER_CREATE_SERVICE
    使要求的CreateService函数创建一个服务对象,并将其添加到数


    SC_MANAGER_ENUMERATE_SERVICE
    使要求的EnumServicesStatus功能清单的服务,这是在数据库


    SC_MANAGER_LOCK
    使要求的LockServiceDatabase功能获得锁定数据库


    SC_MANAGER_QUERY_LOCK_STATUS
    使要求的QueryServiceLockStatus检索功能锁定状态信息的数据



    返回值

    如果函数成功,返回值是一个指定的服务控制管理器数据库的句柄。如果函数失败,返回值为NULL 。要获得更详细的错误信息,可以使用GetLastError 获得错误代码。

    CreateService 函数
    创建一个服务对象,并将其添加到指定的服务控制管理器数据库的函数。
    函数声明
    SC_HANDLE CreateService( SC_HANDLE hSCManager, //服务控制管理程序的数据库句柄 LPCTSTR lpServiceName, //以NULL 结尾的服务名 LPCTSTR lpDisplayName, //以NULL 结尾的服务名 DWORD dwDesiredAccess, //指定服务返回类型 DWORD dwServiceType, //指定服务类型 DWORD dwStartType, //指定何时启动服务 DWORD dwErrorControl, //指定服务启动失败的严重程度 LPCTSTR lpBinaryPathName,//指定服务程序二进制文件的路径 LPCTSTR lpLoadOrderGroup,//指定顺序装入的服务组名 LPDWORD lpdwTagId, //忽略,NULL LPCTSTR lpDependencies, //指定启动该服务前必先启动的服务 LPCTSTR lpServiceStartName,//指定服务帐号 LPCTSTR lpPassword //指定对应的口令);
    参数

    hSCManager服务控制管理器数据库的句柄。 此句柄由OpenSCManager函数返回,并且必须 具有 SC_MANAGER_CREATE_SERVICE 的访问权限。 有关更多的信息请参阅Service安全和访问权限。
    lpServiceName要安装该服务的名称。 最大字符串长度为 256 个字符。 服务控制管理器数据库将保留在的字符的大小写,但是服务名称比较总是区分大小写。 正斜杠和一个反斜线不是有效的服务名称字符。
    lpDisplayName对被用户界面程序用来识别服务的显示名称。 此字符串具有最大长度为 256 个字符。 服务控制管理器中的情况下保留名称。 显示名称比较总是不区分大小写。
    dwDesiredAccess对服务的访问。 请求的访问之前,系统将检查调用进程的访问令牌。 一个值列表请参阅服务安全和访问权限。
    dwServiceType服务类型。 此参数可以是下列值之一:




    VALUE
    MEANING




    SERVICE_ADAPTER
    保留


    SERVICE_FILE_SYSTEM_DRIVER
    文件系统驱动服务程序


    SERVICE_KERNEL_DRIVER
    驱动服务程序


    SERVICE_RECOGNIZER_DRIVER
    保留


    SERVICE_WIN32_OWN_PROCESS
    运行于独立进程的服务程序


    SERVICE_WIN32_SHARE_PROCESS
    被多个进程共享的服务程序




    若使用了SERVICE_WIN32_OWN_PROCESS 或 SERVICE_WIN32_SHARE_PROCESS且使用LocalSystem帐号来运行该服务程序,则还可以附加使用下面的值:



    VALUE
    MEANING




    SERVICE_INTERACTIVE_PROCESS
    该服务可以与桌面程序进行交互操作




    dwStartType服务启动选项。此参数可以是下列值之一:



    VALUE
    MEANING




    SERVICE_AUTO_START
    系统启动时由服务控制管理器自动启动该服务程序


    SERVICE_BOOT_START
    用于由系统加载器创建的设备驱动程序, 只能用于驱动服务程序


    SERVICE_DEMAND_START
    由服务控制管理器(SCM)启动的服务


    SERVICE_DISABLED
    表示该服务不可启动


    SERVICE_SYSTEM_START
    用于由IoInitSystem函数创建的设备驱动程序




    dwErrorControl当该启动服务失败时产生错误的严重程度以及采取的保护措施。
    lpBinaryPathName服务程序二进制文件,完全限定路径。 如果路径中包含空格它必须被引用,以便它正确的解析。
    lpLoadOrderGroup在加载顺序此服务所属的组的名称。
    lpdwTagId指向接收 lpLoadOrderGroup 参数中指定的组中唯一的标记值的变量。
    lpDependencies空分隔名称的服务或加载顺序组系统必须在这个服务开始之前的双空终止数组的指针。 如果服务没有任何依赖关系,请指定为 NULL 或空字符串。
    lpServiceStartName该服务应在其下运行的帐户的名称。
    lpPassword由lpServiceStartName参数指定的帐户名的密码。

    返回值

    如果函数成功,返回值将是该服务的句柄。如果函数失败,则返回值为 NULL。 若要扩展的错误了解调用GetLastError。

    OpenService 函数
    打开一个已经存在的服务。
    函数声明
    SC_HANDLE WINAPI OpenService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_ DWORD dwDesiredAccess);
    参数

    hSCManager
    SCM数据库句柄。
    lpServiceName
    要打开服务的名字,这和CreateService形参lpServiceName一样,不是服务显示名称。
    dwDesiredAccess
    服务权限。

    返回值

    成功,返回服务句柄;失败返回NULL,可以通过GetLastError获取错误码。

    StartService 函数
    启动服务。
    函数声明
    BOOL WINAPI StartService( _In_ SC_HANDLE hService, _In_ DWORD dwNumServiceArgs, _In_opt_ LPCTSTR *lpServiceArgVectors);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄,需要有SERVICE_START权限。
    dwNumServiceArgs
    下一个形参lpServiceArgVectors的字符串个数,如果lpServiceArgVectors为空,那么该参数设为0。
    lpServiceArgVectors
    传递给服务ServiceMain的参数,如果没有,可以设为NULL;否则,第一个形参lpServiceArgVectors[0]为服务的名字,其他则为需要传入的参数。

    注意

    驱动不接受这些参数,即lpServiceArgVectors为空,dwNumServiceArgs为0。
    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    ControlService函数
    给指定的服务发送一个控制码。
    函数声明
    BOOL WINAPI ControlService( _In_ SC_HANDLE hService, _In_ DWORD dwControl, _Out_ LPSERVICE_STATUS lpServiceStatus);
    参数

    hService
    OpenService 或CreateService function返回的服务句柄;
    dwControl
    要发送的控制码,控制码可以是下列系统定义的控制码;也可以是用户自定义的控制码,范围是128-255,需要有SERVICE_USER_DEFINED_CONTROL权限。
    lpServiceStatus
    返回一个指向存储服务最新状态的结构体ServiceStatus,返回的服务信息来自SCM中最近的服务状态报告。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    DeleteService 函数
    从服务控制管理器数据库中删除指定的服务。
    函数声明
    BOOL WINAPI DeleteService( _In_ SC_HANDLE hService);
    参数

    hService
    [输入]服务的句柄,可以由OpenService 或者 CreateService 函数返回,而且该服务必须要有删除权限。

    返回值

    成功,返回非0;失败,返回0,错误码使用GetLastError获得。

    实现思路创建驱动服务的原理是:

    打开服务控制管理器数据库并获取数据库的句柄。若要创建服务的操作,则开始创建服务,设置SERVICE_KERNEL_DRIVER创建驱动服务;若是其他操作,则打开服务,获取服务句柄。然后,根据服务句柄,若操作是启动,则使用StartService启动服务;若服务是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务。关闭服务句柄和服务控制管理器数据库句柄。
    其中,创建驱动服务程序和创建应用层系统服务程序代码基本上是一样的,只是在使用CreateService函数创建服务时候,使用的参数不一样而已,一个是SERVICE_WIN 32_OWN_PROCESS标志,一个是SERVICE_KERNEL_DRIVER标志,其他都是相同的。
    需要注意的是,创建驱动服务也是需要管理员权限去创建的。
    编程实现// 0 加载服务 1 启动服务 2 停止服务 3 删除服务BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType){ BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 }; ::lstrcpy(szName, lpszDriverPath); // 过滤掉文件目录,获取文件名 ::PathStripPath(szName); SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; // 打开服务控制管理器数据库 shOSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; } if (0 != iOperateType) { // 打开一个已经存在的服务 shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } } switch (iOperateType) { case 0: { // 创建服务 // SERVICE_AUTO_START 随系统自动启动 // SERVICE_DEMAND_START 手动启动 shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { // 启动服务 if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { // 停止服务 if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { // 删除服务 if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } // 关闭句柄 if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; } return bRet;}
    程序测试在 main 函数中调用上述封装好的函数进行测试,main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ BOOL bRet = FALSE; char szFileName[MAX_PATH] = "C:\\Users\\TEST\\Desktop\\test.sys"; // 创建并启动服务 bRet = SystemServiceOperate(szFileName, 0); if (FALSE == bRet) { printf("Create Error!\n"); } bRet = SystemServiceOperate(szFileName, 1); if (FALSE == bRet) { printf("Start Error!\n"); } printf("Create and Start OK.\n"); system("pause"); // 停止并删除服务 bRet = SystemServiceOperate(szFileName, 2); if (FALSE == bRet) { printf("Stop Error!\n"); } bRet = SystemServiceOperate(szFileName, 3); if (FALSE == bRet) { printf("Delete Error!\n"); } printf("Stop and Delete OK.\n"); system("pause"); return 0;}
    测试结果:
    写了一个测试的小驱动程序(为了不混淆,在此不给出驱动的代码了),使用我们写的这个驱动加载器加载驱动。
    “以管理员身份运行程序”打开我们的驱动加载器,提示创建并启动服务成功,而且从“DebugView”中看到,有驱动入口点函数的消息输出,说明驱动确实被加载运行了。

    然后,我们执行停止并删除服务,程序提示执行成功。而且,从“DebugView”看到有驱动卸载函数的消息输出,说明驱动成功被停止了。

    所以,测试成功。
    总结创建驱动服务程序和创建应用层系统服务程序代码基本上是一样的,只是在使用CreateService函数创建服务时候,使用的参数不一样而已,一个是SERVICE_WIN 32_OWN_PROCESS标志,一个是SERVICE_KERNEL_DRIVER标志,其他都是相同的。
    同样,创建驱动服务也是需要管理员权限才能够创建。其中,OpenSCManager函数执行需要管理员权限才能执行成功。所以,我们可以根据它的返回值这一个特性,来判断当前进程的权限。如果可以返回成功,说明拥有管理员及以上权限的高权限;否则,是低权限。
    参考参考自《Windows黑客编程技术详解》一书
    2  留言 2018-12-01 12:43:38
  • 编程使用WMI

    背景
    WMI出现至今已经二十多年了,但很多人对它并不熟悉。知道它很好很强大,但不知道它从哪里来,怎么工作,使用范围是什么?
      WMI有一组API。我们不管使用VBScript、PowerShell脚本还是利用C#的来访问WMI的类库,都是因为WMI向外暴露的一组API。这些API是在系统安装WMI模块的时候安装的,通过他们我们能够能拿到我们想要的类。
      WMI有一个存储库。尽管WMI的多数实例数据都不存储在WMI中,但是WMI确实有一个存储库,用来存放提供程序提供的类信息,或者称为类的蓝图或者Schema。
      WMI有一个Service。WMI总是能够响应用户的访问,那是因为它有一个一直运行的Windows服务,名字叫Winmgmt。停止这个服务,所有对WMI的操作都将没有反应。
      WMI是可扩展的。人人都知道WMI能干很多事情,读取本机硬盘信息、读取远程计算机的用户信息、读取域用户信息等等。基本上,你能想到的获取或者更改资源的操作,它都能干。可谓吃得少,干得多。它为什么这么能干呢?这基于WMI的可扩展性。WMI对资源的操作,不是它自己实现了什么方法,而完全取决于向它注册的提供程序。
      WMI是管理员日常必备的强大工具之一,是脚本伴侣。当然也可以把一个大型系统建立在WMI以及WMI的提供程序之上。
    WMI的全称是Windows Management Instrumentation,即Windows管理工具。它是Windows操作系统中管理数据和操作的基础模块。我们可以通过WMI脚本或者应用程序去管理本地或者远程计算机上的资源。对于VC和汇编程序员,想获取诸如CPU序列号和硬盘序列号等信息是非常容易的。但是对于VB以及其他一些脚本语言,想尝试获取系统中一些硬件信息可能就没那么容易了。微软为了能达到一种通用性目的(遵守某些行业标准),设计了WMI。它提供了一个通过操作系统、网络和企业环境去管理本地或远程计算机的统一接口集。应用程序和脚本语言使用这套接口集去完成任务,而不是直接通过Windows API。可能有人要问,为什么不让设计的脚本直接在底层使用Windows API,而非要弄个新的技术呢?原因是在目前Windows API中,有些是不支持远程调用或者脚本调用的。这样通过统一模型的WMI,像VB和脚本语言就可以去访问部分系统信息了。但是并不是所有脚本语言都可以使用WMI技术:它要支持ActiveX技术。

    WMI通常是被脚本所调用的,不过也对。对于WMI能做的操作,我们也完全可以通过VS去调用Win32 API去实现。但是,本文要讲的是使用VS调用WMI提供的接口去获取系统的信息。那么,VS中怎么使用WMI呢?接下来,我就把我所了解的知识分享给大家。
    函数介绍CoInitializeEx 函数
    为当前线程初始化COM库并设置并发模式 。
    函数声明
    HRESULT CoInitializeEx( void * pvReserved, DWORD dwCoInit);
    参数

    pvReserved系统 保留的参数,必须传入 NULL。dwCoInit该标示指明基于当前线程的并发模式和初始化选项。该参数是 COINIT 枚举类型,传入参数时候,除了COINIT_APARTMENTTHREADED 和COINIT_MULTITHREAD ED标记外,其余的标记可以组合使用。
    返回值

    S_OK :COM库初始化成功。S_FALSE :当前线程上,COM库已经被初始化。RPC_E_CHANGED_MODE :COM库已经被初始化且传入参数设置的并发模式和本次不同。

    CoInitializeSecurity 函数
    注册安全性并设置进程的默认安全性值。
    函数声明
    HRESULT CoInitializeSecurity( _In_opt_ PSECURITY_DESCRIPTOR pSecDesc, _In_ LONG cAuthSvc, _In_opt_ SOLE_AUTHENTICATION_SERVICE *asAuthSvc, _In_opt_ void *pReserved1, _In_ DWORD dwAuthnLevel, _In_ DWORD dwImpLevel, _In_opt_ void *pAuthList, _In_ DWORD dwCapabilities, _In_opt_ void *pReserved3);
    参数

    pSecDesc [in]服务器将用于接收呼叫的访问权限。cAuthSvc [in]asAuthSvc参数中的条目计数。只有当服务器调用CoInitializeSecurity时,此参数才被COM使用。如果此参数为0,则不会注册认证服务,并且服务器无法接收安全呼叫。值为-1表示COM选择要注册的身份验证服务,如果是这种情况,则asAuthSvc参数必须为NULL。但是,如果参数为-1,则服务器将不会选择Schannel作为身份验证服务。asAuthSvc [in]一组服务器愿意用来接收呼叫的认证服务。pReserved1 [in]此参数是保留的,必须为NULL。dwAuthnLevel [in]进程的默认身份验证级别。dwImpLevel [in]代理的默认模拟级别。此参数的值仅在进程为客户端时使用。pAuthList [in]指向SOLE_AUTHENTICATION_LIST的指针,它是一个SOLE_AUTHENTICATION_INFO结构的数组。dwCapabilities [in]通过设置一个或多个EOLE_AUTHENTICATION_CAPABILITIES值指定的客户端或服务器的附加功能。pReserved3 [in]此参数是保留的,必须为NULL。
    返回值

    S_OK :COM库初始化成功。RPC_E_TOO_LATE:CoInitializeSecurity 已经被调用。E_OUT_OF_MEMORY:内存不足。

    CoCreateInstance 函数
    用指定的类标识符创建一个COM对象,用指定的类标识符创建一个未初始化的对象。
    函数声明
    STDAPI CoCreateInstance( REFCLSID rclsid, //创建的Com对象的类标识符(CLSID) LPUNKNOWN pUnkOuter, //指向接口IUnknown的指针 DWORD dwClsContext, //运行可执行代码的上下文 REFIID riid, //创建的Com对象的接口标识符 LPVOID * ppv //用来接收指向Com对象接口地址的指针变量);
    参数

    rclsid[in] 用来唯一标识一个对象的CLSID(128位),需要用它来创建指定对象。pUnkOuter[in] 如果为NULL, 表明此对象不是聚合式对象一部分。如果不是NULL, 则指针指向一个聚合式对象的IUnknown接口。dwClsContext[in] 组件类别. 可使用CLSCTX枚举器中预定义的值。riid[in] 引用接口标识符,用来与对象通信。ppv[out] 用来接收指向接口地址的指针变量。如果函数调用成功,*ppv包括请求的接口指针。
    返回值

    S_OK:指定的Com对象实例被成功创建。REGDB_E_CLASSNOTREG:指定的类没有在注册表中注册. 也可能是指定的dwClsContext没有注册或注册表中的服务器类型损坏。CLASS_E_NOAGGREGATION:这个类不能创建为聚合型。E_NOINTERFACE:指定的类没有实现请求的接口, 或者是IUnknown接口没有暴露请求的接口。

    CoSetProxyBlanket 函数
    设置将用于在指定代理上进行呼叫的认证信息。
    函数声明
    HRESULT CoSetProxyBlanket( _In_ IUnknown *pProxy, _In_ DWORD dwAuthnSvc, _In_ DWORD dwAuthzSvc, _In_opt_ OLECHAR *pServerPrincName, _In_ DWORD dwAuthnLevel, _In_ DWORD dwImpLevel, _In_opt_ RPC_AUTH_IDENTITY_HANDLE pAuthInfo, _In_ DWORD dwCapabilities);
    参数

    pProxy [in]要设置的代理。dwAuthnSvc [in]要使用的身份验证服务。dwAuthzSvc [in]要使用的授权服务。pServerPrincName [in]要与身份验证服务一起使用的服务器主体名称。dwAuthnLevel [in]要使用的认证级别。dwImpLevel [in]要使用的模拟级别。pAuthInfo [in]指向建立客户端身份的RPC_AUTH_IDENTITY_HANDLE值的指针。由句柄引用的结构的格式取决于认证服务的提供者。dwCapabilities [in]这个代理的功能。
    返回值

    S_OK:执行成功。E_INVALIDARG:一个或者多个参数无效。

    实现原理现在,我们来解析下在VS2013中使用WMI的原理过程:

    首先,我们要使用CoInitializeEx初始化COM组件环境。因为WMI是基于COM组件实现的,使用COM组件前,必须要进行初始化操作。
    然后,使用CoInitializeSecurity注册安全性并设置进程的默认安全性值。
    接着,使用CoCreateInstance创建一个IWbemLocator的COM对象。
    跟着,通过IWbemLocator::ConnectServer函数连接到WMI,并获取IWbemServices指针。
    然后,使用CoSetProxyBlanket设置连接的安全级别。
    接着,使用IWbemServices指针发出WMI请求,执行WQL语句。
    然后,从查询返回集中获取获取返回数据。
    进行清理工作。

    其中,WQL其实非常简单,它有如下特点:
    SELECT 属性名 FROM 类名每个WQL语句必须以SELECT开始,SELECT后跟你需要查询的属性名,也可以像SQL一样,以*表示返回所有属性值。然后,FROM关键字,即你要查询的类的名字。
    本文使用的WQL语句是:SELECT * FROM Win32_Process。
    编码实现加载WMI所需的库文件#include <comdef.h>#include <WbemIdl.h>#pragma comment(lib, "wbemuuid.lib")
    调用WIM获取进程信息int WMI_EnumProcess(){ /*******************************************/ // 初始化操作 // /*******************************************/ // 初始化COM组件 HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hRes)) { printf("CoInitializeEx Error[%d]\n", hRes); return 1; } // 设置进程的安全级别 hRes = ::CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hRes)) { printf("CoInitializeSecurity Error[%d]\n", hRes); return 2; } /*******************************************/ // 创建一个WMI命名空间连接 // /*******************************************/ // 创建一个CLSID_WbemLocation对象 IWbemLocator *pIWbemLocator = NULL; hRes = ::CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)(&pIWbemLocator)); if (FAILED(hRes)) { printf("CoCreateInstance Error[%d]\n", hRes); return 3; } // 使用 pIWbemLocator 连接到 "root\cimv2", 并获取 pIWbemServices IWbemServices *pIWbemServices = NULL; hRes = pIWbemLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, NULL, 0, NULL, NULL, &pIWbemServices); if (FAILED(hRes)) { printf("pIWbemLocator->ConnectServer Error[%d]\n", hRes); return 4; } /*******************************************/ // 设置连接的安全级别 // /*******************************************/ // 设置连接的安全级别 hRes = ::CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); if (FAILED(hRes)) { printf("CoSetProxyBlanket Error[%d]\n", hRes); return 5; } /*******************************************/ // 执行WQL查询代码 // /*******************************************/ // 这里是列出正在运行进程的例子 // 为了接收结果, 你必须定义一个枚举对象 IEnumWbemClassObject *pIEnumWbemClassObject = NULL; hRes = pIWbemServices->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM Win32_Process"), // 类似数据库中的 SQL 语句 WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pIEnumWbemClassObject); if (FAILED(hRes)) { printf("pIWbemServices->ExecQuery Error[%d]\n", hRes); return 6; } // 显示查询结果 IWbemClassObject *pIWbemClassObject = NULL; ULONG ulRet = 0; VARIANT varProp = { 0 }; while (pIEnumWbemClassObject) { // 获取下一个对象 hRes = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &ulRet); // 判断获取完毕结束 if (0 == ulRet) { break; } // 获取 ProcessId 字段的值 并 显示 ::VariantClear(&varProp); pIWbemClassObject->Get(L"ProcessId", 0, &varProp, NULL, NULL); printf("[%d]\t", varProp.intVal); // 获取 Name 字段的值 并 显示 ::VariantClear(&varProp); pIWbemClassObject->Get(L"Name", 0, &varProp, NULL, NULL); printf("[%ws]\n", varProp.bstrVal); } /*******************************************/ // 清理释放 // /*******************************************/ // 释放 ::VariantClear(&varProp); pIWbemServices->Release(); pIWbemLocator->Release(); ::CoUninitialize(); return 0;}
    程序测试在 main 函数中调用上述封装好的函数进行测试,main 函数为:
    int _tmain(int argc, _TCHAR* argv[]){ // 调用 WMI 去查询进程信息 WMI_EnumProcess(); system("pause"); return 0;}
    测试结果:
    运行程序,进程信息成功显示。所以测试成功。

    总结在VS中使用WMI,流程总是这么几步。要查询其他信息,也只是更改下WQL查询语句以及查询结果的显示而已。如果你学过数据库,或者了解过数据库相关的知识的话,你会发现WQL查询语句和数据SQL语句是很相似的。那是因为,我们之前提到的,WMI也有自己的数据库,也会在数据库里存储一些信息。
    参考文档WMI入门(一):什么是WMI
    WMI技术介绍和应用——WMI概述
    《Windows黑客编程技术详解》一书
    1  留言 2018-11-30 21:25:55
  • 程序在Debug模式下运行正常而在Release模式下运行出错的解决方案

    背景以前在网上查资料的时候,无意中开发别人出现过这样的一个问题:“程序在Debug模式下运行正常,而在Release模式下运行出错”。当时就下意识认为,这不可能,肯定是代码有改动了!但,当自己写的一个小程序出现了这个问题后,才开始正式这个问题。
    现在把当时解决问题的一些思路总结下,分享给大家,希望大家少走弯路吧。
    分析过程在VS开发环境中,Debug模式和Release模式并没有本质上的区别,编译使用的都是同一份源码,这是众所周知的。

    其中,Debug通常称为调试版本,通过一系列编译选项的配合,编译的结果通常包含调试信息,而且不做任何优化,以为开发人员提供强大的应用程序调试能力。
    而Release通常称为发布版本,是为用户使用的,一般客户不允许在发布版本上进行调试。所以不保存调试信息,同时,它往往进行了各种优化,以期达到代码最小和速度最优。为用户的使用提供便利。

    也就是说,Debug模式和Release模式上的区别,就是在VS开发环境里的编译选项的区别。在明确了这一点之后,就不得不回头重新思考自己的程序了。

    首先,Debug模式下正常运行,所以,代码肯定是没有问题的,便排除了代码有问题的假设
    然而,Release模式下,编译通过,运行却出错了。通过上面,我们知道Debug模式和Release模式只是编译选项的区别,所以会不会是一些编译设置的问题导致的
    突然恍然大悟,因为程序里面使用到了开源的第三方库,而且第三方库也是自己编译出来的。Release模式下出错的位置,也是在执行第三方库代码时候报错的

    于是,这样便确定了出错原因。调用第三方库时,Debug模式和Release模式的编译选项和第三方库的编译选项可能没有对应有关。而且,很有可能就是运行库的设置问题。
    解决过程于是,在Release模式下,打开项目工程的属性页后,展开“C/C++”,点击“代码生成”,更改“运行库”里的选项,依次更改为“/MT”、“多线程调试 (/MTd)”、“多线程 DLL (/MD)”、“多线程调试 DLL (/MDd)”进行测试。

    测试发现,更改为“多线程调试 (/MTd)”的时候,生成的程序,可以正常运行了。一下子便把我惊呆了。因为“/MTd”中,MT表示“Multithread, Static Version”,d表示“Debug”。程序是Release模式,便一开始选择了“/MT”去编译。
    现在看到设置为“多线程调试 (/MTd)”的时候,程序正常,我想可能是因为第三方库在Release模式下编译的时候,设置的可能是“多线程调试 (/MTd)”。
    总结既然程序通了,我也没有继续深究下去了。所以,如果以后大家遇到相类似的情况,可以先把“运行库”中的设置依次选择,测试一遍看看再说。
    参考参考自《Windows黑客编程技术详解》一书
    2  留言 2018-11-06 21:57:46

发送私信

只愿岁月静好,别无所求

20
文章数
13
评论数
eject