一、前言
这次的程序是为了完善上一次所编写的进程管理器。使得当我们选中某一个进程的时候,可以查看其DLL文件,并且能够对可疑的模块进行卸载操作。这样就可以有效对抗DLL的恶意注入。
二、界面制作
这个界面是要依托于上一篇文章中制作的界面,需要单击上次界面中的“查看DLL”按钮来启动。在上次的工作区中,找到VC6中菜单栏的“Insert”选项,在其下拉菜单中选择“Resource…”,在弹出的界面中选择“Dialog”然后单击“New”,如下所示:
图1 添加窗口
接下来需要为新窗口取一个名字,比如IDD_DIALOG_DLL。然后再为其添加一个新类,比如CDLLCheck。这样就可以开始设计窗口了,如下图所示:
图2 界面设计
在这个新添加的窗口中,包含有这里需要一个“List Control”和两个“Button”控件。接下来为列表框添加一个名为m_CheckDLL的变量,然后编写代码对其初始化:
void CDLLCheck::InitDLLList()
{
//设置“List Control”控件的扩展风格
m_CheckDLL.SetExtendedStyle(
m_CheckDLL.GetExtendedStyle()
| LVS_EX_GRIDLINES //有网络格
| LVS_EX_FULLROWSELECT); //选中某行使整行高亮(只适用于report风格)
//添加列目
m_CheckDLL.InsertColumn(0, _T("序号"));
m_CheckDLL.InsertColumn(1, _T("名 称"));
m_CheckDLL.InsertColumn(2, _T("路 径"));
//设置列的宽度
m_CheckDLL.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER);
m_CheckDLL.SetColumnWidth(1, LVSCW_AUTOSIZE_USEHEADER);
m_CheckDLL.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);
}
因为我希望在这个窗口刚被打开的时候,上述初始化代码就能够执行,但是这个新增加的窗口却没有OnInitDialog()这样的初始化函数,所以需要手动添加。在VC6的菜单栏中选择“View”,单击下拉菜单中的“ClassWizard”,在“Message Map”选项卡中进行如下设置: 单击OK后,新窗口的cpp程序中就出现了初始化函数:
BOOL CDLLCheck::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
然后再填入:InitDLLList();
再于新窗口头文件中写入:void InitDLLList();
三、编写“查看DLL”按钮控件代码
这里所说的按钮,指的是上一个窗口中的“查看DLL”按钮控件。我希望在单击这个按钮之后,能够弹出这次新建的IDD_DIALOG_DLL窗口,并且能够直接显示出所选择进程的DLL。可以编写程序打开一个模态对话框:void CProcessManageDlg::OnBtnDLL()
{
// TODO: Add your control notification handler code here
pid = GetSelectPid();
CDLLCheck DLLCheck;
DLLCheck.DoModal();
}
上述代码定义了一个对话框对象:DLLCheck,然后利用这个对象调用DoModal函数以产生一个模态对话框。因为主窗口并不知道这个CDLLCheck对话框是什么样的数据类型,所以还必须在主窗口函数的源文件中包含CDLLCheck类的头文件,即“DLLCheck.h”。
需要强调的是,因为我想要把主窗口中被选中进程的PID值传入新窗口以查看其所包含的DLL文件,所以需要在主窗口头文件中的CDialog下的public中声明一个变量:int pid;
这样,子窗口就能够调用父窗口中获取的PID值了。所以上述程序的第一句就是先获取所选中进程的PID值,再打开子窗口。
四、DLL的枚举
DLL枚举的代码填写在新窗口的源文件中,其原理与上篇文章讨论的进程枚举类似,不同的是这里需要先获取父窗口中的PID值,代码如下:void CDLLCheck::ShowModule()
{
//清空列表
m_CheckDLL.DeleteAllItems();
//获取父窗口中的公共变量(所选中进程的PID值)
CProcessManageDlg *p;
p = (CProcessManageDlg *) GetParent();
int nPid = p->pid;
MODULEENTRY32 Me32 = { 0 };
Me32.dwSize = sizeof(MODULEENTRY32);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, nPid);
if ( hSnap == INVALID_HANDLE_VALUE )
{
AfxMessageBox("创建快照失败!");
return ;
}
BOOL bRet = Module32First(hSnap, &Me32);
int i = 0;
CString str;
while ( bRet )
{
str.Format("%d", i);
m_CheckDLL.InsertItem(i, str);
m_CheckDLL.SetItemText(i, 1, Me32.szModule);
m_CheckDLL.SetItemText(i, 2, Me32.szExePath);
i ++;
bRet = Module32Next(hSnap, &Me32);
}
}
这个程序我也是希望在刚打开窗口的时候就显示出来,因此需要在新对话框的OnInitDialog()中添加:ShowMoudle();
并在头文件添加:void ShowModule();
五、“卸载DLL”按钮的实现
这个功能的实现首先是获取父窗口中所选进程的PID值,然后获取当前列表框中所选择的DLL的名称,之后调用卸载函数即可:void CDLLCheck::OnBtnUnInjectDll()
{
// TODO: Add your control notification handler code here
CProcessManageDlg *p;
p = (CProcessManageDlg *) GetParent();
int nPid = p->pid;
//获取列表框中所选中的位置
POSITION Pos = m_CheckDLL.GetFirstSelectedItemPosition();
int nSelect = -1;
while ( Pos )
{
nSelect = m_CheckDLL.GetNextSelectedItem(Pos);
}
//如果在列表框中没有进行选择,则报错
if ( -1 == nSelect )
{
AfxMessageBox("请选择模块!");
return;
}
//获取列表框中DLL的名称
char szDllName[MAX_PATH] = { 0 };
m_CheckDLL.GetItemText(nSelect, 1, szDllName, MAX_PATH);
UnInjectDll(nPid,szDllName);
ShowModule();
}
需要说明的是,上述程序中最后所使用的UnInjectDll(nPid,szDllName)函数,我曾经在《反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写》中讨论过,这里不再论述。这个函数依旧要在新窗口的源程序以及头文件中的相应位置进行声明才能使用。
六、调整进程权限
一般来说,我们是无法查看系统进程的DLL文件的,主要是因为当前进程的权限级别不够,除非当前进程拥有“SeDebugPrivilege”权限,获取权限的步骤如下:
1、使用OpenProcessToken()函数打开当前进程的访问令牌。
2、使用LookupPrivilegeValue()函数取得描述权限的LUID。
3、使用AdjustTokenPrivileges()函数调整访问令牌的权限。
代码如下:void CDLLCheck::DebugPrivilege()
{
HANDLE hToken = NULL;
BOOL bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
if ( bRet == TRUE )
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
CloseHandle(hToken);
}
}
将代码写入新窗口的源程序,并填入窗口初始化的函数,使窗口生成时就拥有权限,最后在头文件中声明即可。
七、实际测试
为了测试本程序,可以参照《DLL注入(中)——DLL注入与卸载器的编写》那样先注入一个DLL,然后用本软件进行查看并卸载:
经实际测试,程序可行,这又是我们反恶意程序的利器。
八、小结
通过两篇文章的讨论,完成了一个简易的进程管理器。虽然简单,但是很多时候它也能起到很大的功效。而通过这几篇文章的讨论,相信大家对于安全类软件的编写有了一定的认识,希望大家能够不断学习,将更加强大的功能添加到自己的软件中,让恶意程序无处藏身。