Windowsi natiivrakendused ja Acronis Active Restore teenus

TĂ€na jĂ€tkame juttu sellest, kuidas koos Innopolis Ülikooli poistega arendame Active Restore tehnoloogiat, et lubada kasutajal alustada oma masinal tööle naasmist vĂ”imalikult kiiresti pĂ€rast riket. Jutuks on natiivrakendused Windowsis, sealhulgas nende loomise ja kĂ€itamise eripĂ€ra. Allpool on natuke meie projektist ning praktiline juhend, kuidas kirjutada natiivrakendusi.

Windowsi natiivrakendused ja Acronis Active Restore teenus

Eelmistes postitustes oleme juba rÀÀkinud, mis on Active Restore, ja kuidas Innopolis Ülikooli tudengid seda arendavad teenus. TĂ€na tahan peatuda natiivrakendustel, mille tasemeni soovime meie aktiivse taastamise teenuse „kaevata“. Kui kĂ”ik lĂ€heb hĂ€sti, siis suudame:

  • KĂ€ivitada teenuse palju varem
  • Suhtuda pilvega, kus varukoopia asub, palju varem
  • MĂ”ista, millises reĆŸiimis sĂŒsteem on – normaalse kĂ€ivitamise vĂ”i taastamise reĆŸiimis, palju varem
  • Taastada palju vĂ€hem faile eelnevalt
  • Lubada kasutajal alustada tööd veelgi kiiremini.

Mis on ĂŒldse natiivne rakendus?

KĂŒsimusele vastamiseks vaatame sĂŒsteemi vĂ€ljakutseid, nĂ€iteks kui programmeerija ĂŒritab oma rakenduses faili luua.

Windowsi natiivrakendused ja Acronis Active Restore teenus
Pavel Yosifovich — Windows Kernel Programming (2019)

Programmeerija kasutab funktsiooni CreateFile, mis on deklareeritud pealkirjafailis fileapi.h ja rakendatud Kernel32.dll-is. Siiski ei tegele see funktsioon failide loomisega, see vaid kontrollib sisendi argumendid ja kutsub vĂ€lja funktsiooni NtCreateFile (Nt-silt nĂ€itab, et funktsioon on natiivne). See funktsioon on deklareeritud pealkirjafailis winternl.h ja rakendatud ntdll.dll-is. See valmistab ette hĂŒppe tuumaruumi ja teeb seejĂ€rel sĂŒsteemi vĂ€ljakutse faili loomiseks. Seega on Kernel32 lihtsalt Ntdll'i mĂ€his. Üks pĂ”hjusi, miks see nii on tehtud, on see, et Microsoft saab sel viisil natiivsete maailmade funktsioone muuta, ilma et nad puutuksid tavalisi liideseid. Microsoft ei soovita otse kutsuda esile natiivsete funktsioonide ja ei dokumenteeri enamikku neist. Muide, dokumenteerimata funktsioone vĂ”ib leida siin.

PĂ”hiline eelis natiivsete rakenduste puhul on see, et ntdll laaditakse sĂŒsteemi palju varem kui kernel32. See on loogiline, sest kernel32 vajab ntdll-i oma tööks. SeetĂ”ttu saavad rakendused, mis kasutavad natiivseid funktsioone, alustada tööle asumist mĂ€rkimisvÀÀrselt varem.

Seega on Windowsi natiivsed rakendused programmid, mis suudavad kÀivituda Windowsi laadimise varajases staadiumis. Need kasutavad AINULT funktsioone ntdll-ist. NÀide sellisest rakendusest: autochk mis kÀivitab chkdisk utility kettavead kontrollimiseks juba enne pÔhiteenuste kÀivitamist. Just sellisel tasemel soovime nÀha meie Active Restore'i.

Mida me vajame?

  • DDK (Driver Development Kit), tuntud ka kui WDK 7 (Windows Driver Kit).
  • Virtuaalne masin (nt Windows 7 x64)
  • Ei ole kohustuslik, kuid pĂ€isefailid vĂ”ivad olla kasulikud ja neid saab alla laadida siin

Mis on koodis?

Harjutame veidi ja kirjutame nÀitena vÀikese rakenduse, mis:

  1. Kuvab ekraanile sÔnum
  2. Allocateerib natuke mÀlu
  3. Ootab sisestust klaviatuurilt
  4. Vabastab reserveeritud mÀlu

Natiivsetes rakendustes on sisenemiskoht mitte main vĂ”i winmain, vaid funktsioon NtProcessStartup, kuna me kĂ€ivitame uusi protsesse sĂŒsteemis otse.

Alustame sÔnumi ekraanile kuvamisest. Selleks on meil natiivne funktsioon NtDisplayString, mis vÔtab argumendiks UNICODE_STRING struktuuri objekti nÀidiku. Selle algatamiseks aitab meid RtlInitUnicodeString. SeetÔttu saame teksti ekraanile kuvamiseks 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 meil on juurdepÀÀs ainult ntdll funktsioonidele ja teised teegid ei ole mĂ€lus veel olemas, tekivad meil kindlasti probleemid mĂ€lu eraldamisega. Operaatoreid new veel ei eksisteeri (sest see pĂ€rineb liiga kĂ”rgetasemelisest C++ maailmast), samuti ei ole funktsiooni malloc (selle jaoks on vajalikud C runtime teegid). Loomulikult saame kasutada ainult virna. Kuid kui peame dĂŒnaamiliselt mĂ€lu eraldama, peab seda tegema kuhjas. Seega loome endale kuhja ja vĂ”tame sealt mĂ€lu, kui meil on seda vaja.

Selle ĂŒlesande jaoks sobib funktsioon RtlCreateHeap. JĂ€tkame RtlAllocateHeap'i ja RtlFreeHeap'i kasutamisega, et mĂ€lu kasutada ja vabastada, kui me seda vajame.

PVOID mÀlu = NULL;
PVOID puhver = NULL;
ULONG puhverSuurus = 42;

// loome kuhja, et hiljem mÀlu eraldada
mÀlu = RtlCreateHeap(
  HEAP_GROWABLE, 
  NULL, 
  1000, 
  0, NULL, NULL
);

// eraldame puhversuuruse suitset
puhver = RtlAllocateHeap(
  mÀlu, 
  HEAP_ZERO_MEMORY, 
  puhverSuurus
);

// vabastame puhvri (tegelikult pole vajalik, sest hÀvitame kuhja jÀrgmises etapis)
RtlFreeHeap(mÀlu, 0, puhver);

RtlDestroyHeap(mÀlu);

Liigume edasi klaviatuuri sisendi ootamise juurde.

// 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 vajame, on kasutada NtReadFile avatud seadmes ning oodata, kuni klaviatuur tagastab meile mĂ”ne nĂ€ppimise. Kui klahvi ESC vajutatakse, jĂ€tkame tööd. Seadme avamiseks peame kutsuma funktsiooni NtCreateFile (avama peab DeviceKeyboardClass0). Samuti kutsume ĂŒles NtCreateEvent, et algatada objekti ootamiseks. Me kuulutame iseseisvalt vĂ€lja KEYBOARD_INPUT_DATA struktuuri, mis esindab klaviatuuri andmeid. See lihtsustab meie tööd.

Natiivrakenduse töö lĂ”ppeb funktsiooni NtTerminateProcess, kutsub ĂŒles, sest 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 saame koodis hĂ”lpsasti kasutada DbgBreakPoint() funktsiooni, et peatuda silurisse. Kuid peame ĂŒhendama WinDbg virtuaalmasinaga tuumadebĂŒĂŒgiks. Juhiseid selle tegemiseks leiate siin vĂ”i lihtsalt kasutada VirtualKD.

Kompileerimine ja kogumine

Lihtsaim viis natiivrakenduse loomiseks on kasutada DDK (Driver Development Kit). Me vajame just vanimat seitsmenda versiooni, kuna hilisemad versioonid on natuke teistsugused ja töötavad tihedalt koos Visual Studio'ga. Kui aga kasutada DDK-d, siis vajab meie projekt lihtsalt Makefile'i ja 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

Teie Makefile on tÀpselt sama, kuid peatume sources'i juures natuke rohkem. Selles failis mÀÀratakse teie programmi lÀhtekood (failid .c), kogumise valikud ja muud parameetrid.

  • TARGETNAME – tĂ€pselt see, mis peab olema lĂ”pptulemuseks saadud tĂ€idetav fail.
  • TARGETTYPE – tĂ€itmisfaili tĂŒĂŒp, see vĂ”ib olla draiver (.sys), siis peab vĂ€lja vÀÀrtus olema DRIVER, kui teek (.lib), siis vÀÀrtus LIBRARY. Meie juhul on vajalik tĂ€itmisfail (.exe), seega seadistame vÀÀrtuse PROGRAM.
  • UMTYPE – selle vĂ€lja vĂ”imalikud vÀÀrtused: console konsoolirakenduse jaoks, windows kas WINDOWS-ENV -le. Kuid peame mÀÀrama nt, et saada natiivne rakendus.
  • BUFFER_OVERFLOW_CHECKS – virna ĂŒlevoolu kontroll, kahjuks ei ole see meie juhtum, lĂŒlitame vĂ€lja.
  • MINWIN_SDK_LIB_PATH – see vÀÀrtus viitab muutuja SDK_LIB_PATH, ei tasu muretseda, et teil ei ole sellist sĂŒsteemimuutujat deklareeritud, hetkel, kui kĂ€ivitame checked build DDK-st, deklareeritakse see muutuja ja viitab vajalikele raamatukogudele.
  • SOURCES – nimekiri teie programmi lĂ€htekoodidest.
  • INCLUDES – pĂ€ised, mis on vajalikud kompileerimiseks. Siin tavaliselt mÀÀratakse tee failidele, mis tulevad koos DDK-ga, kuid vĂ”ite lisada ka muid.
  • TARGETLIBS – nimekiri raamatukogudest, mis tuleb siduda.
  • USE_NTDLL – kohustuslik vĂ€li, mis tuleb seada vÀÀrtusele 1. Sellel on ilmselged pĂ”hjused.
  • USER_C_FLAGS – kĂ”ik lipud, mida saab kasutada eeltöötlusdirektiivides rakenduse koodi ettevalmistamisel.

Seega, et koostada, peame kĂ€ivitama x86 (vĂ”i x64) Checked Build, muutma töökausta projekti kaustaks ja tĂ€itma Build kĂ€su. Tulemuses, mis on ekraanipildil, on nĂ€ha, et meil on loodud ĂŒks kĂ€ivitatav fail.

Windowsi natiivrakendused ja Acronis Active Restore teenus

Seda faili ei saa lihtsalt kĂ€ivitada, sĂŒsteem teavitab meid ja saadab arvama jĂ€rgmise veateate:

Windowsi natiivrakendused ja Acronis Active Restore teenus

Kuidas kÀivitada natiivset rakendust?

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

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Seansi haldur kÀivitab programmid jÀrjestikku sellest loendist. KÀivitatavad failid otsitakse seansi halduri poolt kaustast system32. Registri vÔtme vÀÀrtuse formaat on jÀrgmine:

autocheck autochk *MyNative

VÀÀrtus peab olema kuuekĂŒmnendformaadis, mitte tavalises ASCII-s, seetĂ”ttu on ĂŒlaltoodud vĂ”tme formaat:

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 konvertimiseks vÔite kasutada veebiteenust, nÀiteks seda.

Windowsi natiivrakendused ja Acronis Active Restore teenus
KokkuvÔttes, et kÀivitada natiivne rakendus, peame me:

  1. Kopeerima kÀivitatava faili kausta system32
  2. Lisama registrisse vÔtme
  3. TaaskÀivitage masin

Kasutamiseks on siin valmis skript natiivse rakenduse installimiseks:

install.bat

@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Natiivne nÀidis installitud
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 kasutajate valimisekraani ilmumist, nÀeme jÀrgmisi pilte:

Windowsi natiivrakendused ja Acronis Active Restore teenus

KokkuvÔte

Selle vĂ€ikese rakenduse nĂ€itel oleme veendunud, et natiivse rakenduse kĂ€ivitamine Windowsi tasemel on tĂ€iesti vĂ”imalik. JĂ€tkame koos Innopolis Ülikooli meestega teenuse loomist, mis algatab suhtlemisprotsessi draiveriga palju varem kui meie projekti eelmine versioon. Ja koos win32 keskustega on mĂ”istlik juhtimise andmine tĂ€ieliku teenuse arendamiseks, mis oli juba vĂ€lja töötatud (selle kohta rohkem) siit).

JĂ€rgmises artiklis kĂ€sitleme veel ĂŒhte Active Restore'i teenuse komponenti, nimelt UEFI draivereid. JĂ€lgige meie blogi, et mitte jÀÀda ilma jĂ€rgmisest postitusest.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster