一.概述:
如果一个病毒文件被植入正在运行的进程中,我们想要清除它时系统总会提供无法删除;有时编辑文件的进程被意外中止而文件句柄没有正确释放,导致此文件无法进行改写操作。现在我们会使用Unlocker之类的小工具去解锁,但在编写程序的可能会需要把这些功能包含在自己的代码中,本文就是自己写代码实现”如何关闭已经被加载的DLL或是正在使用的文件”功能,使用文章中的方法能很方便的完成文件解锁功能。
按最初的想法准备在ring0中完成这些功能,但在查找资料的过程中发现既然我们能在ring3中做,为什么不让这些方法更通用一些呢。其实功能实现并不难,主要是前期从哪里入手比较麻烦。
我们知道无论是动态库或是文件在加载到进程中时,总会有一个指向它的指针,如果让进程释放这段指针,那么这些文件就不会被系统锁定。下面将是我们的需要实现文件解锁功能而分解出的步骤
1.    枚举当前系统所有进程;
2.    查找进程中打开的文件句柄和加载的动态库句柄;
3.    通知进程关闭这些句柄。

二.详细设计

2.1查找进程加模的动态库模块
Let’s go,我们来分步完成它吧。对于枚举当前系统所有进程,在这里就不给出代码了,相信实现的方法很多。下面的代码段完成查找指定进程加载动库信息的功能(在这里使用了Jeffrey在《Windows核心编程》一书中提供的CToolhelp类,用它可以完成进程加载信息的分析功能,感谢Jeffrey,我一直在使用它)

// 自定义结构,保存打开句柄的的信息
typedef struct _UNFILE_INFO    {int nFileType;DWORD dwHandle;char *strFileName;
} UNFILE_INFO, *PUNFILE_INFO;
//
// 通过PID号取得PID打开的文件句柄信息
//
void
GetModules(DWORD dwProcessID, CList<PUNFILE_INFO, PUNFILE_INFO> &plsUnFileInfo)
{CToolhelp::EnableDebugPrivilege(TRUE);CToolhelp th(TH32CS_SNAPALL, dwProcessID);// 显示进程的详细资料MODULEENTRY32 me = { sizeof(me) };BOOL fOk = th.ModuleFirst(&me);for (; fOk; fOk = th.ModuleNext(&me)){PVOID pvPreferredBaseAddr = NULL;pvPreferredBaseAddr =GetModulePreferredBaseAddr(dwProcessID, me.modBaseAddr);// 取得进程模块信息PUNFILE_INFO pUnFileInfo = new UNFILE_INFO;// 模块地址pUnFileInfo->dwHandle = (DWORD)me.modBaseAddr;// 模块类型pUnFileInfo->nFileType = UNTYPE_DLL;// 模块名称pUnFileInfo->strFileName = new char[strlen(me.szExePath)+1];memset( pUnFileInfo->strFileName, 0, strlen(me.szExePath)+1);strcpy( pUnFileInfo->strFileName, me.szExePath);// 保存打开的模块信息plsUnFileInfo.AddTail( pUnFileInfo);}
}

上面功能完成了枚举进程加载的模块功能,我们把得到的枚举信息加入了链表中,以备后面使用。

2.2枚举进程打开的文件信息
下面将分段说明如何枚举指定进程打开的文件句柄。在这里我们需要使用两个DDK中提供的函数:

NTSTATUS
ZwQuerySystemInformation(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,OUT PVOID SystemInformation,IN ULONG SystemInformationLength,OUT PULONG ReturnLength OPTIONAL
);
NTSTATUS ZwQueryInformationFile(IN HANDLE  FileHandle,OUT PIO_STATUS_BLOCK  IoStatusBlock,OUT PVOID  FileInformation,IN ULONG  Length,IN FILE_INFORMATION_CLASS  FileInformationClass
);

ZwQuerySystemInformation是个未公开函数,通过它的SYSTEM_INFORMATION_CLASS结构,我们能完成许多进程和线程的操作,下面是它的部分内容,在这时我们使用它的SystemHandleInformation(0x10),来完成文件句柄的操作。

typedef enum _SYSTEM_INFORMATION_CLASS { SystemBasicInformation,                 // 0 SystemProcessorInformation,             // 1 SystemPerformanceInformation,             // 2
…SystemHandleInformation,                 // 16
…SystemSessionProcessesInformation        // 53
} SYSTEM_INFORMATION_CLASS;

ZwQueryInformationFile可以根据参数FileInformationClass的不同值来返回不同的类型,在这里我们使用FileInformationClass=FileInformationClass来得到FILE_NAME_INFORMATION,从而得到文件句柄指向的文件名,下面是它的结构定义。
typedef struct _FILE_NAME_INFORMATION {
  ULONG  FileNameLength;
  WCHAR  FileName[1];
} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;

因为需要在Ring3上使用DDK提供的函数,我们需要导出这些两个函数,下面是导出函数的示例代码:

{g_hNtDLL = LoadLibrary( "ntdll.dll" );if ( !g_hNtDLL ){return FALSE;}ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress( g_hNtDLL, "ZwQuerySystemInformation");if( ZwQuerySystemInformation == NULL){return FALSE;}ZwQueryInformationFile =(ZWQUERYINFORMATIONFILE)GetProcAddress( g_hNtDLL, "ZwQueryInformationFile");if( ZwQueryInformationFile == NULL){return FALSE;}
}

我们先来使用ZwQuerySystemInformation函数枚举系统中打开的所有文件的句柄,枚举出的信息将包含如下结构:
typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG  ProcessId;
    UCHAR  ObjectTypeNumber;
    UCHAR  Flags;
    USHORT  Handle;
    PVOID  Object;
    ACCESS_MASK  GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
ObjectTypeNumber定义了句柄所属对像的类型,文件的ObjectType对不同的操作系统是不同的,所以需要先找到当前操作系统所定义的ObjectType值,这样才能在枚举出的众多句柄信息中找到哪些是文件句柄信息。通用的方法是我们CreateFile打开空设备NUL,记下它的句柄,用来比较。

UCHAR GetFileHandleType()
{HANDLE                     hFile;PSYSTEM_HANDLE_INFORMATION Info;ULONG                      r;UCHAR                      Result = 0;hFile = CreateFile("NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);if (hFile != INVALID_HANDLE_VALUE){Info = GetInfoTable(SystemHandleInformation);if (Info){for (r = 0; r < Info->uCount; r++){if (Info->aSH[r].Handle == (USHORT)hFile &&Info->aSH[r].uIdProcess == GetCurrentProcessId()){Result = Info->aSH[r].ObjectType;break;}}HeapFree(hHeap, 0, Info);}CloseHandle(hFile);}return Result;
}

现在知道了句柄的类型就可以枚举系统中打开的文件了。首先用句柄获取打开的文件名:

typedef struct _NM_INFO
{HANDLE  hFile;FILE_NAME_INFORMATION Info;WCHAR Name[MAX_PATH];
} NM_INFO, *PNM_INFO;// 因为在在线程中取得文件名
DWORD WINAPIGetFileNameThread(PVOID lpParameter){PNM_INFO        NmInfo = lpParameter;IO_STATUS_BLOCK IoStatus;int r;NtQueryInformationFile(NmInfo->hFile, &IoStatus, &NmInfo->Info,sizeof(NM_INFO) - sizeof(HANDLE), FileNameInformation);return 0;
}void CFileProcess::GetFileName(HANDLE hFile, PCHAR TheName)
{HANDLE   hThread;PNM_INFO Info = new NM_INFO;char namebuf[2000] = {0};Info->Info = (PFILE_NAME_INFORMATION)namebuf;Info->hFile = hFile;Info->nRet = 0x00;// 在对打开的句柄调用ZwQueryInformationFile时,调用线程会等待PIPE中的消息// 如果PIPE中没有消息时,线程可能会永久挂起,所以使用一个等待超时来打开句柄hThread = CreateThread(NULL, 0, GetFileNameThread, Info, 0, NULL);if (WaitForSingleObject(hThread, 100) == WAIT_TIMEOUT)TerminateThread(hThread, 0);CloseHandle(hThread);// 调用句柄失败if( Info->nRet == 0x00){delete Info;return ;}// 加上盘符wchar_t volume = GetVolumeName(hFile);if (volume != L'!'){wchar_t outstr[1000] = L"A:";outstr[0] = volume;memcpy(&outstr[2], Info->Info->FileName, Info->Info->FileNameLength);outstr[2+Info->Info->FileNameLength] = 0;WideCharToMultiByte(CP_ACP, 0, outstr, 2+Info->Info->FileNameLength, TheName, MAX_PATH, NULL, NULL);}delete Info;return ;
}

下面代码片段用来枚举打开的文件:

{// 取得操作系统文件句柄定义类型UCHAR ObFileType = GetFileHandleType();// 取得系统中打开的文件句柄列表PULONG buf = GetHandleList();if (buf == NULL)return ;Info = (PSYSTEM_HANDLE_INFORMATION)&buf[1];if (Info){for (r = 0; r < buf[0]; r++, Info++){if (Info->ObjectTypeNumber == ObFileType && Info->ProcessId == dwProcessID){hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, Info->ProcessId);if (hProcess){// 复制句柄到当前进程中,这样方便对当前进程句柄取出文件名// 因为共享一个FileObject,这两个文件句柄对像将由两个进程共享if ( DuplicateHandle( hProcess, (HANDLE)Info->Handle,GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS)){GetFileName(hFile, Name);// 取得文件信息到链表中if( strlen( Name)>0){AfxTrace("PID=%d FileHandle %d FileName=%s", Info->ProcessId, Info->Handle, Name);// 在这里加入你要保存的文件句柄信息}CloseHandle(hFile);}CloseHandle(hProcess);}}}}
}

2.3使用远线程方法关闭进程加载的模块和打开的文件
通过枚举进程加载的模块信息和打开的文件信息,我们已经得到系统中加载的DLL和文件名信息,以下是我们在链表中保存的信息:
{
DWORD    ProcessID    // 进程PID
BYTE    nHandleType    // 句柄类型是文件还是加载的DLL
Char strFileName[MAX_PATH]    // 文件名
}
如果我们需要关闭正在打开的文件,只需要在这个链表中以文件名进行查找,即得到打开文件的进程PID。怎么才能通知别的进程关闭相应的句柄呢,我们通过跨进程去关闭句柄是很困难的,不如直接注入代码到需要的进程中,然后直接关闭句柄就行了。
需要再遍写一个DLL用来注入到进程中通知进程关闭文件句柄。这个DLL不需要做别的处理,只需要在加载的地方根据不同的句柄类型使用不同的方法进行关闭即可。下面是注入到进程的DLL执行的关闭句柄函数:

Void CloseHandel( PVOID pHandle, BYTE nHandleType)
{If(HandleType = HANDLE_FILE)
FreeLibrary((HMODULE)pHandle);elseCloseHandle((HANDLE)pHandle);
}

在DllMain中调用关闭句柄的函数:

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{// Remove this if you use lpReservedUNREFERENCED_PARAMETER(lpReserved);if (dwReason == DLL_PROCESS_ATTACH){DWORD nHandle=0;BYTE nType=0;TRACE0("MOPER.DLL Initializing!\n");AfxMessageBox("MOPER.DLL Initializing!");// 从碎甲程序所生成的文件句柄映射文件中取出要关闭的句柄信息// 方法很多我在这里直接用共享文件实现的,就不写出代码了GetHandle( nHandle, nType)// 关闭句柄            CloseHandel((PVOID)nHandle, nType);
...}
...
}

下面示例取出通过文件句柄链表中的PID,使用远程注入的方法向进程注入关闭句柄的DLL

{HANDLE hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许远程创建线程 PROCESS_VM_OPERATION | //允许远程VM操作PROCESS_VM_WRITE,//允许远程VM写FALSE, dwProcessId);if( hRemoteProcess == NULL)return FALSE;PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");int nLen = (strlen( pszLibFile)+1)*sizeof(char);char * pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, nLen, MEM_COMMIT, PAGE_READWRITE);if( pszLibFileRemote != NULL &&pfnStartAddr !=NULL){if( WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFile, nLen, NULL)){HANDLE hThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);if( !hThread){WaitForSingleObject(hThread, INFINITE);bRet = TRUE;CloseHandle(hThread);}}VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);}CloseHandle( hRemoteProcess);
}

三.备注

对查找文件句柄的方法,参考了鄙人拙译( http://greatdong.blog.edu.cn )的“如何操作被占用文件”在此表示感谢。

使用了Jeffrey在《Windows核心编程》一书中提供的CToolhelp类,用它可以完成进程加载信息的分析功能,感谢Jeffrey。

在Ring3上实现文件碎甲(解锁)功能相关推荐

  1. QT上实现文件拖拽功能

    转自于QT上实现文件拖拽功能 声明dragEnterEvent和dropEvent事件函数,对窗口的拖拽行为进行处理 // 由MainWindow来截取拖拽事件 ①ui->lineEdit-&g ...

  2. 怎么实现抢票软件_怎么样在windows上实现文件预览功能?一个软件搞定,提高效率...

    相信用过mac系统的朋友应该都知道,在mac系统中文本.图片或者视频等文件后,直接按空格就能进行文件内容的预览.在找文本.图片.视频或者音频的时候,这个功能是非常的方便实用的.不需要打开文件就能看到文 ...

  3. linux pureftpd 无法上传文件,实现了在pure-ftpd下限制上传文件类型的功能

    -u --uid 你还需要建立一个这样的文件:touch /var/run/pure-ftpd.upload.pipe. 3.需要注意的是,当起用了CallUploadScript yes 后,ftp ...

  4. react实现上传文件进度条功能_React.js 可拖放文件的上传表单(支持多文件和进度显示)...

    JavaScript 语言: JaveScriptBabelCoffeeScript 确定 console.clear(); const { createClass, PropTypes } = Re ...

  5. http 协议上传文件multipart form-data boundary 说明--转载

    原文地址:http://xixinfei.iteye.com/blog/2002017 含义 ENCTYPE="multipart/form-data" 说明:  通过 http ...

  6. [ASP.NET] 限制上传文件类型的两种方法(转)

    通常,为了防止因用户上传有害文件(如木马.黑客程序)引发的安全性问题,Web程序都会对用户允许上传的文件类型加以限制.而本文将要介绍的就是如何在ASP.NET应用程序中利用Web Control的内置 ...

  7. http使用post上传文件时,请求头和主体信息总结

    请求头必须配置如下行: Content-Type' : 'multipart/form-data; boundary=---12321  boundary=---12321位文件的分界线 body如下 ...

  8. formdata上传文件_关于multipart/formdata上传文件

    最近在做一个文件上传的开放接口,用到Content-Type: multipart/form-data这种请求类型,特地做了一些研究和记录. 在最初的 http协议中,并没有上传文件方面的功能.RFC ...

  9. 前端上传文件,multipart-formdata,boundary的使用

    上传文件需要使用 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFIc14z39Lb6Leu6y ; multip ...

最新文章

  1. 3 命名空间与命名规范
  2. [Eclipse] - 解决导入flask模块出现的Unresolved Import flask问题
  3. java中override快捷键_【基础回溯1】面试又被 Java 基础难住了?推荐你看看这篇文章。...
  4. LiveVideoStackCon讲师热身分享 ( 七 ) —— 视频编码器的对比与选择
  5. 容器编排技术 -- Kubernetes kubectl 概述
  6. java读写锁死锁例子_Java并发关于重入锁与读写锁的详解
  7. linux tee命令_Linux tee命令示例
  8. 古老的txt下传和txt上载
  9. fir高通滤波器matlab程序,FIR数字滤波器的Matlab实现[高等教育]
  10. 中国历史上十大无名英雄
  11. Vue和ElementUI第二天
  12. 论文查找: arXiv,论文阅读:知云文献翻译, 完美组合 !
  13. 记一次Android第三方日历控件CalendarView的使用
  14. 微信上线支付分对标芝麻信用分,教你如何开通!
  15. fadeIn fadeOut
  16. MacAppStore的魅力4点
  17. PS CS6打开一直卡在正在检查内存
  18. MAC安装homebrew及使用
  19. AVFoundation 视频流处理
  20. Android——Xlistview上拉刷新下拉加载

热门文章

  1. shape(15,)与(15,1)的区别
  2. Java删除文件及其子文件、文件夹
  3. TP获取服务器mysql版本
  4. 每一种SDS都能做超融合吗?
  5. The Ice::Current Object
  6. MVC HtmlHelper用法大全
  7. NSMutableArray 记住取不到时要进行强转
  8. 3月上旬中国域名解析服务商TOP10 DNSPOD升至8.24%
  9. aspx-cs-dll :在部署后就让所有的aspx处于已经编译成dll的状态
  10. Java Comparator排序