A forum for reverse engineering, OS internals and malware analysis 

 #31405  by Li Yong
 Wed Apr 04, 2018 5:05 am
I'm working in a test project where i want close a opened handle by a user mode application since that the file name in use match with file name (obtained by this opened handle).

I will enumerate here each method of my example and say what he do.

0 - ntTraverse() - list all files of a specified folder and of your subfolders.
1 - FileHandleToUNICODE_STRING() - transform a file handle to a file name without driver letter, like C:
2 - KStr_Equals() - check if two UNICODE_STRING are equals.
3 - KStr_IndexOf() - search a substring (UNICODE_STRING) in other string (also UNICODE_STRING).
4 - CloseHandlesIfMatchFileName() - try close a opened handle if match with current file name (listed by ntTraverse() method), before opened handle is
converted to filename (like said in item 1).

5 - PrintProcessesUsingFile() - show what process is using the actual listed file, and pid found is passed to CloseHandlesIfMatchFileName() when called.
PS:
us parameter comes of PrintProcessesUsingFilePOA() called in ntTraverse()

Now my trouble: like title of question already says, ZwQueryInformationFile function returns STATUS_OBJECT_TYPE_MISMATCH every time when i want get the file name associated to opened handle but KeStachAttachProcess function is working fine to me.

Screenshot below shows a dll injected by Process Hacker software on notepad.exe (in this case was scanned Process Hacker root folder), obviously this generates a opened handle but like you can see ZwQueryInformationFile not was able to obtain the file name associated with this opened handle (then the error STATUS_OBJECT_TYPE_MISMATCH).

I really need know the file name of opened handle to compare and close exactly the handle matched to current file listed and in use by usermode process.

I'm leaving the complete example with source code (that can be compiled on VS 2017 + WDK with success) attached.

Here is the main logic of project:
Code: Select all
if ((Handles->UniqueProcessId == (HANDLE)id)) {

	status = PsLookupProcessByProcessId(id, &process);

	if (NT_SUCCESS(status))
	{
		RtlZeroMemory(FName.Buffer, FName.MaximumLength);

		KeStackAttachProcess(process, &ks);

		FileHandleToUNICODE_STRING(Handles->HandleValue, &FName);

		if (KStr_IndexOf(&FName, &us, &tmpPos))
		{ 

			DbgPrint("---. FName: %wZ => Pos: %d\n", &FName, tmpPos);

			/*	ohfi.Inherit = 0;
				ohfi.ProtectFromClose = 0;
				ObSetHandleAttributes(Handles->HandleValue, &ohfi, KernelMode);
				status = ZwClose(Handles->HandleValue);
				DbgPrint("ZwClose() status: 0x%X", status);  */

		}

		KeUnstackDetachProcess(&ks);
		ObDereferenceObject(process);
	}
}
And here is the method with ZwQueryInformationFile failing.
Code: Select all
NTSTATUS FileHandleToUNICODE_STRING(IN	HANDLE FileHandle, OUT PUNICODE_STRING FileName) {

	PIO_STATUS_BLOCK		pioStatusBlock;
	PFILE_NAME_INFORMATION	fniFileInfo;
	NTSTATUS				status;

	pioStatusBlock = (PIO_STATUS_BLOCK)ExAllocatePoolWithTag(NonPagedPool, sizeof(IO_STATUS_BLOCK), 'TAG');

	if (pioStatusBlock == NULL)
	{
		status = STATUS_INSUFFICIENT_RESOURCES;

		DbgPrint(":Failed to allocate memory for pioStatusBlock");

		return status;
	}

	fniFileInfo = (PFILE_NAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, sizeof(FILE_NAME_INFORMATION) + (MAX_PATH - 1), 'TAG');

	if (fniFileInfo == NULL)
	{
		status = STATUS_INSUFFICIENT_RESOURCES;

		ExFreePoolWithTag(pioStatusBlock, 'TAG');

		DbgPrint(":Failed to allocate memory for fniFileInfo");

		return status;
	}

	status = ZwQueryInformationFile(FileHandle, pioStatusBlock, fniFileInfo, (ULONG)sizeof(FILE_NAME_INFORMATION) + (MAX_PATH - 1), FileNameInformation);

	if (status != STATUS_SUCCESS)
	{
		ExFreePoolWithTag(pioStatusBlock, 'TAG');
		ExFreePoolWithTag(fniFileInfo, 'TAG');

		DbgPrint("ZwQueryInformationFile() --.STATUS %x \n", status);

		return status;
	}

	FileName->MaximumLength = fniFileInfo->FileNameLength;
	FileName->Length = fniFileInfo->FileNameLength;

	RtlCopyMemory(FileName->Buffer, &fniFileInfo->FileName, fniFileInfo->FileNameLength);

	ExFreePoolWithTag(pioStatusBlock, 'TAG');
	ExFreePoolWithTag(fniFileInfo, 'TAG');

	status = STATUS_SUCCESS;

	return status;  

}
Attachments
Driver.c and ntapi.h
(17.22 KiB) Downloaded 1 time
0xC0000024.png
0xC0000024.png (282.72 KiB) Viewed 195 times
 #31406  by Vrtule
 Wed Apr 04, 2018 12:18 pm
Hello,

if the handle you are calling ZwQueryInformationFile for a kernel handle or user handle? If the latter case, the routine probably will not accept it since it thinks (as all ZwXxx routines do) that you are calling it on behalf of the kernel. And kernel drivers should not trust (and use) user handles.

Furthermore, are you sure that the handle you are querying is a handle to an open file? If you try to get name of certain named pipes (through ZwQueryObject), the request would hang forever.

Vrtule
 #31407  by Li Yong
 Wed Apr 04, 2018 1:12 pm
Vrtule, thank you by answer.

the method CloseHandlesIfMatchFileName() enumerates all opened handles, but i belive that in this case ZwQueryInformationFile is executing only user mode handles, already that is of a particular process (user mode):
Code: Select all
if ((Handles->UniqueProcessId == (HANDLE)id)) {

...

KeStackAttachProcess(process, &ks);
FileHandleToUNICODE_STRING(Handles->HandleValue, &FName);

...
but i tested with NtQueryInformationFile and not worked, comes STATUS_ACCESS_VIOLATION.
 #31408  by Vrtule
 Wed Apr 04, 2018 1:44 pm
If you are using NtQueryInformationFile in a driver dispatch routine invoked because an application sent you an IOCTL request (i.e. ExGetPreviousMode returns UserMode), you must specify all buffers in usermode memory. If ExGetPreviousMode == UserMode, NtXxx routines accept only arguments passed from user mode (e.g. user handles, user buffers, user privileges).
 #31409  by Li Yong
 Wed Apr 04, 2018 2:14 pm
Vrtule wrote: Wed Apr 04, 2018 1:44 pm If you are using NtQueryInformationFile in a driver dispatch routine invoked because an application sent you an IOCTL request (i.e. ExGetPreviousMode returns UserMode), you must specify all buffers in usermode memory. If ExGetPreviousMode == UserMode, NtXxx routines accept only arguments passed from user mode (e.g. user handles, user buffers, user privileges).
In my example (that i have here in my pc), IOCTL is sent to driver only to receive the root directory name to be scanned. In this case, i must use NtXxx routines in all code of driver? if you see the attached file Driver.c will see that i use only ZwXxx routines (the difference here, is that not is sent any IOCTL from user mode), but in my example (that i have here on pc) yes.
 #31410  by EP_X0FF
 Wed Apr 04, 2018 5:23 pm
And why you even ask this question? The answer is obvious from your own code and title.

In this again copy-pasted from somewhere code clearly visible that:

1) It maintains list of handles by calling ZwQuerySystemInformation(...ExtendedHandles...)
2) It loops through this list and calls your routine FileHandleToUNICODE_STRING in context of target process
3) In FileHandleToUNICODE_STRING it calls ZwQueryInformationFile for each handle value
4) ZwQueryInformationFile fails sometimes with status STATUS_OBJECT_TYPE_MISMATCH (what a surprise)

So you expect this function will work with every type of object handle. That's interesting decision.

Here is the example for you what you do:
Hmm there is some handle of unknown type, lets say it is process object handle
Code: Select all
#define MustFail

int CALLBACK WinMain(
    _In_ HINSTANCE hInstance,
    _In_ HINSTANCE hPrevInstance,
    _In_ LPSTR     lpCmdLine,
    _In_ int       nCmdShow
)
{
    HANDLE hObject;

    OBJECT_ATTRIBUTES obja;
    IO_STATUS_BLOCK iost;
    CLIENT_ID cid;
    NTSTATUS status;
    UNICODE_STRING s;

    PFILE_NAME_INFORMATION pfni;
    WCHAR buffer[0x1000];

    cid.UniqueProcess = (HANDLE)GetCurrentProcessId();
    cid.UniqueThread = NULL;

    InitializeObjectAttributes(&obja, NULL, 0, NULL, NULL);

    s.Buffer = NULL;


#ifdef MustFail
    status = NtOpenProcess(&hObject, PROCESS_ALL_ACCESS, &obja, &cid);
#else

    if (!RtlDosPathNameToNtPathName_U(NtCurrentPeb()->ProcessParameters->ImagePathName.Buffer, &s, NULL, NULL))
    {
        Beep(0, 1);
        ExitProcess((DWORD)-1);
    }

    obja.ObjectName = &s;
    obja.Attributes = OBJ_CASE_INSENSITIVE;

    status = NtOpenFile(&hObject, FILE_READ_ATTRIBUTES, &obja, &iost, FILE_SHARE_VALID_FLAGS, 0);
#endif
Query File Name for given handle using NtQueryInformationFile
Code: Select all
    if (NT_SUCCESS(status)) {


        RtlSecureZeroMemory(buffer, 0x1000);
        pfni = (PFILE_NAME_INFORMATION)buffer;
        pfni->FileNameLength = MAX_PATH * 2;

        status = NtQueryInformationFile(hObject, &iost, pfni, sizeof(FILE_NAME_INFORMATION) + (MAX_PATH * 2), FileNameInformation);

        if (status == STATUS_OBJECT_TYPE_MISMATCH) { //<- you are here
            Beep(0, 0);
        }

        NtClose(hObject);
    }

    if (s.Buffer)
        RtlFreeUnicodeString(&s);

}
How that should work.

A) You do not copy-paste complete routines from stackoverflow and write them yourself from zero, if you are unable to do this -> then goto @Finish
B) Before writing this in driver you test parts of your code in user mode, if you are unable to do this -> then goto @Finish
C) While enumerating your handle list you check if requested handle object type is "File" (ZwQueryObject(TypeName) or query File object type index before - open file, query this handle value and remember it type index)
D) You do what ever you want next -> switch context like in your copy-pasted code or duplicate handle (for test in usermode) next query handle with ZwQueryInformationFile or using ObQueryNameString with object referenced by this handle
E) Any fail everywhere above -> then goto @Finish
...


@Finish: You do not mess with things you are don't understand without learning basics first.

One simple thing you should remember and understand - you can't learn anything by doing blatant copy-pasting and flooding about expected errors on forums next. This doesn't work by design.

Asking such questions clearly demonstrates that you are not yet ready for such coding.
 #31411  by Li Yong
 Wed Apr 04, 2018 6:37 pm
EP_X0FF,

thank you. So ZwQueryInformationFile/NtQueryInformationFile will works only if type index of opened handle is "File", else will fail.
And the same also is true for ObReferenceObjectByHandle and ObQueryNameString (implemented below)?
Code: Select all
NTSTATUS FileHandleToUNICODE_STRING(IN	HANDLE FileHandle, OUT PUNICODE_STRING FileName) {

	PFILE_OBJECT        fileObject;
	PDEVICE_OBJECT      fileSysDevice;
	POBJECT_NAME_INFORMATION	pobjObjectNameInfo;
	ULONG      ulLength = 0;
	NTSTATUS            ntStatus;

	ntStatus = STATUS_SUCCESS;

	pobjObjectNameInfo = (POBJECT_NAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, sizeof(OBJECT_NAME_INFORMATION) + (MAX_PATH * 2), 'TG');

	if (pobjObjectNameInfo == NULL)
	{
		DbgPrint("Failed to allocate memory for pobjObjectNameInfo\n");

		ntStatus = STATUS_INSUFFICIENT_RESOURCES;

		return ntStatus;
	}

	ntStatus = ObReferenceObjectByHandle(FileHandle, 0, NULL, KernelMode, &fileObject, NULL);

	if (!NT_SUCCESS(ntStatus)) {

		DbgPrint("Could not get fileobject from handle\n");

		ObDereferenceObject(fileObject);
		ExFreePoolWithTag(pobjObjectNameInfo, 'TG');

		return ntStatus;
	}

	ntStatus = ObQueryNameString(fileObject, pobjObjectNameInfo, sizeof(OBJECT_NAME_INFORMATION) + (MAX_PATH * 2), &ulLength);

	if (ntStatus != STATUS_SUCCESS)
	{
		DbgPrint("ObQueryNameString() --. STATUS %x \n", ntStatus);

		ObDereferenceObject(fileObject);
		ExFreePoolWithTag(pobjObjectNameInfo, 'TG');

		return ntStatus;
	}

	fileSysDevice = IoGetRelatedDeviceObject(fileObject);

	if (!fileSysDevice) {

		DbgPrint("Could not get related device object\n");

		ObDereferenceObject(fileObject);
		ExFreePoolWithTag(pobjObjectNameInfo, 'TG');

		return ntStatus;
	}

	ntStatus = IoQueryFileDosDeviceName(fileObject, &pobjObjectNameInfo);

	if (ntStatus != STATUS_SUCCESS)
	{
		DbgPrint("IoQueryFileDosDeviceName() --. STATUS %x \n", ntStatus);

		ObDereferenceObject(fileObject);
		ExFreePoolWithTag(pobjObjectNameInfo, 'TG');

		return ntStatus;
	}

	DbgPrint("FileName: %ws\n", pobjObjectNameInfo->Name.Buffer);

	FileName->MaximumLength = pobjObjectNameInfo->Name.Length + 100;
	FileName->Length = pobjObjectNameInfo->Name.Length + 100;

	RtlZeroMemory(FileName->Buffer, FileName->MaximumLength);

	RtlCopyMemory(FileName->Buffer, pobjObjectNameInfo->Name.Buffer, pobjObjectNameInfo->Name.Length + 100);

	ObDereferenceObject(fileObject);
	ExFreePoolWithTag(pobjObjectNameInfo, 'TG');
	
   return ntStatus;
}
On case above, for example where i have a dll injected by Process Hacker in notepad.exe, then not was recognized as a type index of "File" already that ZwQueryInformationFile returned STATUS_OBJECT_TYPE_MISMATCH for all handles opened by notepad.exe
 #31412  by Vrtule
 Wed Apr 04, 2018 10:11 pm
ZwXxxFile routines work only with file objects (files, directories, pipes, mailslots, ...), ZwXxxProcess only with process objects, ZwXxxThread only with threads etc. By the way, these routines (their NtXxx variants, since each ZwXxx routine is just a thin wrapper around its NtXxx counterpart) use ObReferenceObjectByHandle to check the target object type. I recommend you to do the same (do not pass the NULL value as the object type parameter).

As EP said, just copy-pasting the code leads nowhere. Even if your code worked you could not be sure that it does the right thing in all situations. It might be kind of an accident that it works. That's why understanding the principles and concepts is useful, especially in low level things like driver development (especially the development of hacky drivers).