• 安全类工具制作第004篇:进程管理器(上)


    一、前言

            进程是计算机中运行的程序,是向操作系统申请资源的基本单位。我们运行一个程序,那么就会相应地创建一个甚至多个进程,关闭程序时,进程也就结束了。查看进程最常用的手段是按下Ctrl+Shift+Delete打开Windows自带的任务管理器,或者使用老牌强力软件“冰刃”,又或者是使用由微软推出的更为强大的Process Monitor,都能基本得到相同的效果。不同的是,强大的进程查看软件能够查看到系统的隐藏进程,而一般的只能查看应用层的进程。而我在这两篇文章中所讨论的就是如何实现一个简易的进程管理器,通过它可以管理当前的进程,也可以管理进程所加载的DLL。这篇文章主要讨论的就是进程管理方面的编程,下一篇再讨论DLL管理方面的程序编写。

     

    二、界面设计

            本程序需要设计两个界面,这篇文章只讨论第一个界面的制作。这里需要一个“List Control”和三个“Button”控件:


    图1 主界面的设计

            然后设置“List Control”的控件属性,在“Sytles”中的“View”中,选择“Report”,再选中“Single Selection”选项。然后为其添加一个名为“m_ProcessList”的变量,然后通过编程进行初始化:

    void CProcessManageDlg::InitProcessList()  
    {  
            //设置“List Control”控件的扩展风格  
            m_ProcessList.SetExtendedStyle(  
                    m_ProcessList.GetExtendedStyle()  
                    | LVS_EX_GRIDLINES        //有网络格  
                    | LVS_EX_FULLROWSELECT);  //选中某行使整行高亮(只适用于report风格)
      
            //添加列目  
            m_ProcessList.InsertColumn(0, _T("序号"));  
            m_ProcessList.InsertColumn(1, _T("进程名称          "));  
            m_ProcessList.InsertColumn(2, _T("PID值")); 
            m_ProcessList.InsertColumn(3, _T("线程数"));
            m_ProcessList.InsertColumn(4, _T("父进程ID"));
            m_ProcessList.InsertColumn(5, _T("线程优先级"));
            //设置列的宽度  
            m_ProcessList.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);  
            m_ProcessList.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);  
            m_ProcessList.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);  
            m_ProcessList.SetColumnWidth(3, LVSCW_AUTOSIZE_USEHEADER);
            m_ProcessList.SetColumnWidth(4, LVSCW_AUTOSIZE_USEHEADER);
            m_ProcessList.SetColumnWidth(5, LVSCW_AUTOSIZE_USEHEADER);
    }  
            之后在CProcessManageDlg::OnInitDialog()中添加:
    InitProcessList();
            以实现初始化,再在头文件中声明:

    void InitProcessList();

    三、进程的枚举

            进程的枚举就是把所有的进程显示出来,而有一些特意隐藏的进程是无法通过常规的枚举方式枚举到的。这里所讲解的是应用层的进程枚举。为实现此功能,这里使用的是CreateToolhelp32Snapshot()。它的作用是对当前系统中的进程进行一个快照,在创建成功后对进程逐个枚举。枚举进程需要用到Process32First()以及Process32Next()这两个函数。使用这几个函数需要先包含Tlhelp32.h头文件。代码如下:

    void CProcessManageDlg::ShowProcess()
    {
            //清空列表
            m_ProcessList.DeleteAllItems();
            //给系统内所有的进程拍个快照
            HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
            if ( hSnap == INVALID_HANDLE_VALUE )
            {
                    AfxMessageBox("进程快照创建失败!");
                    return ;
            }
    
            PROCESSENTRY32 Pe32 = { 0 };
            //在使用这个结构前,先设置它的大小
            Pe32.dwSize = sizeof(PROCESSENTRY32);
            //遍历进程快照,轮流显示每个进程的信息
            BOOL bRet = Process32First(hSnap, &Pe32);
            int i = 0;
            CString str;
    
            while ( bRet )
            {
                    str.Format("%d", i);
                    m_ProcessList.InsertItem(i, str);             
                    //进程名
                    m_ProcessList.SetItemText(i, 1, Pe32.szExeFile);
                    //进程ID
                    str.Format("%d", Pe32.th32ProcessID);
                    m_ProcessList.SetItemText(i, 2, str);
                    //此进程开启的线程计数
                    str.Format("%d", Pe32.cntThreads);
                    m_ProcessList.SetItemText(i, 3, str);
                    //父进程ID
                    str.Format("%d", Pe32.th32ParentProcessID);
                    m_ProcessList.SetItemText(i, 4, str);
                    //线程优先权
                    str.Format("%d", Pe32.pcPriClassBase);
                    m_ProcessList.SetItemText(i, 5, str);
    
                    i ++;
                    bRet = Process32Next(hSnap, &Pe32);
            }
            CloseHandle(hSnap);
    }
            因为我希望刚打开程序,就能够把系统的进程显示出来,因此要在OnInitDialog()中添加:
    ShowProcess();
            最后在头文件中加上:
    void ShowProcess();

    四、结束进程

            通常情况下,进程正常结束时,会调用ExitProcess()函数来使自身退出。而如果想要结束指定的进程,则需要使用TerminateProcess()函数。但是对于进程的操作,往往都需要使用其PID值,为了方便起见,这里编写一个获取进程PID值的程序,以方便接下来对于进程的一系列操作。它的原理就是在进程被枚举出来,显示在列表框中以后,返回所选取进程的“PID值”的内容:

    int CProcessManageDlg::GetSelectPid()
    {
            pid = -1;
            //获取列表框中所选中的位置
            POSITION Pos = m_ProcessList.GetFirstSelectedItemPosition();
            int nSelect = -1;
            while ( Pos )
            {
                    nSelect = m_ProcessList.GetNextSelectedItem(Pos);
            }
            //如果在列表框中没有进行选择,则报错    
            if ( -1 == nSelect )
            {
                    AfxMessageBox("请选择进程!");
                    return -1;
            }
            //获取列表框中显示的PID值    
            char  szPid[10] = { 0 };
            m_ProcessList.GetItemText(nSelect, 2, szPid, 10);
            pid = atoi(szPid);
    
            return pid;
    }
            这个函数需要在头文件中声明:
    int GetSelectPid();
            然后为“结束进程”按钮添加代码:
    void CProcessManageDlg::OnButtonTerminate() 
    {
            // TODO: Add your control notification handler code here
            int nPid = GetSelectPid();
        
            HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, nPid);
        
            TerminateProcess(hProcess, 0);
    
            CloseHandle(hProcess);
        
            ShowProcess();
    }

            上述代码的原理是先获取进程的权限,然后再进行结束。

     

    五、暂停与恢复进程

            有些时候,恶意程序为了保护自身,可能会创建两个或者多个进程,令其“荣辱与共”。当其中一个进程发现另一个进程被结束了,那么它就会把那个被结束的进程重新运行起来。这几个进程相互帮助,所以就很难把恶意程序的进程彻底结束掉,也就不能删除恶意程序本身。遇到这种情况,可以将这几个进程暂停,然后就可以结束掉恶意进程了。

            暂停进程通常使用的是SuspendThread()函数,它需要使用线程的句柄,线程的句柄可以通过OpenThread()函数获得,然后利用Thread32First()以及Thread32Next()这两个函数进行枚举。为“暂停进程”按钮添加代码:
    void CProcessManageDlg::OnBtnStop() 
    {
            // TODO: Add your control notification handler code here
            int nPid = -1;
            nPid = GetSelectPid();
            //创建线程快照
            HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
            if ( hSnap == INVALID_HANDLE_VALUE )
            {
                    AfxMessageBox("暂停进程失败!");
                    return ;
            }
    
            THREADENTRY32 Te32 = { 0 };
            Te32.dwSize = sizeof(THREADENTRY32);
            BOOL bRet = Thread32First(hSnap, &Te32);
        
            while ( bRet )
            {
                    //判断线程所属
                    if ( Te32.th32OwnerProcessID == nPid )
                    {
                            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, Te32.th32ThreadID);
                
                            SuspendThread(hThread);
                
                            CloseHandle(hThread);
                    }
    
                    bRet = Thread32Next(hSnap, &Te32);
            }
    }

            由于CreateToolhelp32Snapshot()只能创建系统的线程快照,不能创建指定进程中的线程的快照。所以如果想要暂停线程,必须对枚举到的线程进行判断,看其是否为指定进程中的线程。在THREADENTRY32这个结构体中的th32ThreadID标识了当前枚举到的线程的线程ID,而th32OwnerProcessID标识了该线程归属的进程的ID。所以在上述代码中需要进行判断,以找到相应的线程。

            接下来为“恢复进程”按钮添加代码:
    void CProcessManageDlg::OnButtonResume() 
    {
            // TODO: Add your control notification handler code here
            int nPid = -1;
            nPid = GetSelectPid();
        
            HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nPid);
            if ( hSnap == INVALID_HANDLE_VALUE )
            {
                    AfxMessageBox("进程恢复失败!");
                    return ;
            }
        
            THREADENTRY32 Te32 = { 0 };
            Te32.dwSize = sizeof(THREADENTRY32);
            BOOL bRet = Thread32First(hSnap, &Te32);
        
            while ( bRet )
            {
                    if ( Te32.th32OwnerProcessID == nPid )
                    {
                            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, Te32.th32ThreadID);
                
                            ResumeThread(hThread);
                
                            CloseHandle(hThread);
                    }
            
                    bRet = Thread32Next(hSnap, &Te32);
            }	
    }

            因为它与暂停进程原理相同,不再赘述。

     

    六、程序效果

            上述程序编译成功后,就能够对进程实现结束、暂停与恢复的效果。

    图2 查看记事本进程

            比如对一个“记事本”程序做实验。打开记事本,运行本软件,找到记事本的进程,单击“暂停进程”按钮,可见虽然记事本程序仍可见,但是已无法对其操作。直至单击“恢复进程”后,记事本才又恢复原样。然后单击“结束进程”,则记事本就被关闭,它已经从列表框中消失了。说明我们的程序是有效的。

     

    七、小结

            这次实现了一个简单的进程管理器程序,这类程序往往在手动查杀病毒方面有很大的用处。也希望读者能够举一反三,在这个基础上开发出功能更加全面的程序出来。
  • 相关阅读:
    SVN使用教程总结
    学习duilib库过程中的笔记
    duilib库使用(一)-- 编译生成依赖库
    在Windows服务进程中启动需管理员权限(带盾牌图标)的应用程序
    如何在Windows服务程序中读写HKEY_CURRENT_USER注册表
    vs2015 编译boost库
    NSIS 打包工具使用
    C 读文件
    常用的字符转化的方法
    C# 中对于json的解析小结
  • 原文地址:https://www.cnblogs.com/csnd/p/11785801.html
Copyright © 2020-2023  润新知