Tutorial NtCreateNamedPipeFile (IPC - Named Pipes)

D

Deleted member 65228

Guest
#1
Hello.

IPC stands for Inter-Process Communication. It is a form of communicating with something else (e.g. one process to another). There are some great methods of IPC and depending on the need will depend on the best suitable option... Named pipes, shared memory and events are three good and common examples.

Recently, I was working on a project which required me to use NtCreateNamedPipeFile (NTDLL) from user-mode instead of CreateNamedPipeA/W (KERNEL32). I also needed to use named pipes from kernel-mode (although there is no routine for ZwCreateNamedPipeFile in kernel-mode available by default since it is not exported by ntoskrnl.exe, and finding the address would be unnecessary work (see last comment of this post)).

In user-mode, when you call CreateNamedPipeA or CreateNamedPipeW, a call to NtCreateNamedPipeFile (NTDLL) will eventually occur.

The function prototype for CreateNamedPipe:
Code:
HANDLE WINAPI CreateNamedPipe(
  _In_     LPCTSTR lpName,
  _In_     DWORD dwOpenMode,
  _In_     DWORD dwPipeMode,
  _In_     DWORD nMaxInstances,
  _In_     DWORD nOutBufferSize,
  _In_     DWORD nInBufferSize,
  _In_     DWORD nDefaultTimeOut,
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
The function prototype for NtCreateNamedPipeFile:
Code:
    PHANDLE NamedPipeFileHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK IoStatusBlock,
    ULONG ShareAccess,
    ULONG CreateDisposition,
    ULONG CreateOptions,
    BOOLEAN WriteModeMessage,
    BOOLEAN ReadModeMessage,
    BOOLEAN NonBlocking,
    ULONG MaxInstances,
    ULONG InBufferSize,
    ULONG OutBufferSize,
    PLARGE_INTEGER DefaultTimeOut
Below is an example I have written to demonstrate how you can create a named pipe in user-mode via NtCreateNamedPipeFile.
Code:
NTSTATUS NtCreateNamedPipe(
     WCHAR *PipeName,
     HANDLE *PipeHandle
)
{
    IO_STATUS_BLOCK IoStatusBlock = { 0 };
    OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
    UNICODE_STRING usPipeName = { 0 };
    LARGE_INTEGER WaitTime = { 0 };

    usPipeName.Buffer = PipeName;
    usPipeName.Length = (wcslen(usPipeName.Buffer) * 2);
    usPipeName.MaximumLength = (usPipeName.Length + 2);

    InitializeObjectAttributes(&ObjectAttributes,
        &usPipeName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL);

    WaitTime.QuadPart = -500000;

    return NtCreateNamedPipeFile(PipeHandle,
        GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
        &ObjectAttributes,
        &IoStatusBlock,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_OPEN_IF,
        FILE_SYNCHRONOUS_IO_NONALERT,
        TRUE,
        TRUE,
        FALSE,
        4294967295, // PIPE_UNLIMITED_INSTANCES in ULONG form
        512, // buffer in
        512, // buffer out
        &WaitTime);
}
When you pass the pipe name, make sure the format is correct for NT. For example, use "\\??\\pipe\\", otherwise it simply won't work. Unlike with the Win32 API, where you can just use "\\\\.\\pipe\\pipename".

You can do the rest of the operations for functionality with other NTAPI functions such as NtReadFile and NtWriteFile.

If you wish to create a named pipe in kernel-mode (or open an existing one), you will need to use IoCreateFile, and use NAMED_PIPE_CREATE_PARAMETERS.

Code:
NTSTATUS IoCreateFile(
  _Out_    PHANDLE            FileHandle,
  _In_     ACCESS_MASK        DesiredAccess,
  _In_     POBJECT_ATTRIBUTES ObjectAttributes,
  _Out_    PIO_STATUS_BLOCK   IoStatusBlock,
  _In_opt_ PLARGE_INTEGER     AllocationSize,
  _In_     ULONG              FileAttributes,
  _In_     ULONG              ShareAccess,
  _In_     ULONG              Disposition,
  _In_     ULONG              CreateOptions,
  _In_opt_ PVOID              EaBuffer,
  _In_     ULONG              EaLength,
  _In_     CREATE_FILE_TYPE   CreateFileType,
  _In_opt_ PVOID              InternalParameters,
  _In_     ULONG              Options
);
Notice the second to last parameter, InternalParameters (PVOID)? Use that for the NAMED_PIPE_CREATE_PARAMETERS.

With this all being said, it is not recommended to use the Native API for named pipes at all. In kernel-mode should you implement named pipes (e.g. via IoCreateFile), it is not officially supported by Microsoft and thus issues can easily occur (it is best to stick to more-documented models, such as Inverted Calls or IOCTL). In user-mode, it is best to stick to the Win32 API for such tasks since that is where documentation and official support lies...

Thanks for reading.
 
Last edited by a moderator:
D

Deleted member 65228

Guest
#2
Heads up.

Code:
usPipeName.Buffer = PipeName;
usPipeName.Length = (wcslen(usPipeName.Buffer) * 2);
usPipeName.MaximumLength = (usPipeName.Length + 2);
This is actually not essential. You can use RtlInitUnicodeString (NTDLL) instead which is much simpler.
Code:
UNICODE_STRING usPipeName;
RtlInitUnicodeString(&usPipeName, L"VALUE");


If you wish to implement functions like ConnectNamedPipe yourself (no Win32 API), you'll need to use other functions like NtFsControlFile... :)

ConnectNamedPipe - internally calls NtFsControlFile (NTDLL)
DisconnectNamedPipe - internally calls NtFsControlFile (NTDLL)
SetNamedPipeHandleState - internally calls NtSetInformationFile
 
Last edited by a moderator:
D

Deleted member 65228

Guest
#3
ConnectNamedPipe - internally calls NtFsControlFile (NTDLL)
You can define a CTL (Control Code) or you can hard-code a ULONG value...
Code:
#define FSCTL_PIPE_LISTEN CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
//or (ULONG)1114120
Code:
IO_STATUS_BLOCK IoStatusBlock = { 0 };

NtStatus = NtFsControlFile(PipeHandle,
            NULL,
            NULL,
            NULL,
            &IoStatusBlock,
            FSCTL_PIPE_LISTEN,
            NULL,
            NULL,
            NULL,
            NULL);
Once you have called that function, it'll continue once a client connects. For example, if you are debugging... You won't be able to step in further until a client connects (or unless an error occurred and the call failed - if the NTSTATUS result is STATUS_PENDING then you should wait for it and then get the success status from the IoStatusBlock).

E.g.

Code:
if (!NT_SUCCESS(NtStatus))
{
     if (NtStatus == STATUS_PENDING)
     {
          // WaitForSingleObject or NtWaitForSingleObject here for PipeHandle 
          NtStatus = IoStatusBlock.Information;
     }
}