Vandaag gaan we verder met ons verhaal over onze samenwerking met Innopolis University om Active Restore-technologie te ontwikkelen waarmee gebruikers na een computercrash zo snel mogelijk hun werk kunnen hervatten. We zullen het hebben over native apps. Windows, включая особенности их создания и запуска. Под катом – немного о нашем проекте, а также практическое руководство как писать нативные приложения.

In eerdere berichten hebben we al verteld wat het is , en hoe studenten van Innopolis zich ontwikkelen . Vandaag wil ik me concentreren op native applicaties, tot op het niveau waarop we onze actieve herstelservice willen 'begraven'. Als alles lukt, kunnen we:
- Start de service zelf veel eerder
- Neem veel eerder contact op met de cloud waar de back-up staat
- Veel eerder om te begrijpen in welke modus het systeem zich bevindt: normaal opstarten of herstel
- Veel minder bestanden die vooraf moeten worden hersteld
- Laat de gebruiker nog sneller aan de slag.
Wat is eigenlijk een native app?
Laten we, om deze vraag te beantwoorden, kijken naar de reeks oproepen die het systeem doet, bijvoorbeeld als een programmeur in zijn applicatie een bestand probeert te maken.

Pavel Yosifovich — Windows Kernel Programming (2019)
De programmeur gebruikt de functie , dat wordt gedeclareerd in het headerbestand fileapi.h en geïmplementeerd in Kernel32.dll. Deze functie maakt het bestand echter niet zelf, maar controleert alleen de invoerargumenten en roept de functie aan (het voorvoegsel Nt geeft alleen aan dat de functie native is). Deze functie wordt gedeclareerd in het headerbestand winternl.h en geïmplementeerd in ntdll.dll. Het bereidt zich voor om in de nucleaire ruimte te springen, waarna het een systeemoproep doet om een bestand te maken. In dit geval blijkt dat Kernel32 slechts een verpakking voor Ntdll is. Een van de redenen waarom dit werd gedaan, is dat Microsoft dus de mogelijkheid heeft om de functies van de oorspronkelijke wereld te veranderen, maar de standaardinterfaces niet aan te raken. Microsoft raadt af om native functies rechtstreeks aan te roepen en documenteert de meeste daarvan niet. Er zijn overigens ongedocumenteerde functies te vinden .
Het belangrijkste voordeel van native applicaties is dat ntdll veel eerder in het systeem wordt geladen dan kernel32. Dit is logisch, omdat kernel32 ntdll nodig heeft om te werken. Als gevolg hiervan kunnen applicaties die native functies gebruiken veel eerder aan de slag.
Dus de Windows Native Applications – это программы, способные запускаться на раннем этапе загрузки Windows. Они используют ТОЛЬКО функции из ntdll. Пример такого приложения: wie presteert om de schijf op fouten te controleren voordat u de hoofdservices start. Dit is precies het niveau dat we willen dat onze Active Restore is.
Wat hebben we nodig?
- (Driver Development Kit), ныне также известный под названием WDK 7 (Windows Driver Kit).
- Виртуальная машина (например, Windows 7 x64)
- Niet nodig, maar headerbestanden die kunnen worden gedownload kunnen helpen
Wat staat er in de code?
Laten we een beetje oefenen en bijvoorbeeld een kleine applicatie schrijven die:
- Geeft een bericht weer op het scherm
- Wijst wat geheugen toe
- Wacht op toetsenbordinvoer
- Maakt gebruikt geheugen vrij
In native applicaties is het toegangspunt niet main of winmain, maar de NtProcessStartup-functie, omdat we feitelijk direct nieuwe processen in het systeem starten.
Laten we beginnen met het weergeven van een bericht op het scherm. Hiervoor hebben we een native functie , dat als argument een verwijzing naar een UNICODE_STRING-structuurobject heeft. RtlInitUnicodeString helpt ons het te initialiseren. Als gevolg hiervan kunnen we deze kleine functie schrijven om tekst op het scherm weer te geven:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Omdat alleen functies van ntdll voor ons beschikbaar zijn, en er simpelweg nog geen andere bibliotheken in het geheugen zijn, zullen we zeker problemen krijgen met het toewijzen van geheugen. De nieuwe operator bestaat nog niet (omdat deze uit de te hoge wereld van C++ komt) en er is geen malloc-functie (er zijn runtime C-bibliotheken voor nodig). Je kunt natuurlijk alleen een stapel gebruiken. Maar als we geheugen dynamisch moeten toewijzen, zullen we dat op de heap (dat wil zeggen heap) moeten doen. Laten we dus een hoop voor onszelf creëren en er herinneringen uit halen wanneer we die nodig hebben.
De functie is geschikt voor deze taak . Vervolgens zullen we met behulp van RtlAllocateHeap en RtlFreeHeap geheugen bezetten en vrijmaken wanneer we het nodig hebben.
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);Laten we verder gaan met wachten op toetsenbordinvoer.
// 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;
}
}Het enige wat we nodig hebben is gebruiken op een open apparaat en wacht tot het toetsenbord elke druk op ons teruggeeft. Als de ESC-toets wordt ingedrukt, gaan we verder met werken. Om het apparaat te openen, moeten we de functie NtCreateFile aanroepen (we moeten DeviceKeyboardClass0 openen). Wij zullen ook bellen om het wait-object te initialiseren. We zullen zelf de KEYBOARD_INPUT_DATA-structuur declareren, die de toetsenbordgegevens vertegenwoordigt. Dit zal ons werk gemakkelijker maken.
De native applicatie eindigt met een functieaanroep omdat we eenvoudigweg ons eigen proces om zeep helpen.
Alle code voor onze kleine applicatie:
#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 kunnen eenvoudig de functie DbgBreakPoint() in onze code gebruiken om deze in de debugger te stoppen. Het is waar dat u WinDbg moet verbinden met een virtuele machine voor het debuggen van de kernel. Instructies over hoe u dit kunt doen, vindt u of gewoon gebruiken .
Compilatie en montage
De eenvoudigste manier om een native applicatie te bouwen is door gebruik te maken van (Driverontwikkelingskit). We hebben de oude zevende versie nodig, omdat latere versies een iets andere aanpak hebben en nauw samenwerken met Visual Studio. Als we de DDK gebruiken, heeft ons project alleen Makefile en bronnen nodig.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defbronnen:
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 = 1Je Makefile zal precies hetzelfde zijn, maar laten we de bronnen wat gedetailleerder bekijken. Dit bestand specificeert de bronnen van uw programma (.c-bestanden), build-opties en andere parameters.
- TARGETNAME – de naam van het uitvoerbare bestand dat uiteindelijk moet worden geproduceerd.
- TARGETTYPE – type uitvoerbaar bestand, het kan een stuurprogramma (.sys) zijn, dan moet de veldwaarde DRIVER zijn, als het een bibliotheek (.lib) is, dan is de waarde LIBRARY. In ons geval hebben we een uitvoerbaar bestand (.exe) nodig, dus stellen we de waarde in op PROGRAM.
- UMTYPE – mogelijke waarden voor dit veld: console voor een consoletoepassing, vensters voor werken in venstermodus. Maar we moeten nt specificeren om een native applicatie te krijgen.
- BUFFER_OVERFLOW_CHECKS – de stapel controleren op bufferoverflow, helaas niet ons geval, we zetten het uit.
- MINWIN_SDK_LIB_PATH – deze waarde verwijst naar de SDK_LIB_PATH variabele. Maak je geen zorgen dat je zo’n systeemvariabele niet hebt gedeclareerd. Wanneer we een gecontroleerde build uitvoeren vanuit de DDK, wordt deze variabele gedeclareerd en verwijst deze naar de benodigde bibliotheken.
- BRONNEN – een lijst met bronnen voor uw programma.
- INCLUSIEF – headerbestanden die nodig zijn voor montage. Hier geven ze meestal het pad aan naar de bestanden die bij de DDK worden geleverd, maar u kunt ook eventuele andere opgeven.
- TARGETLIBS – lijst met bibliotheken die moeten worden gekoppeld.
- USE_NTDLL is een verplicht veld dat om voor de hand liggende redenen op 1 moet worden ingesteld.
- USER_C_FLAGS – alle vlaggen die u kunt gebruiken in preprocessor-richtlijnen bij het voorbereiden van applicatiecode.
Om te bouwen moeten we dus x86 (of x64) Checked Build uitvoeren, de werkmap wijzigen in de projectmap en de opdracht Build uitvoeren. Het resultaat in de schermafbeelding laat zien dat we één uitvoerbaar bestand hebben.

Dit bestand kan niet zo gemakkelijk worden gestart, het systeem vloekt en stuurt ons om na te denken over zijn gedrag met de volgende foutmelding:

Hoe start u een native applicatie?
Wanneer autochk start, wordt de opstartvolgorde van programma's bepaald door de waarde van de registersleutel:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteDe sessiemanager voert programma's uit deze lijst één voor één uit. De sessiebeheerder zoekt zelf naar de uitvoerbare bestanden in de map system32. Het formaat van de registersleutelwaarde is als volgt:
autocheck autochk *MyNativeDe waarde moet een hexadecimaal formaat hebben, niet de gebruikelijke ASCII, dus de hierboven getoonde sleutel heeft het formaat:
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,00Om de titel te converteren, kunt u bijvoorbeeld een online service gebruiken: .

Het blijkt dat we voor het starten van een native applicatie het volgende nodig hebben:
- Kopieer het uitvoerbare bestand naar de map system32
- Voeg een sleutel toe aan het register
- Start de machine opnieuw op
Voor het gemak is hier een kant-en-klaar script voor het installeren van een native applicatie:
install.bat
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pausevoeg.reg toe
REGEDIT4
[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession 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,00Na installatie en opnieuw opstarten, nog voordat het gebruikersselectiescherm verschijnt, krijgen we het volgende beeld:

Totaal
На примере вот такого маленького приложения мы убедились, что запустить приложение на уровне Windows Native вполне возможно. Дальше мы с ребятами из Университета Иннополис продолжим строить сервис, который будет инициировать процесс взаимодействия с драйвером намного раньше, чем в предыдущей версии нашего проекта. А с появлением оболочки win32 логично будет передать управление полноценному сервису, который уже был разработан (об этом подробнее ).
In het volgende artikel zullen we ingaan op een ander onderdeel van de Active Restore-service, namelijk het UEFI-stuurprogramma. Abonneer je op onze blog, zodat je het volgende bericht niet mist.
Bron: www.habr.com
