目录
前言
接下来我们来实现一个MFC版本QQ安全卫士的仿造品,我们需要掌握的前置知识点如下:
(1)熟悉windows原理,学会windowsAPI解析PE文件
(2)熟悉windows的权限管理和hook注入的相关知识
(3)理解MFC界面编程和python网络编程
(4)python结合mysql开发
还不会的同学请关注本专栏,尽快学习前面的章节知识点,下面开始介绍本项目。
一、实现功能
1. 任务管理器功能:能够全面遍历进程、线程以及模块,同时对桌面窗口进行有效管理,方便用户清晰掌握系统运行状态。
2. 系统资源监控与优化:精准监测CPU和内存的使用率,并具备内存优化功能,提升系统整体运行效率。
3. 垃圾清理功能:涵盖系统垃圾、浏览器缓存垃圾以及VS开发环境产生的垃圾文件清理,释放磁盘空间。
4. 服务管理功能:可遍历系统内的所有服务,并支持启动和关闭特定服务,满足用户对系统服务的定制化管理需求。
5. 杀毒功能模块:
- MD5查杀:通过对比文件MD5值识别病毒文件。
- 全路径查杀:依据文件路径特征进行病毒排查。
- 白名单查杀:基于白名单机制,识别并处理未在名单内的进程。
6. 数据库与网络应用:
- 云查杀:利用网络连接服务器,实现云端病毒查杀。
- 提交样本:支持用户提交可疑文件样本,助力病毒库更新。
- 更新病毒库:及时从服务器获取最新病毒信息,保障查杀能力。
7. 进程保护功能:对关键进程进行防护,防止其被恶意终止或篡改。
8. PE文件解析:深入分析PE文件结构,辅助病毒检测和系统维护。
二、附加功能
1. 老板键功能:设置便捷老板键,快速隐藏特定窗口,保护隐私。
2. 系统控制功能:提供关机、重启、休眠等系统操作快捷入口。
3. 文件信息查看:可查询文件的创建时间、修改时间以及MD5值,方便用户了解文件属性。
4. 启动项与软件管理:能够管理系统启动项,同时支持软件卸载操作。
5. 内存优化小火箭及界面优化:以直观的小火箭形式展示内存优化功能,注重界面友好度设计,采用自定义控件提升用户体验。
三、开发环境
- 操作系统:Windows 10
- 数据库:MySQL 5.6
- Python版本:Python 3.6.2 32bit
- 开发工具:VS 2015或更高版本
四、数据库简单字段设计
- 病毒库:包含ID和MD5字段,用于存储病毒文件的唯一标识和对应的MD5值。
- 黑名单库:同样设置ID和MD5字段,记录黑名单中的文件标识及MD5信息。
- 白名单库:通过ID和MD5字段,保存白名单内文件的相关信息。
- 用户表备用:预留用户表,可用于后续扩展用户相关功能 。
五、代码架构
客户端在相应窗口位置发送消息,在特定位置接收消息,开启线程发送更新消息,线程维护,同时保留socket统一接收接口。
六、软件界面
七、功能架构
八、部分功能截图
解析PE文件信息
PE头
区段信息
目录信息
导入表
导出表
资源表
重定位表
延迟加载表
TLS表
集成任务管理器
遍历进程线程模块
遍历文件信息
遍历窗口信息
VS工程垃圾清理
老板键
关机重启等
九、相关实现细节概要
关于任务管理器的实现细节请看上一节,下面介绍本项目剩余部分细节实现。
9.1 获取文件信息
(1)获取文件的创建时间、修改时间和文件大小
要是我们知道了文件的路径,就可以用FindFirstFile()函数来获取单个文件的信息。用这个函数能得到文件的好多信息,这些信息都存放在一个结构体里。下面是这个结构体的原型和注解:
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes; //文件属性
FILETIME ftCreationTime; //文件创建时间
FILETIME ftLastAccessTime; //文件最后的访问时间
FILETIME ftLastWriteTime; //文件最后写入的时间
DWORD nFileSizeHigh; //文件字节数的高32位
DWORD nFileSizeLow; //文件字节数低32位
DWORD dwReserved0; //保留
DWORD dwReserved1; //保留
_Field_z_WCHAR cFileName[MAX_PATH ];//文件名
_Field_z_WCHAR cAlternateFileName[14 ];
#ifdef _MAC
DWORD dwFileType;
DWORD dwCreatorType;
WORD WFinderFlags;
#endif
}WIN32_FIND_DATAW,*PWIN32_FIND_DATAW,*LPWIN32_FIND_DATAW;
由于我们只打算获取一个文件的信息,所以文件路径不用加通配符,也不用调用FindNextFile()函数去逐个查找文件。具体做法如下:
WIN32_FIND_DATA stcFData={0};
HANDLE hFind =FindFirstFile(L"c:\\123.exe",&stcFData);
if(hFind ==INVALID_HANDLE_VALUE)
return FALSE;
最后,就可以使用stcFData结构体中的内容了。
(2)获取文件的MD5值
没有现成的API能直接算出文件的MD5值,不过网上有开源代码可以做这个计算。
9.2 清理电脑垃圾信息
9.2.1 回收站
(1)相关函数及结构体
typedef struct {
DWORD cbSize; //本结构体的大小
int64 i64Size; //回收站的大小
int64 i64NumItems;//回收站中文件的数量
}SHQUERYRBINFO,*LPSHQUERYRBINFO;
- 获取回收站信息
HRESULT SHQueryRecycleBin(
LPCTSTR pszRootPath, //文件或文件夹的路径
LPSHQUERYRBINFO pSHQueryRBInfo //返回SHQUERYRBINFO结构体信息
);
- 清空回收站信息
- `pszRootPath`:这个参数是回收站在根驱动器里的路径地址。它可以是用字符串表示的驱动器、文件夹和子文件夹的名称,也能是空字符串或者NULL。要是为空,就意味着会清空所有驱动器上的回收站。
- `dwFlags`:这是清空回收站时的一些操作设置。
- `SHERB_NOCONFIRMATION`:删除时不会弹出确认删除的对话框。
- `SHERB_NOPROGRESSUI`:删除时不显示删除进度条。
- `SHERB_NOSOUND`:删除完成后不会有提示音。
(2)思路
1. 初始化SHQUERYRBINFO结构体
SHQUERYRBINFO RecycleBinInfo ={};
RecycleBinInfo.cbSize =sizeof (RecycleBinInfo);
2. 查询回收站信息
SHQueryRecycleBin(NULL,&RecycleBinInfo);
//SHQueryRecycleBin第一参数为要查询回收站的盘符或者文件夹
3. 清空回收站
SHEmptyRecycleBin(NULL,NULL,SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);
9.2.2 清理指定数据下的文件
(1)相关函数及结构体
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; //文件属性
FILETIME ftCreationTime; //文件创建时间
FILETIME ftLastAccessTime; //文件最后一次访问时间
FILETIME ftLastWriteTime; //文件最后一次修改时间
DWORD nFileSizeHigh; //文件长度(高32位)
DWORD nFileSizeLow; //文件长度(低32位)
DWORD dwReserved0; //系统保留
DWORD dwReserved1; //系统保留
TCHAR cFileName[MAX_PATH]; //文件名
TCHAR cAlternateFileName[14]; //类似于短路径
}WIN32_FIND_DATA,*PWIN32_FIND_DATA,*LPWIN32_FIND_DATA;
typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
DWORD dwFileAttributes; //文件系统文件或目录的属性信息。
FILETIME ftCreationTime; //创建文件和文件夹的时间
FILETIME ftLastAccessTime; //文件最后一次访问时间
FILETIME ftLastWriteTime; //文件最后一次修改时间
DWORD nFileSizeHigh; //文件长度(高32位)
DWORD nFileSizeLow; //文件长度(低32位)
}WIN32_FILE_ATTRIBUTE_DATA,*LPWIN32_FILE_ATTRIBUTE_DATA;
1. 查找第一个文件
HANDLE WINAPI FindFirstFile(
LPCTSTR lpFileName, //要查询的文件路径
LPWIN32_FIND_DATA lpFindFileData //指向文件信息结构体
);
2. 获取文件属性
BOOL GetFileAttributesEx(
LPCTSTR lpFileName, //文件路径
GET_FILEEX_INFO_LEVELS fInfoLevelId,//指定要获取的属性信息的设置
LPVOID lpFileInformation //指向WIN32_FIND_DATA结构体。
);
也可以通过FindFirstFile函数返回的结构体信息确定文件的属性
3. 获取文件的后缀名
PTSTR PathFindExtension(
PTSTR pszPath //文件的路径
);
4. 获取下一个文件
BOOL FindNextFile(
HANDLE hFindFile, //FindFirstFile的返回值
LPWIN32_FIND_DATA lpFindFileData //指向文件信息结构体
);
(2)思路
实现删除C盘根目录下指定类型的文件
LARGE_INTEGER EnumFoldAndDeleteFile(WCHAR *lpFoldPath,WCHAR *1pFileType)
{
//1. 初始化路径字符串
WCHAR szFoldPath[MAX_PATH]=L"C:\\";
wcscat_s(szFoldPath, L"*");//在路径字符串追加通配符L"\\*"
//2.获取第一个文件的信息
ULARGE_INTEGER qwFileTotalSize ={}; //文件总大小
WIN32_FIND_DATA w32FindData ={};
HANDLE hFindFile =FindFirstFile(szFoldPath,&w32FindData);
//3.循环遍历获取当前目录中的文件信息
do{
//去掉两个特殊目录
if((!wcscmp(w32FindData.cFileName,L"."))|| (!wcscmp(w32FindData.cFileName,L"..")))
continue;
if(w32FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
//文件夹,继续删除文件夹里面的内容
WCHAR szSubfoldPath[MAX_PATH];
StringCchPrintf(szSubfoldPath,MAX_PATH,L"%s\\",lpFoldPath);
//追加反斜杠
StringCchCat(szSubfoldPath,MAX_PATH,w32FindData.cFileName);
//子目录名
qwFileTotalSize =EnumFoldAndDeleteFile(szSubfoldPath,1pFileType);
}
else{
//获取文件的后缀
WCHAR szFullPath[MAX_PATH];
StringCchPrintf(szFullPath, MAX_PATH, L"%s\\%s", lpFoldPath, w32FindData.cFileName);
LPWSTR FileSuffix=PathFindExtension(szFullPath);
if((!lstrcmp(FileSuffix,L".tlog")) || (!lstrcmp(FileSuffix, L".obj"))
|| (!lstrcmp(FileSuffix, L".log"))
|| (!lstrcmp(FileSuffix,L".pch")) || (!lstrcmp(FileSuffix,L".ilk"))
|| (!lstrcmp(FileSuffix, L".pdb")))
{
ULARGE_INTEGER fileSize; //记录文件的大小
fileSize.HighPart =w32FindData.nFileSizeHigh;
fileSize.LowPart =w32FindData.nFileSizeLow;
qwFileTotalSize.QuadPart +=fileSize.QuadPart;
DeleteFile(szFullPath); //删除文件
}
}
}while(FindNextFile(hFindFile,&w32FindData));
return qwFileTotalSize;
}
要是想获取文件大小,由于WIN32_FIND_DATA结构体里文件长度分高位和低位,我们可以借助另一个结构体把它们拼接起来得到完整的文件大小,最后直接用fileSize.QuadPart就行。
像系统临时文件:C:\Windows\Temp
用户临时文件:C:\Users\kevin\AppData\Local\Temp
Windows错误报告:C:\Users\kevin\AppData\Local\Microsoft\Windows\WER\ReportQueue
至于其他的垃圾信息路径,就得自己去收集了,可以参考电脑管家垃圾清理功能里涉及的文件路径。接下来讲讲9.3杀毒功能,包括MD5查杀、全路径查杀和白名单查杀。
(1)相关函数及结构体
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName, //要打开的文件名
_In_ DWORD dwDesiredAccess, //打开方式
_In_ DWORD dwShareMode, //是否共享读
_In_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全设置
_In_ DWORD dwCreationDisposition,//操作文件属性
_In_ DWORD dwFlagsAndAttributes,//文件本身的属性
_In_opt_ HANDLE hTemplateFile //另一个文件句柄,模板文件
);
BOOL WriteFile(
HANDLE hFile, //文件句柄
LPCVOID lpBuffer, //要写入的数据缓冲区
DWORD nNumberOfBytesToWrite, //要写入缓冲区的大小
LPDWORD lpNumberOfBytesWritten,//实际写入文件的字节数
LPOVERLAPPED lpOverlapped //指向OVERLAPPED结构体
);
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
(2)思路
1. 将MD5值手动保存在文件中(.txt)
2. 打开文件
HANDLE hFile =CreateFile(L"5.txt", //名为"5.txt"
GENERIC_READ, //以读方式打开
0, //不能被共享
NULL, //默认安全描述子,不能被继承
OPEN_EXISTING, //只打开以存在的文件
FILE_ATTRIBUTE_NORMAL, //常规文件属性
NULL); //无模板
3. 读取文件中的信息
char ch[100]={};
DWORD dwReads;
ReadFile(hFile,ch,100,&dwReads,NULL);
4. 关闭文件句柄
CloseHandle(hFile);
- MD5查杀:先在一个文件里随意存上几个软件或者文件的MD5值,之后获取需要检测文件的MD5值,再把它和之前存好的MD5值对比,以此判断文件是否安全。
- 全路径查杀:采用逐个查找文件的方式,把找到的每个文件的MD5值和文件路径都进行分析比较。打个比方,要是在C盘根目录下发现有QQ.exe这个文件,就判定它是病毒,这算是一种很基础的文件查杀手段 。
- 白名单查杀:挨个检查进程,看看正在运行的进程是不是在自己预先设定好的白名单里面,如果不在,就把这个进程关掉。
9.3 数据库与网络运用
(云杀毒,从服务器获取查杀的信息)
9.3.1 数据库
(1)病毒信息存于数据库思路
1. 创建数据库
create database 病毒数据库
2. 创建病毒数据表
create table 病毒信息表
(
NAME nchar(50), --病毒名
MD5 nchar(50)not null, --病毒MD5值
)
3. 插入病毒数据(手动在数据库引擎中添加)
Insert Into病毒信息表(NAME,MD5)
values('QQScLauncher.exe','9800BDO8FCF2F15E74B88F3359CC0420')
Insert Into病毒信息表(NAME,MD5)
values('QQScLauncher.exe','9800BD08FCF2F15E74B88F3359CC0420')
4. 用ADO编程的办法来添加和查找数据:要是想知道具体怎么查找和添加,去看上课时发的关于ADO的资料,那里边讲得很详细。
注意:你可以自己在http://www.virscan.org这个网站上找几个你觉得是病毒的信息,然后保存下来。之后在数据库里要是能查到这些信息,那就说明对应的文件是病毒。
5. 创建两个账户,它们的权限不一样,一个账户只有读取数据的权限,另一个账户既能读取又能写入数据,这就是权限管理。
9.3.2 网络应用
(1)数据库在服务器上运行,我们从服务器获取病毒信息:
1. 在服务器那边,用ADO来连接自身服务器,就是把对数据库的各种访问操作都放在服务端进行。接着选一种网络模型(比如消息模型),用来接收客户端发过来的信息。
2. 在客户端这边,先获取文件的MD5值,然后把它发给服务端。服务端查完相关结果后,再把对应的信息返回给客户端。关于网络通信的具体内容,参考课上的资料(比如消息模型部分)。
(2)提交样本信息:
1. 客户端向服务端发送数据,服务端要判断收到的数据,到底是提交的样本信息,还是查询信息。判断方式有:
- 在网络编程里,通过数据名字前面加特殊符号的方法来区分。
- 把客户端要发的数据定义成结构体(结构体里包含数据类型、数据长度、结构体自身长度等信息),这样每次发送的数据就是结构体指针。服务端也用同样的结构体来接收数据。这里有个问题,当发送的数据太长时,没办法一次接收完。这时候结构体里的数据长度就很关键了。用recv接收数据后,会返回当前实际收到的数据长度,如果这个长度小于结构体里保存的数据长度,那就得再接收一次,并且把新收到的数据存到之前数据缓冲区的后面。
例子:
客户端:
//发给服务器的数据包,包括消息头,大小和MD5值
struct MessageContent
{
DWORD dwMessageType; //消息类型
ULARGE_INTEGER ulFileSize; //MD5值的数据大小
char szMD5[36]; //MD5值
};
MessageContent msg;
send(socket,(char *)&msg,sizeof (msg),0);
服务端:用`recv()`函数接收到数据后,把它转成MessageContent类型就行。接着对收到的数据做判断,然后根据判断结果操作数据库,进行增加、删除、修改、查询这些操作。
9.4 注册表操作
(启动项和软件卸载)
9.4.1 软件卸载
(1)相关结构体和函数
1. 打开子句键句柄
LONG RegOpenKeyEx(
_In_ HKEY hKey, //根键句柄
_In_opt_ LPCTSTR lpSubKey, //子键路径
_Reserved_ DWORD ul0ptions, //保留NULL
_In_ REGSAM samDesired,//打开键句柄的权限
_0ut_ PHKEY phkResult //子键句柄
);
2. 枚举获取Uninstall文件夹下的子文件名称
LONG RegEnumKeyEx(
_In_ HKEY hKey, //RegOpenKeyEx获取到的子键句柄
_In_ DWORD dwIndex, //指定被枚举键下子键的索引,从0开始
_0ut_ LPTSTR lpName, //保存子键的名称
_Inout LPDWORD lpcName, //指向szKeyName内存的大小
_Reserved LPDWORD lpReserved,//保留 必须为0
_Inout_ LPTSTR lpClass, //DWORD变量地址,用于获取子键的类型
_Inout_opt_ LPDWORD lpcClass, //指向一块内存,用于获取子键值数据
_Out_opt_ PFILETIME lpftLastWriteTime //上一个参数所向的内存大小
);
3. 获取注册表项指定值的名称和数据类型
LONG RegQueryValueEx(
_In_ HKEY hKey, //子键句柄
_In_opt_ LPCTSTR lpValueName,//要查询的键值名称
_Reserved LPDWORD lpReserved,//保留 必须为0
_0ut_opt LPDWORD lpType, //键值的类型
_0ut_opt_ LPBYTE lpData, //保存得到的结果
_Inout_opt_LPDWORD lpcbData ); //缓冲的大小每次查询时都初始化一下
(2)思路
软件卸载信息有两处,通常系统版本为x64则遍历x64子键
- 主键:HKEY_LOCAL_MACHINE
- 32位下:
子键:Software\Microsoft\Windows\CurrentVersion\Uninstall
- 64位下:
子键:Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
//自定义结构体
typedef struct _SOFTINFO
{
WCHAR szSoftName[50]; //软件名称
WCHAR szSoftVer[50]; //软件版本号
WCHAR szSoftDate[20]; //软件安装日期
WCHAR szSoftSize[MAX_PATH]; //软件大小
WCHAR strSoftInsPath[MAX_PATH]; //软件安装路径
WCHAR strSoftUniPath[MAX_PATH]; //软件卸件路径
WCHAR strSoftVenRel[50]; //软件发布厂商
WCHAR strSoftIco[MAX_PATH]; //软件图标路径
}SOFTINFO,*PSOFTINFO;
vector<SOFTINFO>m_vectSoftInfo; //保存软件相关信息
HKEY RootKey =HKEY_LOCAL_MACHINE; //主键
//子键名称(x86)
LPCTSTR lpSubKey =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
HKEY hkResult =0; //接收将要打开的键的句柄
//1.打开一个己经存在的注册表键
LONG lReturn =RegOpenKeyEx(RootKey, lpSubKey, 0,
KEY_ENUMERATE_SUB_KEYS|
KEY_QUERY_VALUE, //指定打开键句柄的权限
&hkResult); //子键句柄
//2.循环遍历Uninstall目录下的子键
while(true)
{
DWORD dwIndex =0;
DWORD dwKeyLen =255;
WCHAR szNewKeyName[MAX_PATH]={}; //注册表项名称
LONG lReturn =RegEnumKeyEx(hkResult, //子键句柄
dwIndex, //指定被枚举键下子键的索引,从0开始
szNewKeyName, //指向一块内存保存子键的名称
&dwKeyLen, //指向szKeyName内存的大小
0, //保留 必须为0
NULL, //用于获取子键值的类型
NULL, //指向一块内存,用于获取子键值数据
NULL); //上一个参数所向的内存大小
//2.1通过得到的子键名称重新组合成新的子键路径
WCHAR strMidReg[MAX_PATH]={};
swprintf_s(strMidReg,L"%s%s%s",lpSubKey,L"\\",szNewKeyName);
//2.2打开新的子键,获取其句柄
HKEY hkValueKey =0; //子键句柄
RegOpenKeyEx(RootKey,strMidReg,0,KEY_QUERY_VALUE,&hkValueKey);
//2.3获取键值
DWORD dwNameLen =255; //指向szBuffer内存的大小
//获取软件名称
RegQueryValueEx(hkValueKey,L"DisplayName",0,&dwType,
(LPBYTE)m_vectSoftInfo[0].szSoftName,&dwNameLen);
dwNameLen =255; //如果没有重新赋值下一次将获取不到信息
//拆卸路径
RegQueryValueEx(hkValueKey,L"UninstallString",0,&dwType,
(LPBYTE)m_vectSoftInfo[0].strSoftUniPath,&dwNameLen);
dwNameLen =255;
dwIndex++; //子键的索引
}
最后一步卸载软件只需要将得到的拆卸路径以OpenFile打开即可。
9.4.1 管理启动项
(1)相关函数
1. 删除键值
LONG WINAPI RegDeleteValue(
_In_ HKEY hKey, //打开要删除键值所属的子健
_In_opt_ LPCTSTR lpValueName //键值名
);
2. 枚举键值
LONG WINAPI RegEnumValue(
_In_ HKEY hKey, //主键
_In_ DWORD dwIndex, //索引,第1个
_0ut_ LPTSTR lpValueName, //键值的名称
_Inout LPDWORD lpcchValueName,//键值的大小
_Reserved LPDWORD lpReserved, //保留
_0ut_opt_ LPDWORD lpType, //类型
_0ut_opt_ LPBYTE lpData, //保存数据
_Inout_opt LPDWORD lpcbData //lpData的长度
);
(2)思路
注册表启动项在HKEY_CURRENT_USER和HKEY_LOCAL_MACHINE下面的:
- X86:
- L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
- L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run",
- L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
- L"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices0nce",
- L"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
- L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx",
- X64:
- L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run"
- L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce"
//HKEY_CURRENT_USER下的指定键值
WCHAR RegBootStartItemInHKCU[REG_BOOT_START_ITEM_HKCU][MAX_PATH]={
L"Software \\Microsoft\\WindowsNT\\CurrentVersion\\Windows",
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
};
获取启动程序相关信息:前面的操作和拆卸软件的一样,先枚举有子键,再枚举子键内的值。
DWORD dwIndex =0;
RegEnumValue(hSubKey,dwIndex,szKeyValueName,&dwValueNameLen,0,
NULL,(PBYTE)szValue,&dwValueLen);
1. 打开要删除键值所属的子健
HKEY hSubkey;
RegOpenKeyEx(hKey,lpSubkey,0,KEY_SET_VALUE,&hSubkey);
2. 删除指定键值
RegDeleteValue(hSubkey,lpKeyValueName);
9.5 遍历服务,启动,关闭服务
在Windows系统里,有个服务控制管理器,它存着电脑上所有服务的信息和状态。只要能操作这个服务控制管理器,就能获取所有服务的信息和状态,还能加载、启动、停止、关闭服务。下面是操作服务控制管理器的方法:
9.6.1 获取服务控制管理器的句柄
操作服务控制管理器,就跟读写文件一样,都得有个句柄。下面是获取服务控制管理器句柄的API原型和注解:
SC_HANDLE OpenSCManagerW(LPCWSTR lpMachineName, //主机名
LPCWSTR lpDatabaseName, //数据库名
DWORD dwDesiredAccess //访问方式
);
- `lpMachineName`:这是主机名,它可以是远程主机的名字。要是填NULL,就表示打开本地主机。
- `lpDatabaseName`:这个参数要设置成SERVICES_ACTIVE_DATABASE,如果填NULL,系统会自动把它设成这个宏的值。
- `dwDesiredAccess`:这是访问权限,有下面这些宏可以用:
- `SC_MANAGER_ALL_ACCESS`:拥有所有权限。
- `SC_MANAGER_CREATE_SERVICE`:创建服务的权限,使用CreateService()函数时必须要有这个权限。
- `SC_MANAGER_CONNECT`:连接服务控制管理器的权限。
- `SC_MANAGER_ENUMERATE_SERVICE`:枚举所有服务的权限,使用EnumServicesStatus()函数和EnumServicesStatusEx()函数时需要这个权限。
更多详细信息可以自己去查MSDN(在函数名上按F1就能查到)。
9.6.2 枚举所有服务
如果想获取所有服务的信息,那么可以使用枚举服务的API。枚举服务的API原型和注解如下:
BOOL EnumServicesStatusEx(SC_HANDLE hSCManager, //SCM数据库句柄
SC_ENUM_TYPE InfoLevel, //要返回的属性
DWORD dwServiceType, //服务类型
DWORD dwServiceState, //服务器状态
LPBYTE lpServices, //接收信息缓冲区
DWORD cbBufSize, //缓冲区大小
LPDWORD pcbBytesNeeded, //需要的大小
LPDWORD lpServicesReturned, //缓冲区中的服务个数
LPDWORD lpResumeHandle, //NULL
LPCTSTR pszGroupName //NULL
);
- `hSCManager`:这个是OpenSCManagerW函数的返回值。
- `InfoLevel`:一般填SC_ENUM_PROCESS_INFO。
- `dwServiceType`:这是要枚举的服务类型,一般填SERVICE_WIN32,有下面这些宏可以选:
- `SERVICE_DRIVER`:驱动服务。
- `SERVICE_FILE_SYSTEM_DRIVER`:文件系统驱动服务。
- `SERVICE_KERNEL_DRIVER`:内核驱动服务。
- `SERVICE_WIN32`:应用进程的服务,相当于下面两个宏的组合。
- `SERVICE_WIN32_OWN_PROCESS`
- `SERVICE_WIN32_SHARE_PROCESS`
- `lpServices`:用来存枚举出来的服务信息的缓冲区。
- `cbBufSize`:缓冲区的实际大小。
- `pcbBytesNeeded`:需要的缓冲区大小。
- `lpServicesReturned`:获取到的服务信息数量。
这个函数的用法和枚举模块的函数用法差不多。通常先传一个无效的缓冲区去调用这个函数,得到所需缓冲区的字节数,然后申请动态内存,再调用一次这个函数,把有效的缓冲区传进去,这样就能得到所有服务的信息了。
其实,这个缓冲区是一个ENUM_SERVICE_STATUSW结构体的数组,`lpServicesReturned`说的就是这个数组里元素的个数。
9.6.3 遍历服务
下面是获取服务信息的示例:
//1.打开远程计算机服务控制管理器
SC_HANDLE hSCM=OpenSCManagerW(NULL,NULL,SC_MANAGER_ALL_ACCESS);
//2.第一次调用,获取需要的内存大小
DWORD dwServiceNum =0;
DWORD dwSize =0;
EnumServicesStatusEx(hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL, // 所有服务状态
NULL, // 缓冲区
0, //缓冲区大小
&dwSize, //需要的大小
&dwServiceNum, // 缓冲区中的服务个数
NULL,NULL
);
//3. 申请需要的内存,第二次调用
LPENUM_SERVICE_STATUS_PROCESS pEnumSerice =(LPENUM_SERVICE_STATUS_PROCESS)new char[dwSize];
//4 . 第二次枚举
bool bStatus =FALSE;
bStatus=EnumServicesStatusEx(hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL, // 所有服务状态
(PBYTE)pEnumSerice, // 缓冲区
dwSize, //缓冲区大小
&dwSize, //需要的大小
&dwServiceNum, // 缓冲区中的服务个数
NULL,NULL
);
//5 . 遍历信息
for(DWORD i=0;i< dwServiceNum;i ++) {
//获取基础信息
// 服 务 名
pEnumSerice[i].lpServiceName;
//服务状态有已停止,正在运行,正在暂停
//(根据得到的值手动输出字符串,具体值的含义可以在该结构体上按F1 查 MSDN)
pEnumSerice[i].ServiceStatus.dwCurrentState;
//服务类型
//有文件系统驱动服务,驱动服务,独立进程服务
//更多详细的信息查MSDN
pEnumSerice[i].ServiceStatus.dwServiceType;
//6 . 获取服务的详细信息这里将只给出函数
// 打 开 服 务
SC_HANDLE hService = OpenService( hSCM, // 服务控制管理器的句柄
pEnumSerice[i].lpServiceName, // 服务名
SERVICE_QUERY_CONFIG // 打开权限
);
//第一次调用获取需要的缓冲区大小
QueryServiceConfig(hService,NULL,0,&dwSize);
//分配内存
LPQUERY_SERVICE_CONFIG pServiceConfig = \
(LPQUERY_SERVICE_CONFIG)new char[dwSize];
//第二次调用,获取信息
QueryServiceConfig(hService,pServiceConfig,dwSize,&dwSize);
//通过上面获取到的结构体信息具体得到想要的值
//获取启动类型
pServiceConfig->dwStartType;
//类型有:"自启动","手动启动,"已禁用"
//获取路径信息
pServiceConfig->lpBinaryPathName;
}
项目代码:https://download.youkuaiyun.com/download/linshantang/90591342