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 Universitetei Innopolis, zhvillojmĂ« teknologjinĂ« Active Restore, pĂ«r tĂ« lejuar pĂ«rdoruesit tĂ« fillojnĂ« sa mĂ« shpejt tĂ« jetĂ« e mundur punĂ«n nĂ« makinĂ«n e tij pas njĂ« dĂ«shtimi. Do tĂ« flasim pĂ«r aplikacionet native tĂ« Windows, duke pĂ«rfshirĂ« karakteristikat e krijimit dhe ekzekutimit tĂ« tyre. NĂ«n kĂ«tĂ« titull – pak rreth projektit tonĂ« dhe njĂ« udhĂ«zues praktik se si tĂ« shkruani aplikacione native.

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

Në postimet e kaluara, ne tashmë kemi folur se çfarë është Active Restore, dhe se si studentët nga Innopolis po zhvillojnë shërbimin. Sot dua të ndalem te aplikacionet native, deri në nivelin që ne dëshirojmë ta "thellojmë" shërbimin tonë të rikthimit aktiv. Nëse gjithçka shkon mirë, atëherë ne do të mund:

  • TĂ« nisim shĂ«rbimin shumĂ« mĂ« herĂ«t
  • TĂ« lidhemi me cloud-in ku ruhet backup-i shumĂ« mĂ« herĂ«t
  • TĂ« kuptojmĂ« shumĂ« mĂ« herĂ«t nĂ« cilin modalitet ndodhet sistemi – ngarkimi normal apo rikthimi
  • TĂ« rikthejmĂ« shumĂ« mĂ« pak skedarĂ« paraprakisht
  • TĂ« lejojmĂ« pĂ«rdoruesin tĂ« fillojĂ« punĂ«n akoma mĂ« shpejt.

ÇfarĂ« Ă«shtĂ« nĂ« tĂ« vĂ«rtetĂ« njĂ« aplikacion native?

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

Aplikacionet Native të Windows dhe shërbimi Acronis Active Restore
Pavel Yosifovich — Programimi i Kernel-it tĂ« Windows (2019)

Programuesi përdor funksionin CreateFile, i cili është shpallur në skedarin e titullit fileapi.h dhe është realizuar në Kernel32.dll. Megjithatë, vetë kjo funksion nuk merret me krijimin e skedarit, ajo vetëm kontrollon argumentet në hyrje dhe thërret funksionin NtCreateFile (prefiksi Nt tregon se funksioni është native). Ky funksion është shpallur në skedarin e titullit winternl.h dhe është realizuar në ntdll.dll. Ai bën përgatitje për një skak të hapësirës së kernels, pas së cilës bën një thirrje sistemore për të krijuar skedarin. Në këtë rast, del se Kernel32 është thjesht një mbështjellës për Ntdll. Një nga arsyet pse kjo është bërë është se Microsoft ka mundësinë të ndryshojë funksionet e botës native, por pa prekur ndërfaqet standarde. Microsoft nuk rekomandon thirrjen direkte të funksioneve native dhe nuk dokumenton shumicën e tyre. Për më tepër, funksionet e pa dokumentuara mund të gjenden këtu.

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

Prandaj, Aplikacionet Natyrore tĂ« Windows janĂ« programe qĂ« mund tĂ« fillojnĂ« nĂ« njĂ« fazĂ« tĂ« hershme tĂ« ngarkesĂ«s sĂ« Windows. Ato pĂ«rdorin VETËM funksione nga ntdll. NjĂ« shembull i tillĂ« i aplikacionit: autochk i cili ekzekuton chkdisk utility pĂ«r tĂ« kontrolluar disqin pĂ«r gabime para se tĂ« nisin shĂ«rbimet kryesore. PikĂ«risht nĂ« kĂ«tĂ« nivel dĂ«shirojmĂ« tĂ« shohim Active Restore.

ÇfarĂ« na nevojitet?

  • DDK (Pako e Zhvillimit tĂ« Drejtuesve), tani e njohur gjithashtu si WDK 7 (Pako e Drejtuesve tĂ« Windows).
  • NjĂ« makinĂ« virtuale (p.sh., Windows 7 x64)
  • Nuk Ă«shtĂ« e domosdoshme, por mund tĂ« ndihmojnĂ« skedarĂ«t e titujve qĂ« mund tĂ« shkarkoni kĂ«tu

ÇfarĂ« ka nĂ« kod?

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

  1. Shfaq një mesazh në ekran
  2. Alokoni pak memorje
  3. Pret input nga tastiera
  4. Çliron memorjen e zĂ«nĂ«

Në aplikacionet natyrale, pika e hyrjes nuk është main ose winmain, por funksioni NtProcessStartup, pasi realisht po fillojmë drejtpërdrejt procese të reja në sistem.

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

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

Duke qenë se na janë në dispozicion vetëm funksionet nga ntdll, dhe asnjë bibliotekë tjetër në memorie thjesht nuk ka, patjetër do të kemi probleme me alokimin e memorjes. Operatori new ende nuk ekziston (sepse ai vjen nga një botë shumë e lartë C++), as nuk ka funksionin malloc (për të nevojiten bibliotekat e kohës së ekzekutimit C). Sigurisht, mund të përdorim vetëm stekin. Por nëse na nevojitet të alokojmë memorje në mënyrë dinamike, do të duhet ta bëjmë këtë në kupë (dmth. heap). Prandaj, le të krijojmë një kupë për veten tonë dhe të marrim prej saj memorje kur na nevojitet.

Për këtë qëllim, funksioni RtlCreateHeap. Më pas, duke përdorur RtlAllocateHeap dhe RtlFreeHeap, ne do të marrim dhe çlirojmë memorje kur na nevojitet.

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

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

// alokoni një tampon me madhësi bufferSize
buffer = RtlAllocateHeap(
  memory, 
  HEAP_ZERO_MEMORY, 
  bufferSize
);

// çlironi tamponin (në të vërtetë nuk është e nevojshme sepse shkatërrojmë heap-in në hapin e ardhshëm)
RtlFreeHeap(memory, 0, buffer);

RtlDestroyHeap(memory);

Të kalojmë te pritja 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ë një pajisje të hapur dhe të presim deri sa tastiera të kthejë një shtypje. Nëse shtypet çelësi ESC, ne do të vazhdojmë punën. Për të hapur pajisjen, do të na nevojitet të thërrasim funksionin NtCreateFile (duhet të hapim DeviceKeyboardClass0). Gjithashtu, do të thërrasim NtCreateEvent, për të inicializuar një objekt për pritje. Ne do të shpallim vetë strukturën KEYBOARD_INPUT_DATA, e cila përfaqëson të dhënat e tastierës. Kjo do ta bëjë punën tonë më të lehtë.

Puna e aplikacionit natyror përfundon me thirrjen e funksionit NtTerminateProcess, sepse 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 ta përdorim lehtësisht në kod funksionin DbgBreakPoint() për të ndaluar në debagues. Megjithatë, duhet të lidhim WinDbg me makinën virtuale për debagimin e kernelit. Udhëzimet se si të bëni këtë mund të gjeni këtu ose thjesht të përdorim 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 antik i shtatë, sepse versionet më të vonshme kanë një qasje të ndryshme dhe punojnë ngushtë me Visual Studio. Nëse përdorim DDK, projekti ynë ka nevojë vetëm për Makefile dhe 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

Makefile juaj do të jetë pikërisht kështu, ndërsa për sources le të ndalemi pak më gjatë. Në këtë skedë tregohet kodet burimore të programit tuaj (skedarët .c), opsionet e ndërtimit dhe parametra të tjera.

  • TARGETNAME – emri i skedarit ekzekutiv qĂ« duhet tĂ« rezultojĂ« nĂ« fund.
  • TARGETTYPE – tipi i skedarit ekzekutiv, mund tĂ« jetĂ« driver (.sys), atĂ«herĂ« vlera e fushĂ«s duhet tĂ« jetĂ« DRIVER, nĂ«se Ă«shtĂ« bibliotekĂ« (.lib), atĂ«herĂ« vlera LIBRARY. NĂ« rastin tonĂ« na nevojitet njĂ« skedar ekzekutiv (.exe), prandaj e vendosim vlerĂ«n PROGRAM.
  • UMTYPE – vlerat e mundshme tĂ« kĂ«tij fushe: console pĂ«r aplikacione konsolĂ«, windows pĂ«r operim nĂ« mĂ«nyrĂ« dritare. Por na nevojitet tĂ« tregojmĂ« nt, pĂ«r tĂ« marrĂ« njĂ« aplikacion natyror.
  • BUFFER_OVERFLOW_CHECKS – kontrolli i stack-ut pĂ«r mbivendosje tĂ« buffer-it, fatkeqĂ«sisht nuk Ă«shtĂ« rasti ynĂ«, e çaktivizojmĂ«.
  • MINWIN_SDK_LIB_PATH – kjo vlerĂ« i referohet ndryshoreve SDK_LIB_PATH, nuk e keni pĂ«r tĂ« shqetĂ«suar qĂ« nuk keni shpallur njĂ« ndryshore tĂ« tillĂ« sistemore, nĂ« momentin kur tĂ« iniciojmĂ« njĂ« ndĂ«rtim tĂ« kontrolluar nga DDK, kjo ndryshore do tĂ« shpallet dhe do tĂ« tregojĂ« libraritĂ« e nevojshme.
  • SOURCES – lista e burimeve tĂ« programit tuaj.
  • INCLUDES – skedarĂ«t header qĂ« janĂ« tĂ« nevojshĂ«m pĂ«r ndĂ«rtimin. KĂ«tu zakonisht tregohet rruga pĂ«r skedarĂ«t qĂ« vijnĂ« me DDK, por mund tĂ« tregoni pĂ«rveç kĂ«saj çdo skedĂ« tjetĂ«r.
  • TARGETLIBS – lista e librarive qĂ« duhet tĂ« lidhni.
  • USE_NTDLL – njĂ« fushĂ« e domosdoshme qĂ« duhet vendosur nĂ« pozita 1. PĂ«r arsye tĂ« mjaftueshme.
  • USER_C_FLAGS – çdo flamur qĂ« mund tĂ« pĂ«rdorni nĂ« direktivat e preprocessorit gjatĂ« pĂ«rgatitjes sĂ« kodit tĂ« aplikacionit.

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

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

Ky skedar nuk mund të startohet kaq lehtë, sistemi e ankohet dhe na dërgon të mendojmë për sjelljen e tij me gabimin e mëposhtëm:

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

Si të iniciojmë një aplikacion natyror?

Në momentin e startit autochk, rendi i ekzekutimit të programeve përcaktohet nga vlera e çelësit të regjistrit:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

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

autocheck autochk *MyNative

Vlera duhet të jetë në formatin heksadecimal dhe jo në ASCII të zakonshëm, prandaj çelësi i paraqitur më sipër 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
Pra, për të nisur një aplikacion natyror, na nevojitet:

  1. Të kopjojmë skedarin ekzekutiv në dosjen system32
  2. Të shtojmë në regjistër çelësin
  3. Të rindezë 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 Shembulli Native Instaluar
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, para se të shfaqet ekrani i zgjedhjes së përdoruesve, ne do të kemi pamjen e mëposhtme:

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

Përfundimi

Në shembullin e kësaj aplikacioni të vogël, ne u siguruam se është e mundur të nisësh një aplikacion në nivelin Windows Native. Më tutje, me djemtë nga Universiteti Innopolis do të vazhdojmë të ndërtojmë një shërbim që do të nxisë procesin e ndërveprimit me drejtuesin shumë më herët se në versionin e mëparshëm të projektit tonë. Dhe me shfaqjen e shell-it win32, do të jetë logjike të transferohet kontrolli në një shërbim të plotë, i cili është zhvilluar tashmë (për këtë më shumë këtu).

Në artikullin e ardhshëm do të prekim një tjetër komponent të shërbimit Active Restore, pra drejtuesin UEFI. Regjistrohuni në blogun tonë për të mos humbur postimin e ardhshëm.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster