Introduction
Many security tools are proud to release presumably infected files when they are locked in the file system. This feature is not only used in security, else like a method to unblock a file locked by some buggy process. In that case, you could choose to close the buggy application or if you can't find it, restart the system. Some others would prefer to spend some time reading an article on the internet or looking for an unblocking tool. This tip covers what most of those tools are missing, closing the memory mapped files associated with the open file.
Common File Handle Enumeration
Enumerating file handles is a great opportunity to play in the field of the undocumented with the famous ZwQuerySystemInformation()
and the parameter SystemHandleInformation
. The tricky part about this is to recognize from every handle in the system which is a file handle. The structure returned from the API call is the following:
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
The interesting field for now is the ObjectTypeNumber
. This value will guide you to know if this handle represents a file or not. Sadly, this value is not the same for all the operating system versions and we must expect that could change in future releases. The following table shows the file object type numbers for the current releases:
Operating System | File object Type | Section Object Type |
Windows 2000 | 26 | 19 |
Windows XP | 28 | 19 |
Windows Vista | 28 | 33 |
Windows 7 | 28 | 33 |
Windows 8 | 31 | 36 |
Windows 8.1 | 30 | 35 |
These numbers were discovered using Windbg
and the debug symbols of the target operating system. The procedure is not hard to repeat for any other operating system version and the internet is full of examples to guide you in the process.
The following example is a listing all the file handles for the process with identifier 0x390. Taking any known file for the experiment, it is possible to get the object header structure for that file object.
kd> !handle 0 3 0390 File
Searching for Process with Cid == 390
Searching for handles of type File
PROCESS a4138040 SessionId: 1 Cid: 0390 Peb: 7fb23000 ParentCid: 06e4
DirBase: 3db97620 ObjectTable: 8b3e9480 HandleCount: <Data Not Accessible>
Image: notepad.exe
...
0128: Object: 9b5a5300 GrantedAccess: 00120089 Entry: 8ca25250
Object: 9b5a5300 Type: (8532fc30) File
ObjectHeader: 9b5a52e8 (new version)
HandleCount: 1 PointerCount: 30
Directory Object: 00000000 Name: \Windows\Fonts\StaticCache.dat {HarddiskVolume2}
...
kd> dt _OBJECT_HEADER 9b5a52e8
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n27
+0x004 HandleCount : 0n1
+0x004 NextToFree : 0x00000001 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x1e ''
...
Giving the ObjectHeader
pointer the next step is to obtain the TypeIndex
. Depending on the operating system version could be referred as the direct file object type number or the index in the object type table. In any case, this value is enough to identify if the object is a file or not.
A smart programmer could do this calculation programmatically. Anyway, that will not solve the problem of constantly checking if your driver is not going to crash in the next operating system version.
The solution for file handle enumeration is straightforward now. Call ZwQuerySystemInformation
and then filter from the output array the interesting file handle information using the known file object type number. In case of looking for a particular file name, it is possible to obtain the file name from the file object in the returned information. Of course, it will always will be saved to obtain all the required reference pointers to avoid the object going away while checking in memory information.
Section Handle Enumeration
Now, it is possible to use the same method to look for memory mapped files handles. The previous table already shows the object type number for section objects. But in this case, there is an additional problem, not all the section objects have names, or at least a meaningful name. So, it will be difficult to make a relation between the section handle information that it is enumerated and a certain file name. Then, it is time to go deeper in the internal structures of the file system. Using again the Windbg
and taking a look on the file object structure:
typedef struct _FILE_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
PVPB Vpb;
PVOID FsContext;
PVOID FsContext2;
PSECTION_OBJECT_POINTERS SectionObjectPointer;
PVOID PrivateCacheMap;
NTSTATUS FinalStatus;
struct _FILE_OBJECT *RelatedFileObject;
BOOLEAN LockOperation;
BOOLEAN DeletePending;
BOOLEAN ReadAccess;
BOOLEAN WriteAccess;
BOOLEAN DeleteAccess;
BOOLEAN SharedRead;
BOOLEAN SharedWrite;
BOOLEAN SharedDelete;
ULONG Flags;
UNICODE_STRING FileName;
LARGE_INTEGER CurrentByteOffset;
__volatile ULONG Waiters;
__volatile ULONG Busy;
PVOID LastLock;
KEVENT Lock;
KEVENT Event;
__volatile PIO_COMPLETION_CONTEXT CompletionContext;
KSPIN_LOCK IrpListLock;
LIST_ENTRY IrpList;
__volatile PVOID FileObjectExtension;
} FILE_OBJECT, *PFILE_OBJECT;
There is a field on this structure that has a name which is very interesting for this study, SectionObjectPointers
. This is not the pointer to the section object related with the file object, but close to it. The section object pointer structure holds three pointers:
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject;
PVOID SharedCacheMap;
PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS, *PSECTION_OBJECT_POINTERS;
The DataSectionObject
pointer points to CONTROL_AREA
structure used by the Memory Manager to track the state information for a given section. The ShareCacheMap
is only initialized when the file system already is caching this file object. The ImageSectionPointer
is another CONTROL_AREA
but in this case is used when the file is an executable image. The CONTROL_AREA
structure is not documented, but Windbg
shows the following declaration:
nt!_CONTROL_AREA
+0x000 Segment : Ptr32 _SEGMENT
+0x004 ListHead : _LIST_ENTRY
+0x00c NumberOfSectionReferences : Uint4B
+0x010 NumberOfPfnReferences : Uint4B
+0x014 NumberOfMappedViews : Uint4B
+0x018 NumberOfUserReferences : Uint4B
+0x01c u : <unnamed-tag>
+0x020 FilePointer : _EX_FAST_REF
+0x024 ControlAreaLock : Int4B
+0x028 ModifiedWriteCount : Uint4B
+0x02c WaitList : Ptr32 _MI_CONTROL_AREA_WAIT_BLOCK
+0x030 u2 : <unnamed-tag>
+0x040 LockedPages : Uint8B
+0x048 FileObjectLock : _EX_PUSH_LOCK
Until now, there isn't a clear way to get the section object for this file object. But doing some steps back, and taking a look at the undocumented SECTION_OBJECT
structure:
nt!_SECTION_OBJECT
+0x000 StartingVa : Ptr32 Void
+0x004 EndingVa : Ptr32 Void
+0x008 Parent : Ptr32 Void
+0x00c LeftChild : Ptr32 Void
+0x010 RightChild : Ptr32 Void
+0x014 Segment : Ptr32 _SEGMENT_OBJECT
Now, we can see a way to relate the file object with the section object. The section object structure holds a pointer to a SEGMENT_OBJECT
. After some experiments with Windbg
, it is easy to corroborate that for a memory mapped file that segment pointer is the same to the segment pointer in the data section object for the section object pointer of the file object. This relation is commonly used in forensic analysis, the segment object pointer is the key to find the physical memory backing this file, in other words the content of the file in memory. In this case, we only want to know if there is a section object for this file and from this section object the file name. This relation looks pretty simple in the following diagram:
After all this, it will just start to write some lines and add some improvements to the known file handle enumeration code. Now, we can extend it and get not only the section handles else the correspondent file name. From a given file name, we could get all the file handles, and all the section handles. From those handles, you could do what you want, at your own risk.
The original purpose was to unblock completely a file in the operating system. So, we have all the handles we need, now we call the API DuplicateHandle()
with the option DUPLICATE_CLOSE_SOURCE
for every handle. Close your new handles, cross your fingers, and the file will be free again.
Demo Application and Driver
The provided applications are very simple, and the only intention is to show a proof of concept. The HandleBlockApp.exe will create a little file on the same path with the name "unblockme.exe". Then, it will map the file in memory and will loop forever. That will be the moment to try to do any normal operations with the file and see how the access is denied by Windows. After that, you could use the application HandleCloseApp.exe, with administrator rights, because it will need to install the driver (be sure it is in the same path of the application). You must pass the full path to the file you want to release, in our case, the full path to "unblockme.exe". If everything goes fine, after this, you could try again to delete that file. One very interesting result with this demo is that I was able to release inclusive, loaded dynamic libraries.
Please use this application in a test environment. It is very important to remember that to run the driver in x64, you will need to disable the signature enforcement at boot time. I have tested it in many operating systems and I will be happy to fix all the bugs you found.