Windows Native Applications ja teenus Acronis Active Restore

TĂ€na jĂ€tkame rÀÀkimist sellest, kuidas koos Innopolis Ülikooli noorte arendame Active Restore tehnoloogiat, et vĂ”imaldada kasutajal vĂ”imalikult kiiresti oma masinal tööle asuda pĂ€rast riket. RÀÀgime Windowsi kohalikke rakendusi, sealhulgas nende loomise ja kĂ€ivitamise eripĂ€radest. Postituse all – natuke meie projektist ning praktiline juhend kohalike rakenduste kirjutamiseks.

Windows Native Applications ja teenus Acronis Active Restore

Varasemates postitustes oleme juba rÀÀkinud, mis see on Active Restore, ja kuidas Innopolis ĂŒliĂ”pilased seda arendavad. teenusTĂ€na soovin peatuda kohalikel rakendustel, millesse soovime „kaevata“ meie aktiivse taastamise teenust. Kui kĂ”ik Ă”nnestub, siis suudame:

  • Oluliselt varem kĂ€ivitada teenuse
  • Oluliselt varem ĂŒhenduda pilvega, kus asub varukoopia
  • Oluliselt varem mĂ”ista, millises reĆŸiimis sĂŒsteem asub – tavaliselt kĂ€ivitamisel vĂ”i taastamisel
  • Oluliselt vĂ€hem faile taastuda ette
  • Katta kasutaja vĂ”imalikult kiiresti tööle asuda.

Mis ĂŒldse on kohalik rakendus?

Et sellele kĂŒsimusele vastata, vaatame jĂ€rjestust, mille sĂŒsteem kutsub vĂ€lja, nĂ€iteks siis, kui programmeerija ĂŒritab oma rakenduses faili luua.

Windows Native Applications ja teenus Acronis Active Restore
Pavel Yosifovich — Windows Kernel Programming (2019)

Programmeerija kasutab funktsiooni CreateFile, mis on deklareeritud pealkirja failis fileapi.h ja rakendatud Kernel32.dll-is. Kuid see funktsioon ei loo faili, vaid kontrollib sisendi argumente ja kutsub vĂ€lja funktsiooni NtCreateFile (Nt eesliide nĂ€itab, et funktsioon on kohaliku). See funktsioon on deklareeritud pealkirja failis winternl.h ja rakendatud ntdll.dll-is. See valmistab ette hĂŒppe kerneli ruumi, seejĂ€rel kutsub vĂ€lja sĂŒsteemikĂ”ne faili loomiseks. Sel juhul selgub, et Kernel32 on vaid Ntdll-i wrapper. Üks pĂ”hjus, miks see on tehtud, on see, et Microsoftil on vĂ”imalus muuta kohalike maailmade funktsioone, kuid samas mitte segada standardseid liideseid. Microsoft ei soovita otse kutsuda kohalikke funktsioone ega dokumenteeri enamikku neist. Muide, dokumenteerimata funktsioone on vĂ”imalik leida siit.

Peamine eelis natiivrakenduste puhul on see, et ntdll laaditakse sĂŒsteemi mĂ€rgatavalt varem kui kernel32. See on mĂ”istlik, kuna kernel32 vajab ntdll-i oma toimimiseks. Selle tulemusena saavad rakendused, mis kasutavad natiivseid funktsioone, alustada tööd mĂ€rgatavalt varem.

Seega on Windowsi natiivrakendused programmid, mis suudavad kÀivituda Windowsi kÀivitamise varases etapis. Need kasutavad AINULT ntdll-i funktsioone. Sellise rakenduse nÀide: autochk mis tÀidab chkdisk utility kettaka vigade kontrollimiseks juba enne peamiste teenuste laadimist. Just sellisel tasemel tahame nÀha meie Active Restore'i.

Mida me vajame?

  • DDK (Driver Development Kit), tuntud ka kui WDK 7 (Windows Driver Kit).
  • Virtuaalne masin (nĂ€iteks Windows 7 x64)
  • Ei ole kohustuslik, kuid vĂ”ib aidata pealkirja faile, mida saab alla laadida siit

Mis on koodis?

Hakkame natuke harjutama ja kirjutame nÀiteks vÀikese rakenduse, mis:

  1. Kuvab sÔnumi ekraanile
  2. KÀivitab natuke mÀlu
  3. Ootab sisendit klaviatuurilt
  4. Vabastab hÔivatud mÀlu

Natiivrakendustes on sisenemispunktiks mitte main ega winmain, vaid funktsioon NtProcessStartup, kuna me tegelikult kĂ€ivitame uusi protsesse sĂŒsteemis otse.

Alustame ekraanile sÔnumi kuvamisest. Selleks on meil natiivne funktsioon NtDisplayString, mis vÔtab argumentidena UNICODE_STRING struktuuri objekti nÀitaja. Selle initsialiseerimiseks aitab meid RtlInitUnicodeString. Tulemuseks, et teksti kuvamiseks ekraanil, saame kirjutada sellise vÀikese funktsiooni:

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

Kuna meile on saadaval ainult ntdll-i funktsioonid ja teisi teeke pole mĂ€lu veel, siis tekivad meil kindlasti probleemid mĂ€lu haldamisega. new operaatorit veel ei eksisteeri (sest see kuulub liiga kĂ”rgema taseme C++ maailma), samuti ei ole funktsiooni malloc (selle jaoks on vajalikke runtime C teeke). Loomulikult vĂ”ib kasutada ainult steeki. Kuid kui peame dĂŒnaamiliselt mĂ€lu haldama, tuleb seda teha kuhjast (tuntud ka kui heap). Seega loome endale kuhja ja vĂ”tame sellest mĂ€lu siis, kui seda vajame.

Selle ĂŒlesande jaoks sobib funktsioon RtlCreateHeap. Edasi liikudes kasutame RtlAllocateHeap ja RtlFreeHeap, et hallata mĂ€lu, kui meil seda vaja on.

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

// loome vahe mÀlu jaotamiseks hiljem
memory = RtlCreateHeap(
  HEAP_GROWABLE, 
  NULL, 
  1000, 
  0, NULL, NULL
);

// jaota buffer suurusega bufferSize
buffer = RtlAllocateHeap(
  memory, 
  HEAP_ZERO_MEMORY, 
  bufferSize
);

// vabasta buffer (tegelikult ei ole vajalik, kuna hÀvitage vahe jÀrgmises etapis)
RtlFreeHeap(memory, 0, buffer);

RtlDestroyHeap(memory);

Liigume edasi ootama klaviatuuri sisendit.

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

KĂ”ik, mida meil on vaja – on kasutada NtReadFile avatud seadmes ja oodata, kuni klaviatuur tagastab meile mingi vajutuse. Kui vajutatakse klahvi ESC, jĂ€tkame tööd. Seadme avamiseks peame kutsuma funktsiooni NtCreateFile (avatakse DeviceKeyboardClass0). Samuti kutsume ĂŒles NtCreateEvent, et initsialiseerida ootamisobjekt. Me deklareerime ise KEYBOARD_INPUT_DATA struktuuri, mis esindab klaviatuuri andmeid. See lihtsustab meie tööd.

Natiivse rakenduse töö lÔpeb funktsiooni kutsumisega NtTerminateProcess, kuna me lihtsalt tapame oma protsessi.

Kogu meie vÀikese rakenduse kood:

#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: Me vĂ”ime koodis lihtsalt kasutada funktsiooni DbgBreakPoint() debugeerimise peatamiseks. TĂ”si, tuleb WinDbg ĂŒhendada virtuaalmasinaga tuumadebugimiseks. Juhiseid, kuidas seda teha, saab leida siit vĂ”i lihtsalt kasutada VirtualKD.

Kompileerimine ja kokkupanek

KĂ”ige lihtsam viis natiivse rakenduse kokkupanekuks on kasutada DDK (Driver Development Kit). Me vajame just seda iidset seitsmendat versiooni, kuna hilisemad versioonid töötavad suuresti teistmoodi ja on tihedalt seotud Visual Studio’ga. Kui kasutada DDK-d, piisab meie projektist vaid Makefile’ist ja allikatest.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

allikad:

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

Teie Makefile on tÀpselt samasugune, allikatele peatume natuke rohkem. Selles failis nÀidatakse teie programmi allikaid (failid .c), kokkupaneku valikuid ja muid parameetreid.

  • TARGETNAME – tĂ€idetava faili nimi, mis lĂ”puks peaks valmima.
  • TARGETTYPE – tĂ€idetava faili tĂŒĂŒp, see vĂ”ib olla draiver (.sys), siis peab vÀÀrtus olema DRIVER, kui teek (.lib), siis vÀÀrtus LIBRARY. Meie puhul on vajalik tĂ€idetav fail (.exe), seega seame vÀÀrtuseks PROGRAM.
  • UMTYPE – selle vĂ€lja vĂ”imalikke vÀÀrtusi: console konsooli rakenduse jaoks, windows aknarakenduse tööks. Kuid me peame mÀÀrama nt, et saada kohaliku rakenduse.
  • BUFFER_OVERFLOW_CHECKS – ĂŒlevaatus mĂ€lustaki ĂŒleliigse tĂ€itmise kontrollimiseks, kahjuks ei ole see meie juhtum, lĂŒlitame vĂ€lja.
  • MINWIN_SDK_LIB_PATH – see vÀÀrtus viitab muutuja SDK_LIB_PATH, ei maksa muretseda, et sellist sĂŒsteemset muutujat ei ole deklareeritud, hetkel, kui kĂ€ivitame checked build DDKst, see muutuja deklareeritakse ja osutab vajalikele raamatukogudele.
  • SOURCES – teie programmi allikate nimekiri.
  • INCLUDES – pĂ€isefailid, mis on vajalikud kompileerimiseks. Siin tavaliselt mÀÀratakse tee failidele, mis tulevad koos DDKga, kuid saate lisada ka teised.
  • TARGETLIBS – raamatukogude nimekiri, mida tuleb linkida.
  • USE_NTDLL – kohustuslik vĂ€ljad, mis tuleb seada vÀÀrtusele 1. Ilmselgetel pĂ”hjustel.
  • USER_C_FLAGS – kĂ”ik lipud, mida saate kasutada eelprotsessorite suunistes rakenduse koodi ettevalmistamisel.

KokkuvĂ”ttes, et kompileerida, peame kĂ€ivitama x86 (vĂ”i x64) Checked Build, muutma töökausta projekti kaustaks ja kĂ€ivitama Build kĂ€su. Tulemus ekraanil nĂ€itab, et meil on kogutud ĂŒks tĂ€itmisfail.

Windows Native Applications ja teenus Acronis Active Restore

Seda faili ei saa lihtsalt kĂ€ivitada, sĂŒsteem nuriseb ja saadab meid mĂ”tlema oma kĂ€itumise jĂ€rgmise veaga:

Windows Native Applications ja teenus Acronis Active Restore

Kuidas kÀivitada kohalikku rakendust?

KÀivitamise hetkel mÀÀrab autochk programmide kÀivitamise jÀrjekorra registri vÔtme vÀÀrtus:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Seansi haldur kÀivitab jÀrjekorras programme sellest nimekirjast. Isegi tÀidetavaid faile otsib seansi haldur direktoriumist system32. Registri vÔtme vÀÀrtuse formaat on jÀrgmine:

autocheck autochk *MyNative

VÀÀrtus peab olema kuuesajandilisel kujul, mitte tavalises ASCII formaadis, seega saab ĂŒlaltoodud vĂ”ti jĂ€rgmise formaadi:

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

Nime konverteerimiseks saate kasutada veebiteenust, nÀiteks, see.

Windows Native Applications ja teenus Acronis Active Restore
Tuleb vÀlja, et kohalikku rakendust kÀivitamiseks on meil vajalik:

  1. Kopeerida tÀitmisfail kausta system32
  2. Lisada registrisse vÔti
  3. TaaskÀivitada masin

Mugavuse huvides on siin valmis skript kohaliku rakenduse installimiseks:

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Àrast installimist ja taaskÀivitamist, enne kasutajakonto valimise ekraani, nÀeme jÀrgmiseid tulemusi:

Windows Native Applications ja teenus Acronis Active Restore

KokkuvÔte

Selle vĂ€ikses rakenduses oleme veendunud, et rakenduse kĂ€ivitamine Windows Native tasemel on tĂ€iesti vĂ”imalik. Edasi koos Innopolis Ülikooli tiimiga ehitame teenust, mis algatab suhtlemisprotsessi draiveriga palju varem kui meie projekti eelmine versioon. Ja pĂ€rast win32 liidese ilmumist on mĂ”istlik anda juhtimine juba vĂ€lja töötatud tĂ€isfunktsionaalsesse teenusesse (sellega tutvume lĂ€hemalt siin).

JĂ€rgmises artiklis kĂ€sitleme veel ĂŒhte Active Restore teenuse komponenti, nimelt UEFI draiverit. JĂ€lgige meie blogi, et mitte jÀÀda ilma jĂ€rgmistest postitustest.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster