Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore

Sot vazhdojmë tregimin për mënyrën se si ne, së bashku me djemtë nga Universiteti Innopolis, po zhvillojmë teknologjinë Active Restore për të lejuar përdoruesin të fillojë punën sa më shpejt që të jetë e mundur pas një dështimi. Do të flasim për aplikacionet native të Windows, duke përfshirë karakteristikat e ndërtimit dhe ekzekutimit të tyre. Nën kat - pak rreth projektit tonë, si dhe një udhëzues praktik për shkrimin e aplikacioneve native.

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore

NĂ« postimet e kaluara, ne kemi folur pĂ«r se çfarĂ« Ă«shtĂ« Active Restore, dhe si studentĂ«t nga Innopolis po zhvillojnĂ« shĂ«rbim. Sot dua tĂ« ndalem nĂ« aplikacionet native, nĂ« nivelin e tĂ« cilave dĂ«shirojmĂ« tĂ« “zhbim” shĂ«rbimin tonĂ« tĂ« rikthimit aktiv. NĂ«se gjithçka shkon mirĂ«, ne do tĂ« mundĂ«m:

  • TĂ« nisim shĂ«rbimin shumĂ« mĂ« herĂ«t
  • TĂ« lidhemi me cloud-in, ku ndodhet backup-i, shumĂ« mĂ« herĂ«t
  • TĂ« kuptojmĂ« shumĂ« mĂ« herĂ«t nĂ« cilin mod Ă«shtĂ« sistemi – ngarkesa normale apo rikuperimi
  • TĂ« rikuperojmĂ« shumĂ« mĂ« pak skedarĂ« paraprakisht
  • TĂ« lejojmĂ« pĂ«rdoruesin tĂ« fillojĂ« punĂ«n akoma mĂ« shpejt.

ÇfarĂ« Ă«shtĂ« nĂ« tĂ«rĂ«si njĂ« aplikacion native?

Për të përgjigjur në këtë pyetje, le të shikojmë sekuencën e thirrjeve që sistemi i bën, për shembull, nëse programuesi në aplikacionin e tij përpiqet të krijojë një skedar.

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore
Pavel Yosifovich — Programimi i Kernelit Windows (2019)

Programuesi përdor funksionin CreateFile, i cili shpallet në skedarin e përkufizimit fileapi.h dhe realizohet në Kernel32.dll. Megjithatë, ky funksion nuk merret me krijimin e skedarëve, ai vetëm kontrollon argumentet në hyrje dhe thërret funksionin NtCreateFile (prefiksi Nt tregon se funksioni është natyror). Ky funksion shpallet në skedarin e përkufizimit winternl.h dhe realizohet në ntdll.dll. Ai përgatit për një kërcim në hapësirën bërthamore, pas së cilës bën një thirrje sistemike për krijimin e skedarit. Në këtë rast, del se Kernel32 është vetëm një mbështjellëse për Ntdll. Një nga arsyet për këtë është se Microsoft ka mundësinë të modifikojë funksionet e botës natyrale pa prekur ndërfaqet standarde. Microsoft nuk rekomandon thirrjen e funksioneve natyrale direkt dhe nuk dokumenton shumicën e tyre. Dhe për diçka, funksionet e pa dokumentuara mund të gjenden. këtu.

Avantazhi kryesor i aplikacioneve natyrore është se ntdll ngarkohet në sistem shumë më herët se kernel32. Kjo ka kuptim, pasi kernel32 kërkon prania e ntdll për të funksionuar. Si pasojë, aplikacionet që përdorin funksione natyrore mund të fillojnë të punojnë shumë më herët.

KĂ«shtu, Aplikacionet Natyrore tĂ« Windows janĂ« programe qĂ« mund tĂ« ekzekutohen nĂ« fazĂ«n e hershme tĂ« ngarkimit tĂ« Windows. Ato pĂ«rdorin VETËM funksione nga ntdll. NjĂ« shembull i tillĂ« aplikacioni Ă«shtĂ«: autochk i cili ekzekuton chkdisk utility pĂ«r tĂ« kontrolluar disqet pĂ«r gabime akoma para se tĂ« fillojnĂ« shĂ«rbimet kryesore. NĂ« kĂ«tĂ« nivel dĂ«shirojmĂ« tĂ« shohim aktivimin tonĂ« tĂ« rikuperimit.

ÇfarĂ« na nevojitet?

  • DDK (Pako pĂ«r Zhvillimin e Drejtorive), tani gjithashtu e njohur si WDK 7 (Pako pĂ«r DrejtorĂ«t e Windows).
  • NjĂ« makinĂ« virtuale (p.sh., Windows 7 x64)
  • Nuk Ă«shtĂ« e domosdoshme, por mund tĂ« ndihmojnĂ« skedarĂ«t e titujve tĂ« cilat mund tĂ« shkarkohen. kĂ«tu

E çfarë ndodhi në kod?

Le të ushtrojmë pak dhe për shembull të shkruajmë një aplikacion të vogël që:

  1. Shfaq një mesazh në ekran
  2. Alokuan pak hapësirë në memorje
  3. Pret për hyrje nga tastiera
  4. Çliron hapĂ«sirĂ«n e zĂ«nĂ«

Në aplikacionet natyrore, pika e hyrjes nuk është main ose winmain, por funksioni NtProcessStartup, pasi ne përfaqësisht fillojmë një proces të ri direkt në sistem.

Le të fillojmë me shfaqjen e një mesazhi në ekran. Për këtë, kemi funksionin natyror NtDisplayString, i cili merr si argument një tregues në objektin e strukturës UNICODE_STRING. RtlInitUnicodeString do të na ndihmojë për ta inicializuar atë. Si rezultat, për të shfaqur tekstin në ekran, mund të shkruajmë këtë funksion të vogël:

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

Duke qenë se kemi qasje vetëm në funksionet nga ntdll dhe nuk ka bibliotekave të tjera në memorie, patjetër do të përballemi me probleme në alokimin e memories. Operatorët new nuk ekzistojnë akoma (sepse ata vijnë nga një botë shumë të lartë të C++), as ka funksionin malloc (për të cilin nevojiten bibliotekat runtime të C). Natyrisht, mund të përdorim vetëm stack-in. Por nëse na nevojitet të alokojmë dinamikisht memorie, do të duhet ta bëjmë këtë në heap. Prandaj, le të krijojmë një heap për veten tonë dhe të marrim memorie nga aty kur t'na nevojitet.

Për këtë detyrë do të përshtatet funksioni RtlCreateHeap. Më pas, duke përdorur RtlAllocateHeap dhe RtlFreeHeap, ne do të alokojmë dhe lirojmë memorien kur na nevojitet.

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

// krijo heap për të alokuar memorie më vonë
memory = RtlCreateHeap(
  HEAP_GROWABLE, 
  NULL, 
  1000, 
  0, NULL, NULL
);

// aloko buffer me madhësi bufferSize
buffer = RtlAllocateHeap(
  memory, 
  HEAP_ZERO_MEMORY, 
  bufferSize
);

// liro buffer (në të vërtetë nuk është e nevojshme sepse ne shkatërrojmë heap-in në hapin tjetër)
RtlFreeHeap(memory, 0, buffer);

RtlDestroyHeap(memory);

Të kalojmë në pritjen e hyrjes nga tastiera.

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

Gjithçka qĂ« na nevojitet – Ă«shtĂ« tĂ« pĂ«rdorim NtReadFile nĂ« pajisjen e hapur, dhe presim derisa tastiera tĂ« na kthejĂ« ndonjĂ« shtypje. NĂ« rast se shtypet çelĂ«si ESC, ne do tĂ« vazhdojmĂ« punĂ«n. PĂ«r tĂ« hapur pajisjen, na nevojitet tĂ« thĂ«rrasim funksionin NtCreateFile (duhet tĂ« hapim DeviceKeyboardClass0). PĂ«r mĂ« tepĂ«r, do tĂ« thĂ«rrasim NtCreateEvent, pĂ«r tĂ« inicializuar njĂ« objekt pĂ«r pritjen. Do tĂ« shpallim vetĂ« strukturĂ«n KEYBOARD_INPUT_DATA, e cila pĂ«rfaqĂ«son tĂ« dhĂ«nat e tastierĂ«s. Kjo do ta lehtĂ«sojĂ« punĂ«n tonĂ«.

Funksioni i aplikacionit natyror përfundohet me thirrjen e funksionit NtTerminateProcess, sepse ne thjesht po vrasim procesin tonë.

I gjithë kodi i aplikacionit tonë të vogël:

#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: Ne mund të përdorim lehtësisht funksionin DbgBreakPoint() në kod për të ndaluar në debugger. E vërteta është se do të duhej të lidhnim WinDbg me makinën virtuale për debagimin e kernelit. Udhëzimet se si ta bëni këtë mund të gjenden këtu ose thjesht përdorni VirtualKD.

Kompilimi dhe ndërtimi

Mënyra më e thjeshtë për të ndërtuar një aplikacion natyror është të përdorni DDK (Driver Development Kit). Na nevojitet versioni i vjetër i shtatë, pasi versionet më të reja kanë një qasje pak më ndryshe dhe punojnë ngushtë me Visual Studio. Nëse përdorim DDK, projekti ynë ka nevojë vetëm për Makefile dhe burimet.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

burimet:

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

Makefile juaj do të jetë saktësisht si ky, ndërsa për burimet do të ndalemi pak më shumë. Në këtë skedar specifikohen burimet e programit tuaj (skedarët .c), opsionet e ndërtimit dhe parametrat e tjera.

  • TARGETNAME – emri i skedarit ekzekutues qĂ« duhet tĂ« rezultojĂ« nĂ« fund.
  • TARGETTYPE – lloji i skedarit ekzekutues, mund tĂ« jetĂ« njĂ« driver (.sys), atĂ«herĂ« vlera e fushĂ«s duhet tĂ« jetĂ« DRIVER, nĂ«se Ă«shtĂ« njĂ« bibliotekĂ« (.lib), vlera LIBRARY. NĂ« rastin tonĂ«, na nevojitet njĂ« skedar ekzekutues (.exe), prandaj vendosim vlerĂ«n PROGRAM.
  • UMTYPE – vlerat e mundshme tĂ« kĂ«saj fushe: console pĂ«r aplikacionin e konsolĂ«s, windows pĂ«r funksionimin nĂ« modalitetin e dritares. Por ne duhet tĂ« caktosh nt pĂ«r tĂ« marrĂ« njĂ« aplikacion natyror.
  • BUFFER_OVERFLOW_CHECKS – kontrolli i stack-ut pĂ«r mbushjen e tamponit, fatkeqĂ«sisht nuk Ă«shtĂ« rasti ynĂ«, ne e çaktivizojmĂ«.
  • MINWIN_SDK_LIB_PATH – kjo vlerĂ« i referohet variablit SDK_LIB_PATH, nuk ka nevojĂ« tĂ« shqetĂ«soheni qĂ« njĂ« variablĂ« tĂ« tillĂ« sistemore nuk Ă«shtĂ« shpallur, nĂ« momentin kur tĂ« nisĂ«m ndĂ«rtimin e kontrolluar nga DDK, ky variabli do tĂ« shpallet dhe do tĂ« tregojĂ« pĂ«r bibliotekat e nevojshme.
  • SOURCES – lista e burimeve tĂ« programit tuaj.
  • INCLUDES – skedarĂ«t e titulluara qĂ« janĂ« tĂ« nevojshme pĂ«r ndĂ«rtimin. KĂ«tu zakonisht jepni rrugĂ«n pĂ«r skedarĂ«t qĂ« vijnĂ« me DDK, por mund tĂ« jepni gjithashtu çdo tjetĂ«r.
  • TARGETLIBS – lista e bibliotekave qĂ« duhet tĂ« linkohen.
  • USE_NTDLL – njĂ« fushĂ« e detyrueshme qĂ« duhet tĂ« vendoset nĂ« pozitat 1. PĂ«r arsye kuptimplote.
  • USER_C_FLAGS – çdo flamur qĂ« mund tĂ« pĂ«rdorni nĂ« drejtoritĂ« e para-procesit nĂ« pĂ«rgatitjen e kodit tĂ« aplikacionit.

Pra, për ndërtimin na nevojitet të startojmë nga x86 (ose x64) Checked Build, të ndryshojmë katalogun e punës në dosjen e projektit dhe të ekzekutojmë komandën Build. Rezultati në screenshot tregon se kemi ndërtuar një skedar ekzekutiv.

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore

Ky skedar nuk mund të startohet lehtësisht, sistemi ankohet dhe na dërgon të mendojmë për sjelljen e tij me këtë gabim:

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore

Si tĂ« стартoni njĂ« aplikacion tĂ« natyrshĂ«m?

Në momentin e fillimit autochk, sekuenca e fillimit të programeve përcaktohet nga vlera e çelësit të regjistrit:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Menaxheri i seancave ekzekuton një pas një programet nga ky listë. Vetë skedarët ekzekutivë i kërkon menaxheri i seancave në drejtorinë system32. Formati i vlerës së çelësit të regjistrit është si vijon:

autocheck autochk *MyNative

Vlera duhet të jetë në formatin heksadecimal, dhe jo në ASCII të zakonshëm, prandaj çelësi i paraqitur më lart do të ketë formatin:

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ër të konvertuar emrin, mund të përdorni një shërbim online, për shembull, këtë.

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore
Duket se për të nisur aplikacionin natyror, na nevojitet:

  1. Të kopjojmë skedarin ekzekutiv në dosjen system32
  2. Të shtojmë një çelës në regjistër
  3. Të rindezim makinën

Për lehtësi, ja një skenar i gatshëm për instalimin e aplikacionit natyror:

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

Pas instalimit dhe rindezjes, edhe para shfaqjes së ekranit të zgjedhjes së përdoruesve do të kemi këtë pamje:

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore

Përfundimi

Me shembullin e një aplikacioni të tillë të vogël, ne u bindëm se është krejtësisht e mundur të nisësh aplikacionin në nivelin Windows Native. Më pas, ne do të vazhdojmë me djemtë nga Universiteti Innopolis për të ndërtuar një shërbim që do të niste procesin e ndërveprimit me driverin shumë më herët se në versionin e mëparshëm të projektit tonë. Dhe me shfaqjen e mbulesës win32, do të kishte kuptim që të kalonim menaxhimin në një shërbim të plotë që tashmë ishte zhvilluar (për këtë më shumë) këtu).

Në këtë artikull, do të flasim për një komponent tjetër të shërbimit Active Restore, konkretisht për driverin UEFI. Abonohuni në blogun tonë për të mos e humbur postimin e ardhshëm.

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster