Windows Native Applications eta Acronis Active Restore zerbitzua

Gaur, Innopolis Unibertsitateko mutilekin batera, Active Restore teknologia garatzen ari garen istorioari jarraitzen diogu, erabiltzaileak hutsegite baten ondoren bere makinan ahalik eta azkarren lanean hasteko. Windows jatorrizko aplikazioei buruz hitz egingo dugu, haien sorrera eta abiarazteko ezaugarriak barne. Ebakiaren azpian gure proiektuari buruzko apur bat dago, baita jatorrizko aplikazioak idazteko gida praktiko bat ere.

Windows Native Applications eta Acronis Active Restore zerbitzua

Aurreko mezuetan dagoeneko hitz egin dugu zer den Berreskuratze aktiboa, eta Innopoliseko ikasleak nola garatzen diren zerbitzu. Gaur natiboko aplikazioetan zentratu nahi dut, gure berreskuratze-zerbitzu aktiboa "lurperatu" nahi dugun mailan. Dena ondo funtzionatzen badu, gai izango gara:

  • Abiarazi zerbitzua bera askoz lehenago
  • Jarri harremanetan babeskopia dagoen hodeiarekin askoz lehenago
  • Askoz lehenago sistema zein modutan dagoen ulertzeko - abiarazte edo berreskuratze normala
  • Askoz fitxategi gutxiago aldez aurretik berreskuratzeko
  • Eman erabiltzaileari are azkarrago hasteko.

Nolanahi ere, zer da jatorrizko aplikazioa?

Galdera honi erantzuteko, ikus dezagun sistemak egiten dituen deien sekuentzia, adibidez, bere aplikazioko programatzaile bat fitxategi bat sortzen saiatzen bada.

Windows Native Applications eta Acronis Active Restore zerbitzua
Pavel Yosifovich - Windows Kernel Programazioa (2019)

Programatzaileak funtzioa erabiltzen du SortuFitxategia, fileapi.h goiburuko fitxategian deklaratua eta Kernel32.dll-en inplementatzen dena. Hala ere, funtzio honek berak ez du fitxategia sortzen, sarrerako argumentuak soilik egiaztatzen ditu eta funtzioari dei egiten dio NtCreateFile (Nt aurrizkiak funtzioa berezkoa dela adierazten du). Funtzio hau winternl.h goiburuko fitxategian deklaratzen da eta ntdll.dll-en inplementatzen da. Espazio nuklearrera jauzi egiteko prestatzen da, eta ondoren sistema-dei bat egiten du fitxategi bat sortzeko. Kasu honetan, Kernel32 Ntdll-ren bilgarri bat besterik ez dela ematen du. Hori egin izanaren arrazoietako bat Microsoft-ek berezko munduaren funtzioak aldatzeko gaitasuna duela da, baina ez interfaze estandarrak ukitzeko. Microsoft-ek ez du gomendatzen jatorrizko funtzioei zuzenean deitzea eta ez ditu horietako gehienak dokumentatzen. Bide batez, dokumenturik gabeko funtzioak aurki daitezke Hemen.

Aplikazio natiboen abantaila nagusia da ntdll sisteman kernel32 baino askoz lehenago kargatzen dela. Hau logikoa da, kernel32-k ntdll behar duelako funtzionatzeko. Ondorioz, funtzio natiboak erabiltzen dituzten aplikazioak askoz lehenago has daitezke lanean.

Horrela, Windows Native Applications Windows abiaraztean goiz hasi daitezkeen programak dira. ntdll-ko funtzioak BAKARRIK erabiltzen dituzte. Aplikazio horren adibide bat: autok egiten duena chkdisk erabilgarritasuna zerbitzu nagusiak hasi aurretik diskoan akatsik dagoen ikusteko. Hauxe da gure Berrezartze Aktiboa izatea nahi dugun maila.

Zer behar dugu?

  • DDK (Driver Development Kit), gaur egun WDK 7 (Windows Driver Kit) izenez ere ezaguna.
  • Makina birtuala (adibidez, Windows 7 x64)
  • Ez da beharrezkoa, baina deskargatu daitezkeen goiburuko fitxategiak lagun dezakete Hemen

Zer dago kodean?

Landu dezagun pixka bat eta, adibidez, idatzi aplikazio txiki bat:

  1. Mezu bat bistaratzen du pantailan
  2. Memoria pixka bat esleitzen du
  3. Teklatuaren sarreraren zain dago
  4. Erabilitako memoria askatzen du

Aplikazio natiboetan, sarrera-puntua ez da main edo winmain, NtProcessStartup funtzioa baizik, benetan prozesu berriak zuzenean abiarazten baititugu sisteman.

Has gaitezen mezu bat pantailan bistaratzen. Horretarako jatorrizko funtzioa dugu NtDisplayString, UNICODE_STRING egitura-objektu baten erakuslea argumentu gisa hartzen duena. RtlInitUnicodeString-ek hasieratzen lagunduko digu. Ondorioz, pantailan testua bistaratzeko funtzio txiki hau idatz dezakegu:

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

ntdll-ren funtzioak bakarrik eskuragarri ditugunez eta memorian oraindik beste liburutegirik ez dagoenez, zalantzarik gabe, arazoak izango ditugu memoria nola esleitu. Operadore berria oraindik ez da existitzen (C++-ren maila altuko mundutik datorrelako), eta ez dago malloc funtziorik (exekutatzeko C liburutegiak behar ditu). Jakina, pila bat bakarrik erabil dezakezu. Baina memoria dinamikoki esleitu behar badugu, heap-ean (hau da, heap) egin beharko dugu. Beraz, sor ditzagun geure buruari pila bat eta har diezaiogun memoria behar dugunean.

Funtzioa egokia da zeregin honetarako RtlCreateHeap. Ondoren, RtlAllocateHeap eta RtlFreeHeap erabiliz, behar dugunean memoria okupatu eta askatuko dugu.

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);

Goazen teklatuaren sarreraren zain.

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

Behar dugun guztia erabiltzea da NtReadFile irekita dagoen gailu batean, eta itxaron teklatuak edozein sakatu guri itzuli arte. ESC tekla sakatzen bada, lanean jarraituko dugu. Gailua irekitzeko, NtCreateFile funtziora deitu beharko dugu (DeviceKeyboardClass0 ireki beharko dugu). Guk ere deituko dugu NtCreateEventitxaron objektua hasieratzeko. KEYBOARD_INPUT_DATA egitura geuk deklaratuko dugu, teklatuaren datuak adierazten dituena. Honek gure lana erraztuko du.

Jatorrizko aplikazioa funtzio-dei batekin amaitzen da NtTerminateProcessgure prozesua hiltzen ari garelako.

Gure aplikazio txikirako kode guztia:

#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: DbgBreakPoint() funtzioa erraz erabil dezakegu gure kodean araztean gelditzeko. Egia da, WinDbg makina birtual batera konektatu beharko duzu nukleoa arazketa egiteko. Hau egiteko argibideak aurki daitezke Hemen edo besterik gabe erabili VirtualKD.

Konpilazioa eta muntaketa

Aplikazio natibo bat eraikitzeko modurik errazena erabiltzea da DDK (Gidaria Garatzeko Kita). Antzinako zazpigarren bertsioa behar dugu, geroko bertsioek ikuspegi apur bat desberdina baitute eta Visual Studio-rekin estuki lan egiten dute. DDK erabiltzen badugu, gure proiektuak Makefile eta iturriak baino ez ditu behar.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

iturriak:

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

Zure Makefile berdina izango da, baina ikus ditzagun iturriak xehetasun apur bat gehiagorekin. Fitxategi honek zure programaren iturriak (.c fitxategiak), eraikitzeko aukerak eta beste parametro batzuk zehazten ditu.

  • TARGETNAME - amaieran sortu behar den fitxategi exekutagarriaren izena.
  • TARGETTYPE - fitxategi exekutagarri mota, kontrolatzailea (.sys) izan daiteke, orduan eremuaren balioa DRIVER izan behar da, liburutegia (.lib) bada, orduan balioa LIBRARY da. Gure kasuan, fitxategi exekutagarri bat (.exe) behar dugu, beraz, balioa PROGRAM-en ezarriko dugu.
  • UMTYPE - eremu honetako balio posibleak: kontsola aplikazio baterako kontsola, leiho moduan lan egiteko leihoak. Baina nt zehaztu behar dugu jatorrizko aplikazio bat lortzeko.
  • BUFFER_OVERFLOW_CHECKS - pila bat buffer gainezka dagoen egiaztatzen, zoritxarrez ez da gure kasua, desaktibatu egiten dugu.
  • MINWIN_SDK_LIB_PATH - balio honek SDK_LIB_PATH aldagaiari egiten dio erreferentzia, ez kezkatu sistema-aldagairik deklaratu ez duzulako, DDK-tik checked build exekutatzen dugunean, aldagai hau deklaratuko da eta beharrezko liburutegietara zuzenduko da.
  • ITURRIAK – zure programaren iturrien zerrenda.
  • BARNE - muntatzeko beharrezkoak diren goiburuko fitxategiak. Hemen normalean DDKrekin datozen fitxategien bidea adierazten dute, baina beste edozein ere zehaztu dezakezu.
  • TARGETLIBS - lotu behar diren liburutegien zerrenda.
  • USE_NTDLL derrigorrezko eremu bat da, eta 1ean ezarri behar da arrazoi argiengatik.
  • USER_C_FLAGS - Aplikazio-kodea prestatzerakoan aurreprozesadorearen zuzentarauetan erabil ditzakezun banderak.

Beraz, eraikitzeko, x86 (edo x64) Checked Build exekutatu behar dugu, lan-direktorioa proiektuaren karpetara aldatu eta Eraiki komandoa exekutatu. Pantaila-argazkiaren emaitzak fitxategi exekutagarri bat dugula erakusten du.

Windows Native Applications eta Acronis Active Restore zerbitzua

Fitxategi hau ezin da hain erraz abiarazi, sistemak madarikatu egiten du eta bere portaeraz pentsatzera bidaltzen gaitu errore honekin:

Windows Native Applications eta Acronis Active Restore zerbitzua

Nola abiarazi jatorrizko aplikazio bat?

Autochk abiaraztean, programen abiarazte-sekuentzia erregistro-gakoaren balioaren arabera zehazten da:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Saio-kudeatzaileak zerrenda honetako programak exekutatzen ditu banan-banan. Saio-kudeatzaileak fitxategi exekutagarriak berak bilatzen ditu system32 direktorioan. Erregistroko gako-balioaren formatua hau da:

autocheck autochk *MyNative

Balioak hamaseitar formatuan egon behar du, ez ohiko ASCII, beraz goian erakusten den gakoa formatuan egongo da:

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

Izenburua bihurtzeko, lineako zerbitzu bat erabil dezakezu, adibidez, hau.

Windows Native Applications eta Acronis Active Restore zerbitzua
Aplikazio natibo bat abiarazteko, hauxe behar dugu:

  1. Kopiatu fitxategi exekutagarria system32 karpetara
  2. Gehitu gako bat erregistroan
  3. Berrabiarazi makina

Erosotasunerako, hona hemen jatorrizko aplikazio bat instalatzeko prest egindako script bat:

install.bat

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

gehitu.erreg

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

Instalatu eta berrabiarazi ondoren, erabiltzailea hautatzeko pantaila agertu baino lehen ere, argazki hau jasoko dugu:

Windows Native Applications eta Acronis Active Restore zerbitzua

Guztira

Aplikazio txiki horren adibidea erabiliz, aplikazioa Windows Native mailan exekutatzeko nahiko posiblea dela sinetsita geunden. Jarraian, Innopolis Unibertsitateko mutilek eta biok gure proiektuaren aurreko bertsioan baino askoz lehenago gidariaren interakzio prozesuari hasiera emango dion zerbitzu bat eraikitzen jarraituko dugu. Eta win32 shell-aren etorrerarekin, logikoa litzateke kontrola dagoeneko garatuta dagoen zerbitzu oso batera transferitzea (honi buruz gehiago Hemen).

Hurrengo artikuluan Active Restore zerbitzuaren beste osagai bat ukituko dugu, hots, UEFI kontrolatzailea. Harpidetu gure blogera hurrengo mezua ez galtzeko.

Iturria: www.habr.com

Gehitu iruzkin berria