Windows XML Event Log (EVTX)单条日志清除系列文章的第四篇,介绍第二种删除当前系统单条日志记录的方法:获得日志服务Eventlog对应进程中指定日志文件的句柄,通过Dll注入获得该句柄的操作权限,利用该句柄实现日志文件的修改。
其他几篇:
本文将要介绍以下内容:
利用思路
程序实现
枚举日志服务Eventlog对应进程的所有句柄,获得指定日志文件的句柄
通过Dll注入获得该句柄的操作权限
进程间消息传递的方法
系统启动日志服务Eventlog后,会以独占模式打开日志文件,导致其他进程无法打开该日志文件,也就无法进行修改操作
那么,如果我们通过Dll注入进入进程的内存,接着获得指定日志文件的句柄,能否获得该日志文件的操作权限呢?
下载地址:
https://processhacker.sourceforge.io/
执行如下powershell代码:
Get-WmiObject -Class win32_service -Filter "name = 'eventlog'" | select -exp ProcessId
根据pid找到进程,查看Properties
->Handles
能够获得当前进程的所有句柄信息
如下图
可以看到C:\Windows\System32\winevt\Logs\Security.evtx
对应的Handle值为0x1c8
查看processhacker的源码,寻找实现方法
代码位置:
https://github.com/processhacker/processhacker/blob/e2d793289dede80f6e3bda26d6478dc58b20b7f8/ProcessHacker/hndlprv.c#L307
获得参考资料:
On Windows 8 and later, NtQueryInformationProcess with ProcessHandleInformation is the most efficient method.
On Windows XP and later, NtQuerySystemInformation with SystemExtendedHandleInformation.
Otherwise, NtQuerySystemInformation with SystemHandleInformation can be used.
于是,挑选第三个方法尝试实现
注:
经测试,第三个方法适用于Win7和更新版本的操作系统
利用NtQuerySystemInformation查询SystemHandleInformation能够获得所有进程的句柄信息
挑选出日志进程中的所有句柄
接着通过NtDuplicateObject获取句柄的名称和具体的数值信息
最后筛选出想要查找的句柄,输出Handle值
完整实现代码已开源,下载地址如下:
https://github.com/3gstudent/Homework-of-C-Language/blob/master/GetPIDandHandle(evtx).cpp
代码实现了根据输入的关键词进行搜索,获得对应的句柄名称和Handle值
测试如下图
成功获得日志服务Eventlog对应进程的pid,security.evtx对应的Handle值为0x1c8
通过NtCreateThreadEx + LdrLoadDll实现Dll注入的代码可参考:
https://github.com/3gstudent/Homework-of-C-Language/blob/master/NtCreateThreadEx%20%2B%20LdrLoadDll.cpp
注:
注入成功后需要FreeDll
注入成功后,使用获得到的Handle值作为函数CreateFileMapping()的第一个参数,创建一个文件映射内核对象
然后调用函数MapViewOfFile()将文件数据映射到进程的地址空间
接下来修改内存中的数据,删除单条日志记录
最后调用函数FlushViewOfFile(),将内存数据写入磁盘
在实际使用过程中,整个实现日志记录删除功能的代码要放在Dll中,而通过CreateRemoteThread无法向Dll传入参数,这就导致无法删除指定EventlogRecordId的日志
这里可以借助进程间的消息传递
实现方法有多种,例如信号、管道、消息队列和共享内存,甚至是读写文件
由于在0x04部分使用了函数CreateFileMapping()创建一个文件映射内核对象,所以进程间消息传递也使用内存映射的方式
创建一个共享内存,代码可参考:
https://github.com/3gstudent/Homework-of-C-Language/blob/master/OpenFileMapping.cpp
代码中创建了两个内存映射对象,并且指定了函数CreateFileMapping()的访问权限为允许任何人访问该对象,即函数CreateFileMapping()的第二个参数
通常情况下,该值设置为NULL,表示默认访问权限,但在注入的Dll中必须设置为允许任何人访问该对象,否则提示拒绝访问
原因如下:
Dll注入svchost.exe后,权限为System,默认访问权限无法访问由用户创建的内存映射文件对象,必须指定为允许任何人访问该对象
当然,如果是两个用户权限的进程进行消息传递,函数CreateFileMapping()的第二个参数为NULL即可
读取指定共享内存,代码可参考:
https://github.com/3gstudent/Homework-of-C-Language/blob/master/OpenFileMapping.cpp
代码中读取这两个内存映射对象,添加了数据类型转换的功能(字符串转int)
删除的关键代码如下:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/DeleteRecordofFile.cpp
代码实现了删除文件c:\test\Setup.evtx
中的一条日志(EventRecordID=14),新文件保存为c:\test\SetupNew.evtx
整个实现流程分成两部分:
启动程序
注入的Dll
代码地址:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/Loader-rewriting.cpp
流程如下:
获得日志服务Eventlog对应进程的pid
枚举日志服务Eventlog对应进程的的所有句柄,获得指定日志文件的句柄
创建两个内存映射,用于向dll传递日志文件的句柄和需要删除日志的EventRecordID
向日志服务Eventlog对应的进程注入Dll
释放Dll
关闭内存映射
代码地址:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/Dll-rewriting.cpp
流程如下:
分别从两个内存映射读取消息,将读取到的内容从字符串转换成int类型,获得日志文件的句柄及需要删除日志的EventRecordID
调用函数CreateFileMapping(),传入日志文件的句柄,创建一个文件映射内核对象
调用函数MapViewOfFile()将文件数据映射到进程的地址空间
修改内存数据,删除指定日志
调用函数FlushViewOfFile(),将内存数据写入磁盘
关闭日志文件的内存映射
测试如下图
可供参考的代码:
https://github.com/360-A-Team/EventCleaner/blob/master/EventCleaner/EventCleaner.cpp#L528
我按照这个思路写的代码:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/DeleteRecordofFileEx.cpp
代码实现了调用Windows API EvtExportLog对日志文件进行筛选,去除指定日志后,将剩下的日志内容保存为新文件temp.evtx
整个实现流程分成三部分:
日志删除程序
启动程序
注入的Dll
代码地址:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/DeleteRecord-EvtExportLog.cpp
流程如下:
指定日志文件和需要删除日志的EventRecordID
生成新的日志文件temp.evtx
代码地址:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/Loader-EvtExportLog.cpp
流程如下:
获得日志服务Eventlog对应进程的pid
枚举日志服务Eventlog对应进程的的所有句柄,获得指定日志文件的句柄
创建三个内存映射,用于向dll传递日志文件的句柄、新日志文件的长度和新日志文件的内容
向日志服务Eventlog对应的进程注入Dll
释放Dll
关闭内存映射
代码地址:
https://github.com/3gstudent/Eventlogedit-evtx--Evolution/blob/master/Dll-EvtExportLog.cpp
流程如下:
从第一个内存映射获得日志文件的句柄,将读取到的内容从字符串转换成int类型
从第二个内存映射获得新日志文件的长度
根据新日志文件的长度调整内存映射的读取长度,从第三个内存映射获得新日志文件的内容
调用函数CreateFileMapping(),传入日志文件的句柄,创建一个文件映射内核对象
调用函数MapViewOfFile()将文件数据映射到进程的地址空间
修改内存数据,覆盖为新日志文件的内容
调用函数FlushViewOfFile(),将内存数据写入磁盘
关闭日志文件的内存映射
测试如下图
注:
对于以上两种方法,删除setup.evtx
是没有问题的,删除system.evtx
和security.evtx
会存在因为竞争条件导致删除失败的情况
本文介绍了第二种删除当前系统单条日志记录的方法:获得日志服务Eventlog对应进程中指定日志文件的句柄,通过Dll注入获得权限,利用该句柄实现日志文件的修改 某些情况下,dll注入会失败,那么是否还有删除当前系统单条日志记录的方法呢?安全脉搏下一篇文章将会介绍。