Today, we continue our story about how we, along with the team from Innopolis University, are developing the Active Restore technology to allow users to start working on their machines as soon as possible after a crash. We will discuss native Windows applications, including the specifics of their creation and launch. Below is a bit about our project and a practical guide on how to write native applications.

In previous posts, we have already talked about what is and how the students from Innopolis are developing it. Today, I want to focus on native applications, to the level at which we want to "embed" our active recovery service. If everything goes well, we will be able to: Start the service much earlier
- Connect to the cloud where the backup is stored much earlier
- Understand much earlier what mode the system is in – normal boot or recovery
- Recover many fewer files in advance
- Allow the user to get to work even faster.
- What exactly is a native application?
To answer this question, let’s take a look at the sequence of calls that the system makes, for example, if a programmer tries to create a file in their application.
Pavel Yosifovich — Windows Kernel Programming (2019)

The programmer uses the function
CreateFile NtCreateFile . .
The main advantage of native applications is that ntdll is loaded into the system significantly earlier than kernel32. This makes sense, as kernel32 requires ntdll to function. Consequently, applications that use native functions can start operating much earlier.
Thus, Windows Native Applications are programs capable of launching at an early stage of the Windows boot process. They use ONLY functions from ntdll. An example of such an application is: which executes the to check the disk for errors before the main services start. This is precisely the level at which we want to see our Active Restore.
What do we need?
- (Driver Development Kit), also known today as WDK 7 (Windows Driver Kit).
- A virtual machine (for example, Windows 7 x64)
- Not necessary, but header files that can be downloaded may be helpful.
What about the code?
Let's practice a bit and write a small application that:
- Displays a message on the screen
- Allocates some memory
- Waits for input from the keyboard
- Frees the allocated memory
In native applications, the entry point is not main or winmain, but the function NtProcessStartup, as we are effectively launching new processes directly in the system.
Let's start by displaying a message on the screen. For this, we have a native function , which takes a pointer to an object of the UNICODE_STRING structure as an argument. We can initialize it using RtlInitUnicodeString. As a result, to display text on the screen, we can write a small function like this:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Since we only have access to functions from ntdll and no other libraries are yet in memory, we will inevitably face problems with how to allocate memory. The new operator does not exist yet (as it belongs to the too high-level world of C++), nor is there a malloc function (which requires C runtime libraries). We can only use the stack. However, if we need to dynamically allocate memory, we will have to do it in the heap. So let's create a heap for ourselves and allocate memory from it when we need it.
For this task, we can use the function . Next, using RtlAllocateHeap and RtlFreeHeap, we will allocate and free memory when needed.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// create heap in order to allocate memory later
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// allocate buffer of size bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// free buffer (actually not needed because we destroy heap in next step)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Let’s move on to waiting for input from the keyboard.
// https://docs.microsoft.com/en-us/windows/win32/api/ntddkbd/ns-ntddkbd-keyboard_input_data
typedef struct _KEYBOARD_INPUT_DATA {
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
//...
HANDLE hKeyBoard, hEvent;
UNICODE_STRING skull, keyboard;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER ByteOffset;
KEYBOARD_INPUT_DATA kbData;
// inialize variables
RtlInitUnicodeString(&keyboard, L"DeviceKeyboardClass0");
InitializeObjectAttributes(&ObjectAttributes, &keyboard, OBJ_CASE_INSENSITIVE, NULL, NULL);
// open keyboard device
NtCreateFile(&hKeyBoard,
SYNCHRONIZE | GENERIC_READ | FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&Iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,FILE_DIRECTORY_FILE,
NULL, 0);
// create event to wait on
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &ObjectAttributes, 1, 0);
while (TRUE)
{
NtReadFile(hKeyBoard, hEvent, NULL, NULL, &Iosb, &kbData, sizeof(KEYBOARD_INPUT_DATA), &ByteOffset, NULL);
NtWaitForSingleObject(hEvent, TRUE, NULL);
if (kbData.MakeCode == 0x01) // if ESC pressed
{
break;
}
}All we need to do is use on the opened device and wait for a keypress from the keyboard. In case the ESC key is pressed, we will continue operation. To open the device, we need to call the NtCreateFile function (we'll need to open DeviceKeyboardClass0). We will also call , to initialize the waiting object. We will independently declare the KEYBOARD_INPUT_DATA structure, which represents keyboard data. This will make our work easier.
The native application's work ends with the call to the , because we are simply terminating our own process.
The entire code of our small application:
#include "ntifs.h" // WinDDK7600.16385.1incddk
#include "ntdef.h"
//------------------------------------
// Following function definitions can be found in native development kit
// but I am too lazy to include `em so I declare it here
//------------------------------------
NTSYSAPI
NTSTATUS
NTAPI
NtTerminateProcess(
IN HANDLE ProcessHandle OPTIONAL,
IN NTSTATUS ExitStatus
);
NTSYSAPI
NTSTATUS
NTAPI
NtDisplayString(
IN PUNICODE_STRING String
);
NTSTATUS
NtWaitForSingleObject(
IN HANDLE Handle,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout
);
NTSYSAPI
NTSTATUS
NTAPI
NtCreateEvent(
OUT PHANDLE EventHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN EVENT_TYPE EventType,
IN BOOLEAN InitialState
);
// https://docs.microsoft.com/en-us/windows/win32/api/ntddkbd/ns-ntddkbd-keyboard_input_data
typedef struct _KEYBOARD_INPUT_DATA {
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
//----------------------------------------------------------
// Our code goes here
//----------------------------------------------------------
// usage: WriteLn(L"Hello Native World!n");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}
void NtProcessStartup(void* StartupArgument)
{
// it is important to declare all variables at the beginning
HANDLE hKeyBoard, hEvent;
UNICODE_STRING skull, keyboard;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER ByteOffset;
KEYBOARD_INPUT_DATA kbData;
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
//use it if debugger connected to break
//DbgBreakPoint();
WriteLn(L"Hello Native World!n");
// inialize variables
RtlInitUnicodeString(&keyboard, L"DeviceKeyboardClass0");
InitializeObjectAttributes(&ObjectAttributes, &keyboard, OBJ_CASE_INSENSITIVE, NULL, NULL);
// open keyboard device
NtCreateFile(&hKeyBoard,
SYNCHRONIZE | GENERIC_READ | FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&Iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,FILE_DIRECTORY_FILE,
NULL, 0);
// create event to wait on
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &ObjectAttributes, 1, 0);
WriteLn(L"Keyboard readyn");
// create heap in order to allocate memory later
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
WriteLn(L"Heap readyn");
// allocate buffer of size bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
WriteLn(L"Buffer allocatedn");
// free buffer (actually not needed because we destroy heap in next step)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);
WriteLn(L"Heap destroyedn");
WriteLn(L"Press ESC to continue...n");
while (TRUE)
{
NtReadFile(hKeyBoard, hEvent, NULL, NULL, &Iosb, &kbData, sizeof(KEYBOARD_INPUT_DATA), &ByteOffset, NULL);
NtWaitForSingleObject(hEvent, TRUE, NULL);
if (kbData.MakeCode == 0x01) // if ESC pressed
{
break;
}
}
NtTerminateProcess(NtCurrentProcess(), 0);
}PS: We can easily use the DbgBreakPoint() function in the code to stop in the debugger. However, we will need to connect WinDbg to the virtual machine for kernel debugging. Instructions on how to do this can be found or simply use .
Compilation and assembly
The easiest way to build a native application is to use Driver Development Kit). We specifically need the old seventh version, as later versions have a slightly different approach and are closely integrated with Visual Studio. If you use the DDK, then our project only needs a Makefile and sources.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defsources:
TARGETNAME = MyNative
TARGETTYPE = PROGRAM
UMTYPE = nt
BUFFER_OVERFLOW_CHECKS = 0
MINWIN_SDK_LIB_PATH = $(SDK_LIB_PATH)
SOURCES = source.c
INCLUDES = $(DDK_INC_PATH);
C:WinDDK7600.16385.1ndk;
TARGETLIBS = $(DDK_LIB_PATH)ntdll.lib
$(DDK_LIB_PATH)nt.lib
USE_NTDLL = 1Your Makefile will be exactly the same; let’s stop a little longer on sources. This file specifies the source code of your program (the .c files), build options, and other parameters.
- TARGETNAME is the name of the executable file that should be generated.
- TARGETTYPE is the type of executable file; this could be a driver (.sys), in which case the value should be DRIVER, or a library (.lib), then the value LIBRARY. In our case, we need an executable file (.exe), so we set the value to PROGRAM.
- UMTYPE – possible values for this field: console for console applications, windows for windowed applications. However, we need to specify nt to obtain a native application.
- BUFFER_OVERFLOW_CHECKS – checking the stack for buffer overflow, unfortunately not our case, we will disable it.
- MINWIN_SDK_LIB_PATH – this value refers to the SDK_LIB_PATH variable, there's no need to worry if you haven't declared such a system variable; when we run a checked build from the DDK, this variable will be declared and will refer to the necessary libraries.
- SOURCES – the list of source files for your program.
- INCLUDES – header files required for the build. Here, you usually specify the path to the files included with the DDK, but you can additionally specify any others.
- TARGETLIBS – the list of libraries that need to be linked.
- USE_NTDLL – a mandatory field that must be set to 1. For quite obvious reasons.
- USER_C_FLAGS – any flags you can use in preprocessor directives when preparing the application code.
So to build, we need to run x86 (or x64) Checked Build, change the working directory to the project folder, and execute the Build command. The result in the screenshot shows that we have built one executable file.

This file cannot be easily run; the system complains and sends us to ponder its behavior with the following error:

How to run a native application?
At startup, the program launch sequence is determined by the value of the registry key:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteThe session manager executes the programs in this list sequentially. The session manager looks for the executable files in the system32 directory. The format of the registry key value is as follows:
autocheck autochk *MyNativeThe value must be in hexadecimal format, not the usual ASCII, therefore the key presented above will have the format:
61,75,74,6f,63,68,65,63,6b,20,61,75,74,6f,63,68,6b,20,2a,00,4d,79,4e,61,74,69,76,65,00,00To convert the name, you can use an online service, for example, .

So, to run the native application, we need to:
- Copy the executable file to the system32 folder
- Add a key to the registry
- Reboot the machine
For convenience, here's a ready-made script for installing the native application:
install.bat
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pauseadd.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager]
"BootExecute"=hex(7):61,75,74,6f,63,68,65,63,6b,20,61,75,74,6f,63,68,6b,20,2a,00,4d,79,4e,61,74,69,76,65,00,00After installation and rebooting, even before the user selection screen appears, we will see the following picture:

Summary
Using this small application as an example, we have confirmed that it is indeed possible to launch an application at the Windows Native level. Moving forward, we will continue to collaborate with the team from Innopolis University to build a service that will initiate interaction with the driver much earlier than in the previous version of our project. With the introduction of the win32 shell, it will logically transfer control to a fully developed service that has already been created (more on this later). ).
In the next article, we will touch on another component of the Active Restore service, namely the UEFI driver. Subscribe to our blog to not miss the next post.
Source: habr.com
