Windows Native Applications un Acronis Active Restore pakalpojums

Šodien turpinām stāstu par to, kā kopā ar puišiem no Innopolis universitātes izstrādājam tehnoloģiju Active Restore, lai ļautu lietotājam pēc iespējas ātrāk atsākt darbu savā datorā pēc avārijas. Mēs runāsim par Windows lietotnēm, tostarp to izstrādes un palaišanas īpatnībām. Zemāk ir nedaudz informācijas par mūsu projektu, kā arī praktisks ceļvedis, kā rakstīt nativās lietotnes.

Windows Native Applications un Acronis Active Restore pakalpojums

Iepriekšējās ziņās mēs jau runājām par to, kas ir Active Restore, un kā studenti no Innopolis to izstrādā. pakalpojumsŠodien es vēlos pievērst uzmanību nativām lietotnēm, līdz kurām mēs vēlamies "iestrādāt" mūsu aktīvās atjaunošanas pakalpojumu. Ja viss izdosies, tad mēs spēsim:

  • Ieslēgt pašu pakalpojumu daudz agrāk
  • Šķirties no mākonis, kurā atrodas dublējums, daudz agrāk
  • Saprast, kādā režīmā atrodas sistēma – normālā palaišanā vai atjaunošanā, daudz agrāk
  • Atjaunot daudz mazāk failu iepriekš
  • Ļaut lietotājam ātrāk uzsākt darbu.

Kas vispār ir nativa lietotne?

Lai atbildētu uz šo jautājumu, aplūkosim izsaukumu virkni, ko veic sistēma, piemēram, ja programmētājs savā lietotnē mēģina izveidot failu.

Windows Native Applications un Acronis Active Restore pakalpojums
Pavel Yosifovich — Windows Kernel Programming (2019)

Programmētājs izmanto funkciju CreateFile, kas ir definēta galvenes failā fileapi.h un realizēta Kernel32.dll. Tomēr šī funkcija neizveido failu, tā vienkārši pārbauda izejas argumentus un izsauc funkciju NtCreateFile (prefikss Nt tieši norāda, ka funkcija ir nativa). Šī funkcija ir definēta galvenes failā winternl.h un realizēta ntdll.dll. Tā veic sagatavošanos lekt kodola telpā, pēc kā tiek veikts sistēmas izsaukums, lai izveidotu failu. Tādējādi izrādās, ka Kernel32 ir tikai apvalks ap Ntdll. Viens no iemesliem, kāpēc tā tas tiek darīts, Microsoft tādejādi var izmainīt nativā pasaulē esošās funkcijas, neietekmējot standarta saskarnes. Microsoft neiesaka tieši izsaukt nativās funkcijas un nenorāda lielāko daļu no tām. Starp citu, nepublicētās funkcijas var atrast. šeit.

Galvenā noderīgo lietotņu priekšrocība ir tā, ka ntdll tiek ielādēta sistēmā ievērojami ātrāk nekā kernel32. Tas ir loģiski, jo kernel32 prasa ntdll darbībai. Tādēļ lietotnes, kas izmanto noderīgas funkcijas, var uzsākt darbību ievērojami agrāk.

Tādējādi, Windows Native Applications ir programmas, kas spēj tikt uzsāktas agrīnā Windows ielādes posmā. Tās izmanto TIKAI funkcijas no ntdll. Šādas lietotnes piemērs ir: autochk , kas izpilda chkdisk utility disku pārbaudei uz kļūdām vēl pirms galveno pakalpojumu palaišanas. Tieši šādā līmenī mēs vēlamies redzēt mūsu Active Restore.

Kas mums būs nepieciešams?

  • DDK (Driver Development Kit), tagad pazīstams arī kā WDK 7 (Windows Driver Kit).
  • Virtuālā mašīna (piemēram, Windows 7 x64)
  • Tas nav obligāti, bet var palīdzēt virsrakstu faili, kurus var lejupielādēt. šeit

Kas tad kodā?

Sāksim nedaudz trenēties un kā piemēru uzrakstīsim nelielu lietotni, kas:

  1. Izejas ziņu ekrānā
  2. Alocē nedaudz atmiņas
  3. Gaida ievadi no tastatūras
  4. Atbrīvo aizņemto atmiņu

Noderīgajās lietotnēs ieejas punkts nav mainīgais, vai winmain, bet gan funkcija NtProcessStartup, jo mēs faktiski tieši uzsākam jaunu procesu sistēmā.

Sāksim ar ziņas parādīšanu ekrānā. Tam mēs izmantojam noderīgo funkciju NtDisplayString, kas kā argumentu pieņem rādītāju uz UNICODE_STRING struktūras objektu. To mēs varam inicializēt, izmantojot RtlInitUnicodeString. Rezultātā, ziņas izvadīšanai uz ekrāna varam uzrakstīt šādu nelielu funkciju:

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

Tā kā mums ir pieejamas tikai funkcijas no ntdll, un citu bibliotēku atmiņā vienkārši vēl nav, mums neizbēgami radīsies problēmas ar atmiņas alocēšanu. Jaunā operators vēl nav pieejams (jo tas nāk no pārāk augsta līmeņa C++ pasaules), kā arī nav funkcijas malloc (tai nepieciešamas C runtime bibliotēkas). Protams, varam izmantot tikai steku. Bet, ja mums ir nepieciešams dinamiski alocēt atmiņu, būs jādara to kaudzē (t.i., heap). Tāpēc šai vajadzībai izveidosim kaudzi un ņemsim no tās atmiņu, kad tas būs nepieciešams.

Šai uzdevumam derēs funkcija RtlCreateHeap. Pēc tam, izmantojot RtlAllocateHeap un RtlFreeHeap, mēs būs brīvi izmantojuši un atbrīvojuši atmiņu, kad tas būs nepieciešams.

PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;

// izveidojiet kaudzi, lai vēlāk piešķirtu atmiņu
memory = RtlCreateHeap(
  HEAP_GROWABLE, 
  NULL, 
  1000, 
  0, NULL, NULL
);

// piešķiriet buferi ar izmēru bufferSize
buffer = RtlAllocateHeap(
  memory, 
  HEAP_ZERO_MEMORY, 
  bufferSize
);

// atbrīvo buferi (faktiski tas nav nepieciešams, jo nākamajā solī mēs iznīcinām kaudzi)
RtlFreeHeap(memory, 0, buffer);

RtlDestroyHeap(memory);

Pāriesim pie ievades gaidīšanas no tastatūras.

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

Viss, kas mums nepieciešams, – ir izmantot NtReadFile atvērtajā ierīcē un gaidīt, kamēr tastatūra atgriezīs kādu nospiešanu. Ja tiek nospiesta taustiņš ESC, mēs turpināsim darbu. Lai atvērtu ierīci, mums būs jāizsauc funkcija NtCreateFile (jāatver būs DeviceKeyboardClass0). Mēs arī izsauksim NtCreateEvent, lai inicializētu gaidīšanas objektu. Mēs patstāvīgi paziņosim struktūru KEYBOARD_INPUT_DATA, kas attēlo tastatūras datus. Tas atvieglos mūsu darbu.

Natīvas aplikācijas darbība tiek pabeigta, izsaucot funkciju NtTerminateProcess, jo mēs vienkārši nogalina savu pašu procesu.

Visu mūsu nelielās aplikācijas kodu:

#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: Mēs varam viegli izmantot funkciju DbgBreakPoint() kodā, lai apstātos debuggēšanā. Patiesībā jābūt pievienotam WinDbg virtuaļai mašīnai, lai veiktu kernela debuggēšanu. Instrukciju kā to izdarīt var atrast šeit vai vienkārši izmantot VirtualKD.

Kompilācija un salikšana

Vienkāršākais veids, kā savākt natīvu aplikāciju, ir izmantot DDK (Driver Development Kit). Mums nepieciešama tieši senā septītā versija, jo vēlākās versijas ir nedaudz citādas un cieši strādā ar Visual Studio. Ja izmantosim DDK, tad mūsu projektam būs nepieciešami tikai Makefile un sources.

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

Jūsu Makefile būs tieši tāds pats, par sources mazliet detalizētāk apstāsimies. Šajā failā norādīti jūsu programmas avoti (faili .c), kompilācijas opcijas un citi parametri.

  • TARGETNAME – izpildāmā faila nosaukums, kas beigās jāiegūst.
  • TARGETTYPE – izpildāmā faila tips, tas var būt draiveris (.sys), tad lauka vērtībai jābūt DRIVER, ja bibliotēka (.lib), tad vērtība LIBRARY. Mūsu gadījumā nepieciešams izpildāmais fails (.exe), tādēļ mēs iestatām vērtību PROGRAM.
  • UMTYPE – iespējamās vērtības šim laukam: console konsoles lietojumprogrammām, windows darbam logu režīmā. Taču mums jānorāda nt, lai iegūtu nativu lietojumprogrammu.
  • BUFFER_OVERFLOW_CHECKS – steka pārbaude uz buferu pārsniegšanu, diemžēl tas nav mūsu gadījums, izslēdzam.
  • MINWIN_SDK_LIB_PATH – šī vērtība attiecas uz mainīgo SDK_LIB_PATH, nav jāuztraucas, ka jums nav deklarētas šādas sistēmas mainīgās, kad mēs palaidīsim checked build no DDK, šī mainīgā tiks deklarēta un norādīs uz nepieciešamajām bibliotēkām.
  • SOURCES – jūsu programmas avotu saraksts.
  • INCLUDES – galvene faili, kas nepieciešami kompilācijai. Šeit parasti norāda ceļu uz failiem, kas nāk komplektā ar DDK, taču jūs varat norādīt arī jebkura cita papildu faila ceļu.
  • TARGETLIBS – bibliotēku saraksts, kas jāpievieno.
  • USE_NTDLL – obligāts lauks, kas jāiestata uz 1. Pamatota iemesla dēļ.
  • USER_C_FLAGS – jebkuri karogus, kurus varat izmantot priekšapstrādes direktīvās, gatavojot lietojumprogrammas kodu.

Tādēļ, lai veiktu kompilāciju, mums jāpalaiž x86 (vai x64) Checked Build, jāmaina darba katalogs uz projekta mapi un jāizpilda komanda Build. Rezultāts ekrānšāviņā parāda, ka esam izveidojuši vienu izpildāmo failu.

Windows Native Applications un Acronis Active Restore pakalpojums

Šo failu nebūs viegli palaist, sistēma sūdzas un nosūta mūs apsvērt tās uzvedību ar nākamo kļūdu:

Windows Native Applications un Acronis Active Restore pakalpojums

Kā palaist nativu lietojumprogrammu?

Palaides brīdī autochk programmu palaišanas secību nosaka reģistra atslēgas vērtība:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Sesijas menedžeris secīgi izpilda programmas no šī saraksta. Paši izpildāmie faili sesijas menedžeris meklē direkotorijā system32. Reģistra atslēgas vērtības formāts ir šāds:

autocheck autochk *MyNative

Vērtībai jābūt heksadecimālā formātā un nevis ierastajā ASCII, līdz ar to iepriekš minētā atslēga būs formātā:

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

Lai konvertētu nosaukumu, varat izmantot tiešsaistes pakalpojumu, piemēram, šis.

Windows Native Applications un Acronis Active Restore pakalpojums
Tādējādi, lai palaistu nativu lietojumprogrammu, mums ir jā:

  1. Kopējiet izpildāmo failu uz mapi system32
  2. Pievienojiet reģistram atslēgu
  3. Restartējiet mašīnu

Lai atvieglotu, šeit jums ir gatavs skripts nativas lietojumprogrammas uzstādīšanai:

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

Pēc instalēšanas un restartēšanas pirms lietotāju izvēles ekrāna mēs iegūsim šādu ainu:

Windows Native Applications un Acronis Active Restore pakalpojums

Rezultāts

Piemērā ar šādu mazu lietotni mēs pārliecinājāmies, ka palaist lietotni Windows Native līmenī ir pilnīgi iespējams. Turpmāk mēs kopā ar Innopolis Universitātes puišiem turpināsim veidot pakalpojumu, kas iniciēs mijiedarbības procesu ar vadītāju daudz agrāk, nekā iepriekšējā mūsu projekta versijā. Un ar win32 saskarnes parādīšanos ir loģiski nodot kontroli pilnvērtīgam pakalpojumam, kas jau ir izstrādāts (par to sīkāk šeit).

Nākamajā rakstā mēs pievērsīsimies vēl vienam Active Restore pakalpojuma komponentam, proti, UEFI vadītājam. Sekojiet mūsu emuāram, lai nepalaistu garām nākamo ierakstu.

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster