Windows Native Applications en Acronis Active Restore-service

Vandaag vervolgen we het verhaal van hoe we, samen met de jongens van Innopolis University, Active Restore-technologie ontwikkelen zodat de gebruiker na een storing zo snel mogelijk aan zijn machine kan gaan werken. We zullen het hebben over native Windows-applicaties, inclusief de kenmerken van hun creatie en lancering. Hieronder vindt u iets over ons project, evenals een praktische gids over het schrijven van native applicaties.

Windows Native Applications en Acronis Active Restore-service

In eerdere berichten hebben we al verteld wat het is Actief herstel, en hoe studenten van Innopolis zich ontwikkelen service. 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.

Windows Native Applications en Acronis Active Restore-service
Pavel Yosifovich - Windows Kernel-programmering (2019)

De programmeur gebruikt de functie Bestand maken, 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 NtCreateFile (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 hier.

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.

Windows Native Applications zijn dus programma's die al vroeg tijdens het opstarten van Windows kunnen starten. Ze gebruiken ALLEEN functies van ntdll. Een voorbeeld van zo’n toepassing: autochk wie presteert chkdisk-hulpprogramma 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?

  • DDK (Driver Development Kit), nu ook bekend als WDK 7 (Windows Driver Kit).
  • Virtuele machine (bijvoorbeeld Windows 7 x64)
  • Niet nodig, maar headerbestanden die kunnen worden gedownload kunnen helpen hier

Wat staat er in de code?

Laten we een beetje oefenen en bijvoorbeeld een kleine applicatie schrijven die:

  1. Geeft een bericht weer op het scherm
  2. Wijst wat geheugen toe
  3. Wacht op toetsenbordinvoer
  4. 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 NtDisplayString, 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 RtlCreateHeap. 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 NtReadFile 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 NtCreateEventom 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 NtTerminateProcesomdat 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 hier of gewoon gebruiken VirtueleKD.

Compilatie en montage

De eenvoudigste manier om een ​​native applicatie te bouwen is door gebruik te maken van DDK (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.def

bronnen:

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			= 1

Je 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.

Windows Native Applications en Acronis Active Restore-service

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:

Windows Native Applications en Acronis Active Restore-service

Hoe start u een native applicatie?

Wanneer autochk start, wordt de opstartvolgorde van programma's bepaald door de waarde van de registersleutel:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

De 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 *MyNative

De 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,00

Om de titel te converteren, kunt u bijvoorbeeld een online service gebruiken: deze.

Windows Native Applications en Acronis Active Restore-service
Het blijkt dat we voor het starten van een native applicatie het volgende nodig hebben:

  1. Kopieer het uitvoerbare bestand naar de map system32
  2. Voeg een sleutel toe aan het register
  3. 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
pause

add.reg

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,00

Na installatie en opnieuw opstarten, nog voordat het gebruikersselectiescherm verschijnt, krijgen we het volgende beeld:

Windows Native Applications en Acronis Active Restore-service

Totaal

Aan de hand van het voorbeeld van zo'n kleine applicatie waren we ervan overtuigd dat het heel goed mogelijk is om de applicatie op Windows Native-niveau te draaien. Vervolgens zullen de jongens van Innopolis University en ik doorgaan met het bouwen van een service die het interactieproces met de bestuurder veel eerder zal initiëren dan in de vorige versie van ons project. En met de komst van de win32-shell zou het logisch zijn om de controle over te dragen aan een volwaardige service die al is ontwikkeld (meer hierover hier).

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

Voeg een reactie