Sysinternals Homepage
Forum Home Forum Home > Windows Discussions > Development
  New Posts New Posts RSS Feed - Overview - Handle Enumeration
  FAQ FAQ  Forum Search   Events   Register Register  Login Login

Overview - Handle Enumeration

 Post Reply Post Reply Page  <12
Author
Message Reverse Sort Order
Matts_User_Name View Drop Down
Senior Member
Senior Member
Avatar

Joined: 10 August 2006
Location: USA
Status: Offline
Points: 687
Post Options Post Options   Thanks (0) Thanks(0)   Quote Matts_User_Name Quote  Post ReplyReply Direct Link To This Post Posted: 01 May 2008 at 6:54am

So... You want to know how to get these:





Seeing that this seems to be a hot topic in the Development area of this forum I decided to elaborate on how to enumerate handles of any process:

I did this in VB6, and if it can be done there, it can be done with any language that has access the Windows API.

(Note: The following structues and APIs can be found in winternl.h
http://source.winehq.org/source/include/winternl.h )


1. Enumerate
Enumerate all handles with NtQuerySystemInformation:
Use 16 for SYSTEM_INFORMATION_CLASS --> which says to it "I want to query handles"

Note: Remember to allocate enough room in your buffer to hold the data. In VB6 I have seen some use a Byte Array, or you can just use VirtualAlloc. (Remember to use VirtualFree when you have your data)



2. Query
Copy the buffer into SYSTEM_HANDLE (AKA - SYSTEM_HANDLE_ENTRY).
This is found 4 bytes after the buffer's pointer (the first 4 bytes are the number of handles found)

Remember: NTQSI with 16 asks it to give up SYSTEM_HANDLE_INFORMATION, which as you can see in winternl.h, the data follows the 4 bytes.


3. Manipulate
Walk the list, and manipulate/search it to your liking. It might be useful to store these in an array, depending on your purpose.
The way I did it was:
  1. Get number of total handles and store into variable (first 4 bytes in buffer.
  2. Get each SYSTEM_HANDLE. In the buffer, the next one starts where the previous one ends.
          SYSTEM_HANDLE's size is 10. So the buffer's data is outlined as:

          0-4 = Entries
          4-14 = Handle 1
          14-24 = Handle 2
          24-(size of SH) = Handle x

         
The best way to store these is to create a loop and increase an offset variable (which starts at 4) and once the handle is stored, the offset is increased by Length of SH.
Then once you have that offset, read it from memory. Ex: CopyMemory (RtlMoveMemory)

Note: All these handles in the buffer are not just your process's handles, it is every handle in the system.
To get the handles for each process, look for the handle's PID from SYSTEM_HANDLE



4. Need more data?
In order to gain the Handle's Type and Name the handle must exist in your process. To do this, you must make a copy of the handle using NtDuplicateHandle (Or just DuplicateHandle) and create a copy of it inside your process.

Once the copy is in your process, use NtQueryObject with the OBJECT_INFORMATION_CLASS of 1 telling it to hand over its Name (In UNICODE_STRING format)

Note: NtQueryObject needs the handle's value. So basically this can convert a handle value into the Handle Name (Or Type).
If you are wondering why earlier I said the handle must exist in your process, this is why: Because NtQueryObject was only made to query a local handle's data. Just like how CloseHandle or NtClose is only for local handles.

So to get the handle name for every process:
  • Create local copy of a handle - NtDuplicateHandle
  • Get the name - NtQueryObject (with 1)
  • Query the string from ONI.Name which is from the UNICODE_STRING buffer

The same idea for handle Type is used. The only difference is that OBJECT_TYPE_INFORMATION (2) is used instead of OBJECT_NAME_INFORMATION (1).


Note: Process and Thread handles do not have a name. They can be obtained from:
NtQueryInformationProcess ---> Process_Basic_Information.PID and
NtQueryInformationThread ---> Thread_Basic_Information.CliendID.TID

File path names can be converted from Device Path to Logical Drive path using GetLogicalDriveStrings, and converting them with QueryDosDevice.
Once you know which drive goes with which device, you can convert the \Device\... to X:\


IMPORTANT! If you NtQueryObject with ONI (1) on a special NamedPipe handle, your thread will hang. Infact in my testing, I wasn't even able to kill the process nor shut down my computer.


To avoid these I found a undocumented similarity. Skip the NtQueryObject - ONI call if the Handle's access value is "12019F" (0x0012019F) ---> Dec = 1180063.

EP_X0FF and I talk about this here:
http://forum.sysinternals.com/forum_posts.asp?TID=14435&PN=1

He also suggested spawning another thread for the query, but I don't have that much experience with thread creation, so I luckily found this common factor.

Note: This bug is only for ONI (Name) = 1 and not OTI (Type) = 2.
This avoidance method appears to be foolproof and I have yet to experience a hung thread when avoiding these troublesome File-type NamedPipe handles.




5. Need a CloseHandleEx? (Remote Handle Closing)

As you probably know, NtClose and CloseHandle do not work on remote process handles.
Luckily NtDuplicateObject will do this for us with just one specific flag.
What is this special little flag you ask? --> DUPLICATE_CLOSE_SOURCE  (= 1)

Even though NTDO is used to create a copy of the handle into some other process, using that flag will close the source of the handle. You dont even need to bother closing the local handle that is created, because if you just pass it a null for the destination process handle, it cant go anywhere.
In other words, if no target process is specified and the DCS flag is used, the handle is closed remotely without even making another copy.

Note: The documented API that calls NtDuplicateObject is called DuplicateHandle, which will basically explain what NtDuplicateObject does too.
http://msdn.microsoft.com/en-us/library/ms724251(VS.85).aspx

Example code:

Private Sub CloseHandleEx(ByVal lPID As Long, ByVal lHandle As Long)
    Dim hProcess As Long
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, lPID)
    Call NtDuplicateObject(hProcess, lHandle, 0, ByVal 0, 0, 0, DUPLICATE_CLOSE_SOURCE)
    Call NtClose(hProcess)
End Sub

I am sure there are other methods to perform this remote handle close, but this technique seems the easiest to me.
Another other way could include using CRT (CreateRemoteThread) and telling it to execute NtClose or CloseHandle, which would be similar to remote dll unloading with CRT telling the remote process to execute FreeLibrary.



Feel free to add any comments, questions, improvements, examples, or clarifications if you want.

Mods: You might want to pin this if you feel it could help others.




Edited by Matts_User_Name - 01 May 2008 at 10:07am
Back to Top
 Post Reply Post Reply Page  <12
  Share Topic   

Forum Jump Forum Permissions View Drop Down