C++ 黑客编程揭秘与防范(第3版)
上QQ阅读APP看书,第一时间看更新

3.2.3 注册表下启动项的管理

对于Windows操作系统来说,注册表中保存了非常多的系统配置,例如常见的IE主页保存在HKEY_LOCAL_MACHINE\Software\Mircosoft\Internet Explorer\Main下的Start Page中;再比如禁止磁盘驱动器自动运行的AutoRun功能在注册表的HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer下的NoDriveTypeAut- oRun中进行设置;还有映像劫持、文件关联等很多系统配置,都可以在注册表中直接进行配置。

有很多常见的安全工具都需要对注册表进行操作,这里介绍通过注册表获得随Windows系统启动时的启动项。在注册表的启动项中,除了正常的系统工具、软件工具外,病毒和木马也会利用注册表的启动项悄然地让自己跟随Windows的启动而启动,从而实现自启动的功能。下面通过编写一个枚举注册表启动项的工具,进一步学习注册表操作时使用API函数的相关流程,从而将前面的知识得到实际的应用。

1.程序的界面及相关代码

注册表中可以用来完成开机启动的地方非常多,这里不会一一介绍。对注册表具体键值的介绍并非本书的重点,这里只介绍注册表中众多可以完成开机启动的其中一个位置,至于其他地方,读者可以自行搜集并完成。这里的程序依然使用对话框的形式,其界面如图3-6所示。

图3-6 注册表启动项管理界面

这个界面就是笔者已经编写好的软件的界面。这个界面中用到了CListCtrl控件,用户对其进行添加并进行相应的设置即可。在实例的介绍中,笔者会尽可能少地提及控件属性的设置。因为这并非本书应该提到的内容,读者可以自行参考MFC开发相关的书籍。这里给出一个关于CListCtrl初始化的代码,具体如下:

        VOID CManageRunDlg::InitRunList()
        {
            // 设置扩展样式
            m_RunList.SetExtendedStyle(
                    m_RunList.GetExtendedStyle()
                    | LVS_EX_GRIDLINES            // 有网格
                    | LVS_EX_FULLROWSELECT);     // 选择单行

            // 在ListCtrl中插入新列
            m_RunList.InsertColumn(0, "NO.");
            m_RunList.InsertColumn(1, "键值名称");
            m_RunList.InsertColumn(2, "键     值");

            /*
            LVSCW_AUTOSIZE_USEHEADER:
            列的宽度自动匹配为标题文本
            如果这个值用在最后一列,列宽被设置为ListCtrl剩余的长度
            */
              m_RunList.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);
              m_RunList.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
              m_RunList.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);
          }

2.启动项的枚举

这个实例主要是通过枚举注册表中的“ HKEY_LOCAL_MACHINE\Software\Microso ft\Windows\CurrentVersion\Run”子键下的键值项,取得跟随Windows启动而启动的程序。在运行软件“注册表启动项管理”后,应该将上述注册表子键位置下的所有启动项的内容显示出来,其代码如下:

        #define REG_RUN "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\"

        VOID CManageRunDlg::ShowRunList()
        {
            // 清空ListCtrl中的所有项
            m_RunList.DeleteAllItems();

            DWORD dwType = 0;
            DWORD dwBufferSize = MAXBYTE;
            DWORD dwKeySize = MAXBYTE;
            char szValueName[MAXBYTE] = { 0 };

            HKEY hKey = NULL;
            LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                    REG_RUN, 0, KEY_ALL_ACCESS, &hKey);

            if ( lRet != ERROR_SUCCESS )
            {
                return ;
            }

            int i = 0;
            CString strTmp;

            while ( TRUE )
            {
                // 枚举键项
                lRet = RegEnumValue(hKey, i, szValueName,
                    &dwBufferSize, NULL, &dwType,
                    (unsigned char *)szValueKey, &dwKeySize);

                // 没有则退出循环
                if ( lRet == ERROR_NO_MORE_ITEMS )
                {
                    break;
                }

                // 显示到列表控件中
                strTmp.Format("%d", i);
                m_RunList.InsertItem(i, strTmp);
                m_RunList.SetItemText(i, 1, szValueName);
                m_RunList.SetItemText(i, 2, szValueKey);

                ZeroMemory(szValueKey, MAXBYTE);
                ZeroMemory(szValueName, MAXBYTE);

                dwBufferSize = MAXBYTE;
                dwKeySize = MAXBYTE;

                i ++;
            }

            RegCloseKey(hKey);
        }

当将注册表中的自启动项显示出来后,必然会对其进行一定的操作或处理。对于注册表启动项的管理来说,常见的有3个功能,首先是屏蔽启动项,然后是删除启动项,最后是添加启动项(这三者是并列关系,不是先后顺序)。这里的程序中只完成后两个功能,即删除启动项和添加启动项。删除启动项和屏蔽启动项是有差别的,其差别在于屏蔽启动项是可恢复的,而删除启动项是不可恢复的,至于屏蔽启动项这个功能就留给读者实现了。很多系统优化工具和系统安全工具中都有此功能,读者请参考优化工具的实现原理自行编写代码完成。

3.添加启动项的代码

只要将需要跟随Windows启动的软件添加至“HKEY_LOCAL_MACHINE\Software\Micro soft\Windows\CurrentVersion\Run”子键下,就可以实现所需的功能,代码如下:

        void CManageRunDlg::OnBtnAdd()
        {
            // TODO: Add your control notification handler code here
            CRegAdd RegAdd;
            RegAdd.DoModal();

            // 判断输入是否完整
            if ( strlen(RegAdd.m_szKeyName) > 0 &&
                strlen(RegAdd.m_szKeyValue) > 0)
            {
                HKEY hKey = NULL;
                LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                    REG_RUN, 0, KEY_ALL_ACCESS, &hKey);

                if ( lRet != ERROR_SUCCESS )
                {
                    return ;
                }

                RegSetValueEx(hKey, RegAdd.m_szKeyName, 0,
                    REG_SZ, (const unsigned char*)RegAdd.m_szKeyValue,
                    strlen(RegAdd.m_szKeyValue) + sizeof(char));

                RegCloseKey(hKey);

                ShowRunList();
            }
            else
            {
                AfxMessageBox("请输入完整的内容");
            }
        }

在代码中,CRegAdd对应着添加启动项的窗口,该窗口的代码如下:

        void CRegAdd::OnBtnOk()
        {
            // TODO: Add your control notification handler code here
            ZeroMemory(m_szKeyName, MAXBYTE);
            ZeroMemory(m_szKeyValue, MAX_PATH);

            GetDlgItemText(IDC_EDIT_KEYNAME, m_szKeyName, MAXBYTE);
            GetDlgItemText(IDC_EDIT_KEYVALUE, m_szKeyValue, MAX_PATH);

            EndDialog(0);
        }

4.删除启动项的代码

删除启动项的实现代码比添加启动项的代码要简单,但是在删除的时候涉及一个关于CListCtrl控件的编程,也就是选中列表框中的哪个启动项要进行删除。这是一个对控件进行编程的问题,在代码中获取选中的启动项后,要进行删除就非常简单了,代码如下:

        void CManageRunDlg::OnBtnDel()
        {
            // 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);

            HKEY hKey = NULL;

            LONG lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                    REG_RUN, 0, KEY_ALL_ACCESS, &hKey);

            RegDeleteValue(hKey, szKeyName);

            RegCloseKey(hKey);

            ShowRunList();
        }

对于注册表启动项的管理软件就编写到这里,读者可以将其他的可以让软件开机启动的注册表子键添加到软件中去,这样启动项管理软件就更加强大、更加完美了。但是,当不断深入对注册表的了解时,会发现更多的可以让软件随机启动的子键,这样就需要每次将新发现的子键添加到代码中,而每次改动代码是非常繁琐的。那么,有没有什么好的方法可以在每次添加子键的同时不改变代码本身呢?结合前面介绍的文件操作知识,可以把要枚举的注册表子键保存到一个文件中,然后让程序去该文件中读取这些子键,最后通过API函数对注册表进行枚举。这样,以后每当在注册表中有新的需要枚举的内容时,只需要修改保存注册表子键的文件即可,而不需要对程序本身进行修改了。