Introduction
We will use driver from our previous article to break with debugger in kernel mode. Then, we will replace ZwCreateFile
and ZwClose
function calls with our own implementation that reveals usage of internal _OPEN_PACKET
structure.
Background
You need to install:
- Virtual Box
- Virtual Box Extension Pack (so our Guest OS can see USB flash drive)
- Guest OS (I installed both Windows 8.1 x86 and Windows 8.1 x64)
- Virtual Box Guest Additions http://download.virtualbox.org/virtualbox/5.2.6/ (so we can share folders between Host and Guest).
In Guest OS, run cmd as administrator and execute the following commands:
bcdedit /copy {current} /d "Windows 8.1 Debug"
bcdedit /debug {NEW GUID} on
bcdedit /set {NEW GUID} testsigning on
// required only for 64 bit version of Windows bcdedit /dbgsettings serial debugport:1 baudrate:115200
Now shutdown guest OS and add serial port:
Insert USB flash drive and add USB filter for it:
Add shared folder:
Using the Code
We will simplify our user mode program to use service:
HANDLE Handle;
ULONG Id;
Handle = CreateFileW(L"\\\\.\\DeviceInfo", 0, FILE_SHARE_READ |
FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
if (Handle != INVALID_HANDLE_VALUE)
{
Id = USBDriveRef(Handle, 0);
if (Id != INVALID_ID)
{
DeviceUnref(Handle, Id);
wprintf(L"success\n");
}
else wprintf(L"failure\n");
CloseHandle(Handle)
}
User mode code must be recompiled with release configuration so we can run it on Guest OS without Visual Studio installed. On the other hand, we can stay with debug configuration for our driver.
Put executables to the shared folder on Host OS (driver, program to run service, program to use service). Launch Guest OS and wait for OS selection window. Now Run IDA (32 bit version for 32 bit Guest / 64 bit version for 64 bit Guest) and go to Debugger -> Attach -> Windbg debugger
Go to Debug options -> Set specific options and select Kernel mode debugging
Enter the following connection string:
com:port=\\.\pipe\com_1,baud=115200,pipe
Hit OK, select process to attach to:
And hit OK again. Now return to Guest OS and select "Windows 8.1 Debug" boot entry. IDA will break:
At the bottom of the screen, you can enter WINDBG
commands. Enter the following WINDBG
command:
bu deviceowner!GetDeviceObject
Continue process and allow system to boot. Now copy our executables from shared folder (in explorer: Network -> VBOXSVR -> \\VBOXSVR\Shared) to another location and run service. Run program to use service, and our unresolved breakpoint will be hit:
Now you can dig into ZwCreateFile function and follow the execution flow. Enter the following WINDBG
command to see the layout of internal _OPEN_PACKET
structure:
dt nt!_OPEN_PACKET
You can use Reactos source repository to get an idea of what should happen and to discover internal structure names.
Header file that contains definition of internal structures and internal function prototypes:
typedef struct _IO_DRIVER_CREATE_CONTEXT
{
SHORT Size;
PVOID pExtraCreateParameter;
PVOID pDeviceObjectHint;
PVOID pTxnParameters;
} IO_DRIVER_CREATE_CONTEXT, *PIO_DRIVER_CREATE_CONTEXT;
typedef struct _OPEN_PACKET
{
SHORT Type;
SHORT Size;
FILE_OBJECT *pFileObject;
NTSTATUS FinalStatus;
ULONG_PTR Information;
ULONG ParseCheck;
union
{
FILE_OBJECT *pRelatedFileObject;
DEVICE_OBJECT *pReferencedDeviceObject;
};
OBJECT_ATTRIBUTES *pOriginalAttributes;
LARGE_INTEGER AllocationSize;
ULONG CreateOptions;
USHORT FileAttributes;
USHORT ShareAccess;
PVOID pEaBuffer;
ULONG EaLength;
ULONG Options;
ULONG Disposition;
FILE_BASIC_INFORMATION *pBasicInformation;
FILE_NETWORK_OPEN_INFORMATION *pNetworkInformation;
CREATE_FILE_TYPE CreateFileType;
PVOID pMailslotOrPipeParameters;
BOOLEAN Override;
BOOLEAN QueryOnly;
BOOLEAN DeleteOnly;
BOOLEAN FullAttributes;
PVOID pLocalFileObject;
ULONG InternalFlags;
KPROCESSOR_MODE AccessMode;
IO_DRIVER_CREATE_CONTEXT DriverCreateContext;
} OPEN_PACKET, *POPEN_PACKET;
typedef BOOLEAN (__fastcall *TFsRtlpCleanupEcps)(PVOID pExtraCreateParameter);
typedef NTSTATUS (__fastcall *TIopCreateFile)(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
PLARGE_INTEGER pAllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG Disposition,
ULONG CreateOptions,
PVOID pEaBuffer,
ULONG EaLength,
CREATE_FILE_TYPE CreateFileType,
PVOID pExtraCreateParameters,
ULONG Options,
ULONG Flags,
PIO_DRIVER_CREATE_CONTEXT pIoDriverCreateContext);
typedef VOID (*TIopCloseFile)(PEPROCESS pProcess,
PFILE_OBJECT pFileObject, ULONG HandleCount, ULONG SystemHandleCount);
extern TFsRtlpCleanupEcps FsRtlpCleanupEcps;
extern TIopCloseFile IopCloseFile;
extern "C"
{
extern POBJECT_TYPE *IoDriverObjectType;
NTKERNELAPI NTSTATUS ObOpenObjectByName(POBJECT_ATTRIBUTES pObjectAttributes,
POBJECT_TYPE pObjectType,
KPROCESSOR_MODE AccessMode,
PACCESS_STATE pAccessState,
ACCESS_MASK DesiredAccess,
POPEN_PACKET pOpenPacket,
PHANDLE pHandle);
NTKERNELAPI NTSTATUS IoCheckEaBufferValidity(PFILE_FULL_EA_INFORMATION pEaBuffer,
ULONG EaLength,
PULONG pErrorOffset);
}
NTSTATUS OpenDriver(WCHAR *pDriverName, DRIVER_OBJECT **ppDriverObject);
NTSTATUS CreateFile(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
PLARGE_INTEGER pAllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG Disposition,
ULONG CreateOptions,
PVOID pEaBuffer,
ULONG EaLength);
NTSTATUS OpenFile(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions);
NTSTATUS Close(HANDLE Handle);
Source file that contains our own implementation of ZwCreateFile
and ZwClose
functions:
#include <wdm.h>
#include "Internals.h"
TFsRtlpCleanupEcps FsRtlpCleanupEcps;
TIopCloseFile IopCloseFile;
NTSTATUS OpenDriver(WCHAR *pDriverName, DRIVER_OBJECT **ppDriverObject)
{
HANDLE Handle;
NTSTATUS Status;
UNICODE_STRING ObjectName;
OBJECT_ATTRIBUTES ObjectAttributes;
DRIVER_OBJECT *pDriverObject;
RtlInitUnicodeString(&ObjectName, pDriverName);
InitializeObjectAttributes(&ObjectAttributes, &ObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL);
Status = ObOpenObjectByName(&ObjectAttributes, *IoDriverObjectType,
KernelMode, NULL, FILE_READ_ATTRIBUTES, NULL, &Handle);
if (NT_SUCCESS(Status))
{
Status = ObReferenceObjectByHandle(Handle, 0, NULL, KernelMode, (PVOID*)&pDriverObject, NULL);
if (NT_SUCCESS(Status)) *ppDriverObject = pDriverObject;
Close(Handle);
}
return Status;
}
NTSTATUS __fastcall IopCreateFile(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
PLARGE_INTEGER pAllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG Disposition,
ULONG CreateOptions,
PVOID pEaBuffer,
ULONG EaLength,
CREATE_FILE_TYPE CreateFileType,
PVOID pExtraCreateParameters,
ULONG Options,
ULONG Flags,
PVOID pIoDriverCreateContext)
{
BOOLEAN b;
HANDLE Handle;
NTSTATUS Status;
PVOID pEaBuffer2;
ULONG ErrorOffset;
POPEN_PACKET pOpenPacket;
pOpenPacket = (POPEN_PACKET)ExAllocatePool(NonPagedPool, sizeof(OPEN_PACKET));
if (pOpenPacket)
{
pOpenPacket->Type = IO_TYPE_OPEN_PACKET;
pOpenPacket->Size = sizeof(OPEN_PACKET);
pOpenPacket->pFileObject = NULL;
pOpenPacket->FinalStatus = 0;
pOpenPacket->Information = 0;
pOpenPacket->ParseCheck = 0;
pOpenPacket->pRelatedFileObject = NULL;
pOpenPacket->pOriginalAttributes = pObjectAttributes;
if (pAllocationSize)
{
pOpenPacket->AllocationSize.LowPart = pAllocationSize->LowPart;
pOpenPacket->AllocationSize.HighPart = pAllocationSize->HighPart;
}
else
{
pOpenPacket->AllocationSize.LowPart = 0;
pOpenPacket->AllocationSize.HighPart = 0;
}
pOpenPacket->CreateOptions = CreateOptions;
pOpenPacket->FileAttributes = FileAttributes;
pOpenPacket->ShareAccess = ShareAccess;
if (pEaBuffer && EaLength)
{
pEaBuffer2 = ExAllocatePool(NonPagedPool, EaLength);
if (pEaBuffer2)
{
memcpy(pEaBuffer2, pEaBuffer, EaLength);
Status = IoCheckEaBufferValidity((PFILE_FULL_EA_INFORMATION)pEaBuffer2,
EaLength, &ErrorOffset);
if (NT_SUCCESS(Status))
{
pOpenPacket->pEaBuffer = pEaBuffer2;
pOpenPacket->EaLength = EaLength;
}
else
{
ExFreePool(pEaBuffer2);
goto cleanup;
}
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto cleanup;
}
}
else
{
pOpenPacket->pEaBuffer = NULL;
pOpenPacket->EaLength = 0;
}
pOpenPacket->Options = Options;
pOpenPacket->Disposition = Disposition;
pOpenPacket->pBasicInformation = NULL;
pOpenPacket->pNetworkInformation = NULL;
pOpenPacket->CreateFileType = CreateFileType;
pOpenPacket->pMailslotOrPipeParameters = pExtraCreateParameters;
pOpenPacket->Override = FALSE;
pOpenPacket->QueryOnly = FALSE;
pOpenPacket->DeleteOnly = FALSE;
pOpenPacket->FullAttributes = FALSE;
pOpenPacket->pLocalFileObject = NULL;
pOpenPacket->InternalFlags = Flags;
pOpenPacket->AccessMode = KernelMode;
if (pIoDriverCreateContext)
{
memcpy(&pOpenPacket->DriverCreateContext, pIoDriverCreateContext,
sizeof(IO_DRIVER_CREATE_CONTEXT));
}
else
{
memset(&pOpenPacket->DriverCreateContext, 0, sizeof(IO_DRIVER_CREATE_CONTEXT));
pOpenPacket->DriverCreateContext.Size = sizeof(IO_DRIVER_CREATE_CONTEXT);
}
Status = ObOpenObjectByName(pObjectAttributes, *IoFileObjectType,
KernelMode, NULL, DesiredAccess, pOpenPacket, &Handle);
if (pOpenPacket->DriverCreateContext.pExtraCreateParameter)
{
b = FsRtlpCleanupEcps(pOpenPacket->DriverCreateContext.pExtraCreateParameter);
if (b) pOpenPacket->DriverCreateContext.pExtraCreateParameter = NULL;
}
if (pOpenPacket->ParseCheck == 0xBEAA0251) b = TRUE;
else b = FALSE;
if (NT_SUCCESS(Status) && b)
{
pOpenPacket->pFileObject->Flags |= FO_HANDLE_CREATED;
pOpenPacket->pFileObject->Flags &= ~FO_DISALLOW_EXCLUSIVE;
pIoStatusBlock->Status = pOpenPacket->FinalStatus;
pIoStatusBlock->Information = pOpenPacket->Information;
*pHandle = Handle;
ObDereferenceObject(pOpenPacket->pFileObject);
Status = pOpenPacket->FinalStatus;
}
else
{
if (NT_SUCCESS(Status))
{
ObCloseHandle(Handle, KernelMode);
Status = STATUS_OBJECT_TYPE_MISMATCH;
}
if (NT_SUCCESS(pOpenPacket->FinalStatus))
{
if (pOpenPacket->pFileObject)
{
if (!b)
{
if (pOpenPacket->pFileObject->FileName.Length)
ExFreePoolWithTag(pOpenPacket->pFileObject->FileName.Buffer, 0);
pOpenPacket->pFileObject->DeviceObject = NULL;
}
else
{
if (!(pOpenPacket->pFileObject->Flags & FO_HANDLE_CREATED))
IopCloseFile(NULL, pOpenPacket->pFileObject, 1, 1);
}
}
}
if (pOpenPacket->pFileObject) ObDereferenceObject(pOpenPacket->pFileObject);
}
cleanup:
ExFreePool(pOpenPacket);
}
else Status = STATUS_INSUFFICIENT_RESOURCES;
return Status;
}
NTSTATUS CreateFile(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
PLARGE_INTEGER pAllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG Disposition,
ULONG CreateOptions,
PVOID pEaBuffer,
ULONG EaLength)
{
return IopCreateFile(pHandle,
DesiredAccess,
pObjectAttributes,
pIoStatusBlock,
pAllocationSize,
FileAttributes,
ShareAccess,
Disposition,
CreateOptions,
pEaBuffer,
EaLength,
CreateFileTypeNone,
NULL,
0,
0x20,
NULL);
}
NTSTATUS OpenFile(PHANDLE pHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES pObjectAttributes,
PIO_STATUS_BLOCK pIoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions)
{
return CreateFile(pHandle, DesiredAccess, pObjectAttributes,
pIoStatusBlock, NULL, 0, ShareAccess, FILE_OPEN, OpenOptions, NULL, 0);
}
NTSTATUS Close(HANDLE Handle)
{
return ObCloseHandle(Handle, KernelMode);
}
To get the address of not exported (private
) functions like FsRtlpCleanupEcps
and IopCloseFile
, we need to obtain address of exported symbol and add offset (offset can be calculated by subtracting addresses that can be observed when we debug the original kernel code).
FsRtlpCleanupEcps = (TFsRtlpCleanupEcps)((UCHAR*)ObOpenObjectByName - 0x1BCB4);
IopCloseFile = (TIopCloseFile)((UCHAR*)ObOpenObjectByName - 0x402C);
FsRtlpCleanupEcps = (TFsRtlpCleanupEcps)((UCHAR*)ObOpenObjectByName + 0x79530);
IopCloseFile = (TIopCloseFile)((UCHAR*)ObOpenObjectByName + 0x5599C);