Windows Native Applications och tjÀnsten Acronis Active Restore.

Idag fortsĂ€tter vi berĂ€ttelsen om hur vi tillsammans med killarna frĂ„n Innopolis universitet utvecklar teknologin Active Restore för att lĂ„ta anvĂ€ndaren börja arbeta pĂ„ sin maskin sĂ„ tidigt som möjligt efter ett fel. Det handlar om inhemska Windows-appar, inklusive egenskaperna för deras skapande och lansering. Under tryck – lite om vĂ„rt projekt samt en praktisk guide för hur man skriver inhemska appar.

Windows Native Applications och tjÀnsten Acronis Active Restore.

I tidigare inlÀgg har vi redan berÀttat om vad Active RestoreÀr, och hur studenter frÄn Innopolis utvecklar tjÀnst. Idag vill jag fokusera pÄ inhemska appar, till den nivÄ vi vill "grÀva ner" vÄr aktiva ÄterstÀllningstjÀnst. Om allt gÄr som planerat, kan vi:

  • Starta tjĂ€nsten mycket tidigare
  • Kontakta molnet dĂ€r backupen ligger mycket tidigare
  • FörstĂ„ mycket tidigare vilket lĂ€ge systemet befinner sig i – normalt lĂ€ge eller Ă„terstĂ€llningslĂ€ge
  • ÅterstĂ€lla mycket fĂ€rre filer i förvĂ€g
  • LĂ„ta anvĂ€ndaren börja arbeta Ă€nnu snabbare.

Vad Àr egentligen en inhemsk app?

För att svara pÄ denna frÄga, lÄt oss titta pÄ sekvensen av anrop som systemet gör, till exempel nÀr en programmerare i sin app försöker skapa en fil.

Windows Native Applications och tjÀnsten Acronis Active Restore.
Pavel Yosifovich — Windows Kernel Programming (2019)

Programmeraren anvÀnder funktionen CreateFile, som deklareras i header-filen fileapi.h och implementeras i Kernel32.dll. Men sjÀlva denna funktion sysslar inte med att skapa en fil, den kontrollerar bara argumenten in och anropar funktionen NtCreateFile (prefixet Nt indikerar just att funktionen Àr inhemsk). Denna funktion Àr deklarerad i header-filen winternl.h och implementeras i ntdll.dll. Den förbereder för hoppet in i kÀrnrymden, efter vilket den gör ett systemanrop för att skapa filen. I detta fall Àr Kernel32 bara en wrapper för Ntdll. En av anledningarna till varför detta görs Àr att Microsoft pÄ sÄ sÀtt kan Àndra funktioner i den inhemska vÀrlden utan att röra de standardiserade grÀnssnitten. Microsoft rekommenderar inte att man anropar inhemska funktioner direkt och dokumenterar inte merparten av dem. För övrigt kan man hitta odokumenterade funktioner. ). Ett typiskt exempel pÄ anvÀndning:.

Den största fördelen med inhemska applikationer Àr att ntdll laddas in i systemet betydligt tidigare Àn kernel32. Det Àr logiskt, eftersom kernel32 behöver ntdll för att fungera. Som en följd av detta kan applikationer som anvÀnder inhemska funktioner börja arbeta betydligt tidigare.

SÄledes Àr Windows Native Applications program som kan starta i ett tidigt skede av Windows-laddningen. De anvÀnder ENDAST funktioner frÄn ntdll. Ett exempel pÄ en sÄdan applikation: autochk som kör chkdisk utility för att kontrollera disken för fel redan innan de huvudtjÀnsterna startar. PÄ denna nivÄ vill vi verkligen se vÄr Active Restore.

Vad behöver vi?

  • DDK (Driver Development Kit), numera Ă€ven kĂ€nt som WDK 7 (Windows Driver Kit).
  • En virtuell maskin (till exempel Windows 7 x64)
  • Även om det inte Ă€r nödvĂ€ndigt kan headerfiler som kan laddas ner vara till hjĂ€lp ). Ett typiskt exempel pĂ„ anvĂ€ndning:

Vad sÀgs om koden?

LÄt oss trÀna lite och som exempel skriva en liten applikation som:

  1. Visar ett meddelande pÄ skÀrmen
  2. Allokerar lite minne
  3. VÀntar pÄ tangentbordsinmatning
  4. Frigör det upptagna minnet

I inhemska applikationer Àr inte ingÄngspunkten main eller winmain, utan funktionen NtProcessStartup, eftersom vi faktiskt startar nya processer direkt i systemet.

LÄt oss börja med att visa ett meddelande pÄ skÀrmen. För detta har vi en inhemsk funktion NtDisplayString, som tar en pekare till ett objekt av strukturen UNICODE_STRING som argument. Vi kan hjÀlpa till att initiera det med RtlInitUnicodeString. DÀrför kan vi skriva en liten funktion för att visa text pÄ skÀrmen:

//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
    UNICODE_STRING string;
    RtlInitUnicodeString(&string, Message);
    NtDisplayString(&string);
}

Eftersom vi endast har tillgÄng till funktioner frÄn ntdll, och inga andra bibliotek finns i minnet, kommer vi troligtvis att stöta pÄ problem med att allokera minne. Operatören new existerar Ànnu inte (eftersom den kommer frÄn en alltför hög nivÄ i C++), och det finns inte heller nÄgon malloc-funktion (som krÀver C-löptider). Vi kan förstÄs bara anvÀnda stacken. Men om vi behöver dynamiskt allokera minne mÄste vi göra det i heapen. DÀrför lÄt oss skapa en heap för oss sjÀlva och ta minne dÀrifrÄn nÀr vi behöver.

För denna uppgift passar funktionen RtlCreateHeap. DÀrefter, med hjÀlp av RtlAllocateHeap och RtlFreeHeap, kommer vi att anvÀnda och frigöra minnage nÀr vi behöver det.

PVOID minne = NULL;
PVOID buffert = NULL;
ULONG buffertStorlek = 42;

// skapa heap för att senare allokera minne
minne = RtlCreateHeap(
  HEAP_GROWABLE,
  NULL,
  1000,
  0, NULL, NULL
);

// allokera buffert av storlek buffertStorlek
buffert = RtlAllocateHeap(
  minne,
  HEAP_ZERO_MEMORY,
  buffertStorlek
);

// frigöra buffert (egentligen inte nödvÀndigt eftersom vi förstör heapen i nÀsta steg)
RtlFreeHeap(minne, 0, buffert);

RtlDestroyHeap(minne);

LÄt oss gÄ vidare till att vÀnta pÄ inmatning frÄn tangentbordet.

// 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;
	}
}

Allt vi behöver göra Àr att anvÀnda NtReadFile pÄ den öppna enheten och vÀnta tills tangentbordet returnerar ett tangenttryck. Om ESC-tangenten trycks ned, fortsÀtter vi arbetet. För att öppna enheten behöver vi kalla pÄ funktionen NtCreateFile (vi mÄste öppna DeviceKeyboardClass0). Vi kommer ocksÄ att kalla pÄ NtCreateEvent, för att initiera ett objekt för vÀntan. Vi kommer att deklarera strukturen KEYBOARD_INPUT_DATA, som representerar tangentbordsdata. Detta kommer att underlÀtta vÄrt arbete.

En nativen applikation avslutas med att kalla pÄ funktionen NtTerminateProcess, eftersom vi helt enkelt dödar vÄr egen process.

Hela koden för vÄr lilla applikation:

#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: Vi kan enkelt anvÀnda funktionen DbgBreakPoint() i koden för att stanna i debuggan. Men vi behöver koppla WinDbg till den virtuella maskinen för kernel-debugging. Instruktioner om hur man gör detta finns ). Ett typiskt exempel pÄ anvÀndning: eller helt enkelt anvÀnda VirtualKD.

Kompilering och byggande

Det enklaste sÀttet att bygga en nativen applikation Àr att anvÀnda DDK (Driver Development Kit). Vi behöver just den Àldre sjunde versionen, eftersom senare versioner har en nÄgot annan metod och arbetar nÀra med Visual Studio. Om vi anvÀnder DDK, behövs endast Makefile och sources för vÄrt projekt.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

sources:

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

Din Makefile kommer att vara exakt likadan, lÄt oss stanna lite lÀngre vid sources. I den hÀr filen anges kÀllorna till ditt program (filer .c), byggalternativ och andra parametrar.

  • TARGETNAME – namnet pĂ„ den körbara filen som ska skapas i slutĂ€ndan.
  • TARGETTYPE – typen av körbar fil, detta kan vara en drivrutin (.sys), dĂ„ bör vĂ€rdet vara DRIVER, om det Ă€r ett bibliotek (.lib), sĂ„ ska vĂ€rdet LIBRARY. I vĂ„rt fall behövs en körbar fil (.exe), sĂ„ vi sĂ€tter vĂ€rdet PROGRAM.
  • UMTYPE – möjliga vĂ€rden för detta fĂ€lt: console för konsolapplikation, windows för arbete i fönsterlĂ€ge. Men vi behöver ange nt för att fĂ„ en inbyggd applikation.
  • BUFFER_OVERFLOW_CHECKS – kontroll av stacken för buffertöverskridning, tyvĂ€rr Ă€r det inte vĂ„rt fall, vi stĂ€nger av detta.
  • MINWIN_SDK_LIB_PATH – detta vĂ€rde hĂ€nvisar till variabeln SDK_LIB_PATH, det Ă€r ingen anledning att oroa sig för att ni inte har sĂ„dan systemvariabel deklarerad, vid den tidpunkt vi kör checked build frĂ„n DDK, kommer denna variabel att deklareras och peka pĂ„ nödvĂ€ndiga bibliotek.
  • SOURCES – lista över kĂ€llor för ditt program.
  • INCLUDES – headerfiler som behövs för att bygga. HĂ€r anger man vanligtvis sökvĂ€gen till filer som följer med DDK, men du kan ocksĂ„ ange andra.
  • TARGETLIBS – lista över bibliotek som behöver lĂ€nkas.
  • USE_NTDLL – obligatoriskt fĂ€lt som mĂ„ste stĂ€llas in till 1. Av uppenbara skĂ€l.
  • USER_C_FLAGS – alla flaggor som du kan anvĂ€nda i preprocessordirektiv vid förberedelse av programkoden.

SÄ för att bygga behöver vi starta x86 (eller x64) Checked Build, byta arbetskatalog till mappen med projektet och utföra kommandot Build. Resultatet i skÀrmdumpen visar att vi har samlat en körbar fil.

Windows Native Applications och tjÀnsten Acronis Active Restore.

Denna fil gÄr inte att köra sÄ enkelt, systemet klagar och skickar oss att tÀnka pÄ sitt beteende med följande felmeddelande:

Windows Native Applications och tjÀnsten Acronis Active Restore.

Hur kör man en inbyggd applikation?

Vid start av autochk bestÀms sekvensen för programstart av vÀrdet i registreringsnyckeln:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Sessionshanteraren kör program frÄn denna lista i ordning. SjÀlva programfilerna letar sessionshanteraren efter i katalogen system32. Formatet för vÀrdet av registreringsnyckeln Àr följande:

autocheck autochk *MyNative

VÀrdet mÄste vara i hexadecimalt format, inte i det vanliga ASCII, dÀrmed kommer nyckeln ovan att ha formatet:

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

För att konvertera namnet kan man anvÀnda en online-tjÀnst, till exempel, det hÀr.

Windows Native Applications och tjÀnsten Acronis Active Restore.
SÄ för att köra en inbyggd applikation behövs:

  1. Kopiera den körbara filen till mappen system32
  2. LĂ€gg till en nyckel i registret
  3. Starta om maskinen

För bekvÀmlighetens skull har jag hÀr ett fÀrdigt skript för att installera den inbyggda applikationen:

install.bat

@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pause

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

Efter installationen och omstarten, innan anvÀndarvals skÀrmen dyker upp, kommer vi att se följande bild:

Windows Native Applications och tjÀnsten Acronis Active Restore.

Resultat

Med hjÀlp av en sÄ liten applikation har vi visat att det Àr fullt möjligt att köra en applikation pÄ Windows Native-nivÄ. HÀrnÀst kommer vi, tillsammans med killarna frÄn Innopolis universitet, att fortsÀtta bygga en tjÀnst som initierar interaktionen med drivrutinen mycket tidigare Àn i den tidigare versionen av vÄrt projekt. Med introduktionen av win32-grÀnssnittet kommer det logiskt att överlÀmna kontrollen till en fullfjÀdrad tjÀnst som redan har utvecklats (mer om detta lÀngre fram) hÀr).

I nÀsta artikel kommer vi att ta upp en annan komponent av Active Restore-tjÀnsten, nÀmligen UEFI-drivrutinen. Prenumerera pÄ vÄr blogg för att inte missa nÀsta inlÀgg.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster