企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 一、前言 注册表是Windows操作系统中的一个非常重要的数据库,里面记录了我们系统的几乎所有信息。也正因为这个原因,病毒木马也常常利用注册表来做文章。而对于杀毒软件来说,注册表也是要重点关注的对象。我在[《反病毒攻防研究第002篇:利用注册表实现自启动》](http://blog.csdn.net/ioio_jy/article/details/38894059)中也提到过,恶意程序可以利用注册表实现自启动,比如可以增加系统的启动项或通过映像劫持等的方法。这次我打算编写一个注册表启动项管理器,利用它可以对注册表的启动项进行查看,还可以删除可疑启动项。尽管功能很简单,但却也是反病毒木马的利器。需要说明的是,这篇文章所编写的程序,仅仅针对[《利用注册表实现自启动》](http://blog.csdn.net/ioio_jy/article/details/38894059)中提到的两个实现程序自启动的注册表键以及键值来讨论的,也就是说我这里并不会在程序中添加所有的注册表启动项,有兴趣的读者可以自行添加。 ## 二、界面的制作 依旧利用MFC创建一个基于对话框的程序,界面如下: ![](https://box.kancloud.cn/2016-02-18_56c5742959253.jpg) 图1 界面设计 程序需要使用两个“Static Text”、两个“List Control”以及三个“Button”控件。需要将两个“List Control”控件的属性都进行如下设置: ![](https://box.kancloud.cn/2016-02-18_56c5742979757.jpg) 图2 设置List Control的属性 最后为界面上面的“List Control”控件添加一个名为“m_RunList”的变量,为下面的“List Control”控件添加一个名为“m_IFEOList”的变量。然后通过编程对两个“List Control”控件进行初始化,主要是对表格的属性进行设置: ~~~ void CRegManageRunDlg::InitRunList() { //设置“List Control”控件的扩展风格 m_RunList.SetExtendedStyle( m_RunList.GetExtendedStyle() | LVS_EX_GRIDLINES //有网络格 | LVS_EX_FULLROWSELECT); //选中某行使整行高亮(只适用于report风格) //添加列目 m_RunList.InsertColumn(0, _T("序号")); m_RunList.InsertColumn(1, _T("名 称")); m_RunList.InsertColumn(2, _T("类 型")); m_RunList.InsertColumn(3, _T("数 据")); //设置列的宽度 m_RunList.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); m_RunList.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER); m_RunList.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER); m_RunList.SetColumnWidth(3, LVSCW_AUTOSIZE_USEHEADER); } void CRegManageRunDlg::InitIFEOList() { //设置“List Control”控件的扩展风格 m_IFEOList.SetExtendedStyle( m_IFEOList.GetExtendedStyle() | LVS_EX_GRIDLINES //有网络格 | LVS_EX_FULLROWSELECT); //选中某行使整行高亮(只适用于report风格) //添加列目 m_IFEOList.InsertColumn(0, _T("序号")); m_IFEOList.InsertColumn(1, _T("被劫持程序名称")); //设置列的宽度 m_IFEOList.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); m_IFEOList.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER); } ~~~ 然后进行对话框的初始化,在OnInitDialog()中添加: ~~~ InitRunList(); InitIFEOList(); ~~~ 最后在头文件中的相应位置添加: ~~~ void InitRunList(); void InitIFEOList(); ~~~ 我之所以要使用两个“ListControl”控件,是因为我需要枚举启动项和映像劫持。前者所枚举的是启动项“Run”这个“键”下面的所有“键值”,而后者所枚举的是“映像劫持”这个“键”下面的所有“子键”。对于枚举不同的内容,我觉得还是分开讨论比较好。 ## 三、编写代码 首先需要编写两个宏定义,表示我们将要枚举的项: ~~~ #define REG_RUN "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\" #define REG_IFEO "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\" ~~~ 接下来编写显示启动项的代码: ~~~ void CRegManageRunDlg::ShowRunList() { //清空列表 m_RunList.DeleteAllItems(); DWORD dwType = 0; DWORD dwBufferSize = MAXBYTE; DWORD dwKeySize = MAXBYTE; char szValueName[MAXBYTE] = { 0 }; char szValueKey[MAXBYTE] = { 0 }; char szType[MAXBYTE] = { 0 }; //打开注册表启动项 HKEY hKeyRun = NULL; LONG lRetRun = RegOpenKey(HKEY_CURRENT_USER, REG_RUN, &hKeyRun); if ( lRetRun != ERROR_SUCCESS ) { AfxMessageBox("注册表打开失败!"); return ; } int i = 0; CString strRunTmp; while ( TRUE ) { lRetRun = RegEnumValue(hKeyRun, i, szValueName, &dwBufferSize, NULL, &dwType, (unsigned char *)szValueKey, &dwKeySize); if ( lRetRun == ERROR_NO_MORE_ITEMS ) { break; } strRunTmp.Format("%d", i); //判断注册表项值的类型 if(dwType == REG_SZ) { //一个以0结尾的字符串 strcpy(szType, "REG_SZ"); } else if(dwType == REG_BINARY) { //任何形式的二进制数据 strcpy(szType, "REG_BINARY"); } else if(dwType == REG_DWORD) { //一个32位的数字 strcpy(szType, "REG_DWORD"); } else if(dwType == REG_EXPAND_SZ) { //一个以0结尾的字符串,该字符串包含对环境变量(如%PATH%)的未扩展引用 strcpy(szType, "REG_EXPAND_SZ"); } else { //其它 strcpy(szType, "OTHER"); } //将获取的注册表内容写入“List Control”控件 m_RunList.InsertItem(i, strRunTmp); m_RunList.SetItemText(i, 1, szValueName); m_RunList.SetItemText(i, 2, szType); m_RunList.SetItemText(i, 3, szValueKey); i++; //清空缓冲区 ZeroMemory(szValueName,MAXBYTE); ZeroMemory(szType,MAXBYTE); ZeroMemory(szValueKey, MAXBYTE); } RegCloseKey(hKeyRun); } ~~~ 然后编写显示映像劫持的代码: ~~~ void CRegManageRunDlg::ShowIFEOList() { //清空列表 m_IFEOList.DeleteAllItems(); DWORD dwSize = MAXBYTE; char szKeyName[MAXBYTE] = { 0 }; //打开注册表映像劫持项 HKEY hKeyIFEO = NULL; LONG lRetIFEO = RegOpenKey(HKEY_LOCAL_MACHINE, REG_IFEO, &hKeyIFEO); if ( lRetIFEO != ERROR_SUCCESS ) { AfxMessageBox("映像劫持打开失败!"); return ; } int j = 0; CString strTmp; while(TRUE) { strTmp.Format("%d", j); lRetIFEO = RegEnumKey(hKeyIFEO, j, szKeyName, dwSize); if ( lRetIFEO == ERROR_NO_MORE_ITEMS ) { break; } m_IFEOList.InsertItem(j, strTmp); m_IFEOList.SetItemText(j, 1, szKeyName); j ++; ZeroMemory(szKeyName,MAXBYTE); } RegCloseKey(hKeyIFEO); } ~~~ 为了使这两个函数正常使用,需要在对话框初始化时,就将注册表内容显示出来。在OnInitDialog()中添加: ~~~ ShowRunList(); ShowIFEOList(); ~~~ 再在头文件中进行函数声明: ~~~ void ShowIFEOList(); void ShowRunList(); ~~~ 至此,两个“List Control”控件的程序编写完毕,接下来是三个按钮控件的编程,首先是“删除启动项”按钮: ~~~ void CRegManageRunDlg::OnBtnRunDel() { // TODO: Add your control notification handler code here POSITION pos = m_RunList.GetFirstSelectedItemPosition(); int nSelected = -1; while ( pos ) { nSelected = m_RunList.GetNextSelectedItem(pos); } if ( -1 == nSelected ) { AfxMessageBox("您尚未选择选择要删除的启动项!"); return ; } char szKeyName[MAXBYTE] = { 0 }; m_RunList.GetItemText(nSelected, 1, szKeyName, MAXBYTE); CString str = "您即将要删除的启动项是:"; str += szKeyName; AfxMessageBox(str); str.Empty(); HKEY hKey = NULL; LONG lRet = RegOpenKey(HKEY_CURRENT_USER, REG_RUN, &hKey); if ( lRet != ERROR_SUCCESS ) { AfxMessageBox("启动项打开失败!"); return ; } RegDeleteValue(hKey, szKeyName); RegCloseKey(hKey); ShowRunList(); } ~~~ 然后是“删除映像劫持”按钮: ~~~ void CRegManageRunDlg::OnBtnIFEODel() { // TODO: Add your control notification handler code here POSITION pos = m_IFEOList.GetFirstSelectedItemPosition(); int nSelected = -1; while ( pos ) { nSelected = m_IFEOList.GetNextSelectedItem(pos); } if ( -1 == nSelected ) { AfxMessageBox("您尚未选择选择要删除的映像劫持!"); return ; } char szKeyName[MAXBYTE] = { 0 }; m_IFEOList.GetItemText(nSelected, 1, szKeyName, MAXBYTE); CString str = "您即将要删除的映像劫持是:"; str += szKeyName; AfxMessageBox(str); str.Empty(); HKEY hKey = NULL; LONG lRet = RegOpenKey(HKEY_LOCAL_MACHINE, REG_IFEO, &hKey); if ( lRet != ERROR_SUCCESS ) { AfxMessageBox("映像劫持打开失败!"); return ; } RegDeleteKey(hKey, szKeyName); RegCloseKey(hKey); ShowIFEOList(); } ~~~ 最后是“退出”按钮: ~~~ void CRegManageRunDlg::OnBtnQuit() { // TODO: Add your control notification handler code here EndDialog(0); } ~~~ 至此,所有代码编写完毕。由于程序原理非常简单,所以这里无需过多论述。 ## 四、实际测试 测试时需要使用[《反病毒攻防研究第002篇:利用注册表实现自启动》](http://blog.csdn.net/ioio_jy/article/details/38894059)中编写的用于模拟计算机病毒的程序,它会在注册表中的两个位置添加自身,以实现自启动。运行该“病毒”程序后,用这里编写的“启动项管理器”进行查看: ![](https://box.kancloud.cn/2016-02-18_56c5742992acf.jpg) 图3 查看启动项 可见我们的程序已经正确识别出了“恶意程序”所添加的启动项,接下来就可以通过点击相应的按钮进行删除,这里不再赘述。 ## 五、小结 我们可以在此基础上开发一个完整的注册表启动项管理器,使其可以枚举所有的启动项,再添加其它一些实用的功能,令恶意程序无处藏身。MFC为我们提供了丰富的控件,利用它们可以编写出功能强大且界面精美的程序,这也是我的《安全类软件编写》这一系列坚持使用MFC的原因。