Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums

Å odien turpinām stāstu par to, kā mēs kopā ar puiÅ”iem no Innopolis Universitātes attÄ«stām Active Restore tehnoloÄ£iju, lai pēc kļūmes lietotājs pēc iespējas ātrāk varētu sākt strādāt pie savas maŔīnas. Mēs runāsim par vietējām Windows lietojumprogrammām, tostarp to izveides un palaiÅ”anas funkcijām. Zem griezuma ir nedaudz par mÅ«su projektu, kā arÄ« praktisks ceļvedis, kā rakstÄ«t vietējās lietojumprogrammas.

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums

IepriekŔējos ierakstos mēs jau runājām par to, kas tas ir AktÄ«vā atjaunoÅ”ana, un kā attÄ«stās Innopolis studenti pakalpojumu. Å odien es vēlos koncentrēties uz vietējām lietojumprogrammām, kuru lÄ«menÄ« mēs vēlamies ā€œapglabātā€ mÅ«su aktÄ«vās atkopÅ”anas pakalpojumu. Ja viss izdosies, tad mēs varēsim:

  • Palaidiet pakalpojumu daudz agrāk
  • Daudz agrāk sazinieties ar mākoni, kurā atrodas dublējums
  • Daudz agrāk, lai saprastu, kādā režīmā sistēma atrodas - parastā sāknÄ“Å”ana vai atkopÅ”ana
  • IepriekÅ” atkopjamo failu skaits ir daudz mazāks
  • Ä»aujiet lietotājam sākt darbu vēl ātrāk.

Kas vispār ir vietējā lietotne?

Lai atbildētu uz Å”o jautājumu, apskatÄ«sim izsaukumu secÄ«bu, ko sistēma veic, piemēram, ja programmētājs savā lietojumprogrammā mēģina izveidot failu.

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums
Pāvels Josifovičs ā€” Windows kodola programmÄ“Å”ana (2019)

Programmētājs izmanto funkciju Izveidot failu, kas ir deklarēts galvenes failā fileapi.h un ieviests programmā Kernel32.dll. Tomēr Ŕī funkcija pati neveido failu, tā tikai pārbauda ievades argumentus un izsauc funkciju NtCreateFile (prefikss Nt tikai norāda, ka funkcija ir vietējā). Å Ä« funkcija ir deklarēta galvenes failā winternl.h un ieviesta failā ntdll.dll. Tas gatavojas pārlēkÅ”anai kodoltelpā, pēc tam veic sistēmas izsaukumu, lai izveidotu failu. Å ajā gadÄ«jumā izrādās, ka Kernel32 ir tikai Ntdll iesaiņojums. Viens no iemesliem, kāpēc tas tika darÄ«ts, ir tas, ka Microsoft tādējādi var mainÄ«t vietējās pasaules funkcijas, bet nepieskarties standarta saskarnēm. Microsoft neiesaka tieÅ”i izsaukt vietējās funkcijas un lielāko daļu no tām nedokumentē. Starp citu, var atrast nedokumentētas funkcijas Å”eit.

Galvenā vietējo lietojumprogrammu priekÅ”rocÄ«ba ir tā, ka ntdll tiek ielādēts sistēmā daudz agrāk nekā kernel32. Tas ir loÄ£iski, jo kernel32 darbam ir nepiecieÅ”ams ntdll. Rezultātā lietojumprogrammas, kas izmanto vietējās funkcijas, var sākt darboties daudz agrāk.

Tādējādi Windows vietējās lietojumprogrammas ir programmas, kuras var palaist agri Windows sāknÄ“Å”anas laikā. Viņi izmanto TIKAI ntdll funkcijas. Šādas lietojumprogrammas piemērs: autochk kurÅ” uzstājas chkdisk utilÄ«ta lai pirms galveno pakalpojumu palaiÅ”anas pārbaudÄ«tu, vai diskā nav kļūdu. Tas ir tieÅ”i tāds lÄ«menis, kādu mēs vēlamies, lai mÅ«su aktÄ«vā atjaunoÅ”ana bÅ«tu.

Ko mums vajag?

  • DDK (Driver Development Kit), tagad pazÄ«stams arÄ« kā WDK 7 (Windows draiveru komplekts).
  • Virtuālā maŔīna (piemēram, Windows 7 x64)
  • Nav nepiecieÅ”ams, taču var palÄ«dzēt galvenes faili, kurus var lejupielādēt Å”eit

Kas ir kodā?

Nedaudz trenēsimies un, piemēram, uzrakstīsim nelielu pieteikumu, kas:

  1. Parāda ziņojumu ekrānā
  2. PieŔķir daļu atmiņas
  3. Gaida tastatūras ievadi
  4. Atbrīvo izmantoto atmiņu

Vietējās lietojumprogrammās ievades punkts nav galvenais vai winmain, bet gan funkcija NtProcessStartup, jo mēs faktiski tieÅ”i palaižam sistēmā jaunus procesus.

Sāksim ar ziņojuma parādÄ«Å”anu ekrānā. Å im nolÅ«kam mums ir vietējā funkcija NtDisplayString, kas kā argumentu izmanto rādÄ«tāju uz UNICODE_STRING struktÅ«ras objektu. RtlInitUnicodeString palÄ«dzēs mums to inicializēt. Tā rezultātā, lai ekrānā parādÄ«tu tekstu, mēs varam ierakstÄ«t Å”o mazo 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 noteikti bÅ«s problēmas ar atmiņas pieŔķirÅ”anu. Jaunais operators vēl neeksistē (jo tas nāk no pārāk augsta lÄ«meņa C++ pasaules), un nav malloc funkcijas (tam nepiecieÅ”amas izpildlaika C bibliotēkas). Protams, jÅ«s varat izmantot tikai kaudzi. Bet, ja mums ir dinamiski jāpieŔķir atmiņa, mums tas bÅ«s jādara kaudzē (t.i., kaudzÄ«tē). Tāpēc izveidosim sev kaudzi un ņemsim no tās atmiņu ikreiz, kad tas bÅ«s nepiecieÅ”ams.

Funkcija ir piemērota Å”im uzdevumam RtlCreateHeap. Tālāk, izmantojot RtlAllocateHeap un RtlFreeHeap, mēs aizņemsim un atbrÄ«vosim atmiņu, kad tā bÅ«s nepiecieÅ”ama.

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

// create heap in order to allocate memory later
memory = RtlCreateHeap(
  HEAP_GROWABLE, 
  NULL, 
  1000, 
  0, NULL, NULL
);

// allocate buffer of size bufferSize
buffer = RtlAllocateHeap(
  memory, 
  HEAP_ZERO_MEMORY, 
  bufferSize
);

// free buffer (actually not needed because we destroy heap in next step)
RtlFreeHeap(memory, 0, buffer);

RtlDestroyHeap(memory);

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

// 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ērtā ierÄ«cē un pagaidiet, lÄ«dz tastatÅ«ra atgriezÄ«s mums jebkuru nospieÅ”anu. Ja tiek nospiests ESC taustiņŔ, mēs turpināsim darbu. Lai atvērtu ierÄ«ci, mums bÅ«s jāizsauc funkcija NtCreateFile (mums bÅ«s jāatver DeviceKeyboardClass0). Mēs arÄ« piezvanÄ«sim NtCreateEventlai inicializētu gaidÄ«Å”anas objektu. Mēs paÅ”i deklarēsim KEYBOARD_INPUT_DATA struktÅ«ru, kas atspoguļo tastatÅ«ras datus. Tas atvieglos mÅ«su darbu.

Vietējā lietojumprogramma beidzas ar funkcijas izsaukumu NtTerminateProcessjo mēs vienkārÅ”i nogalinām paÅ”i savu procesu.

Viss mūsu mazās lietojumprogrammas kods:

#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() savā kodā, lai apturētu to atkļūdotājs. Tiesa, jums bÅ«s jāpievieno WinDbg ar virtuālo maŔīnu kodola atkļūdoÅ”anai. NorādÄ«jumus, kā to izdarÄ«t, var atrast Å”eit vai vienkārÅ”i izmantot VirtualKD.

SastādīŔana un montāža

VienkārŔākais veids, kā izveidot vietējo lietojumprogrammu, ir izmantot DDK (Driver Development Kit). Mums ir nepiecieÅ”ama senā septÄ«tā versija, jo jaunākajām versijām ir nedaudz atŔķirÄ«ga pieeja un tās cieÅ”i sadarbojas ar Visual Studio. Ja mēs izmantojam DDK, tad mÅ«su projektam ir nepiecieÅ”ams tikai Makefile un avoti.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

avoti:

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, taču apskatÄ«sim avotus nedaudz sÄ«kāk. Å ajā failā ir norādÄ«ti jÅ«su programmas avoti (.c faili), bÅ«vējuma opcijas un citi parametri.

  • TARGETNAME ā€“ izpildāmā faila nosaukums, kas beigās jāizveido.
  • 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 ir LIBRARY. MÅ«su gadÄ«jumā mums ir nepiecieÅ”ams izpildāmais fails (.exe), tāpēc mēs iestatām vērtÄ«bu uz PROGRAMMA.
  • UMTYPE ā€“ Ŕī lauka iespējamās vērtÄ«bas: konsole konsoles lietojumprogrammai, logi darbam logu režīmā. Bet mums ir jānorāda nt, lai iegÅ«tu vietējo lietojumprogrammu.
  • BUFFER_OVERFLOW_CHECKS ā€“ pārbaudot steku, vai nav pārpildÄ«ts buferis, diemžēl ne mÅ«su gadÄ«jumā, mēs to izslēdzam.
  • MINWIN_SDK_LIB_PATH ā€“ Ŕī vērtÄ«ba attiecas uz mainÄ«go SDK_LIB_PATH, neuztraucieties, ka jums nav deklarēts Ŕāds sistēmas mainÄ«gais, kad mēs izpildÄ«sim pārbaudÄ«to bÅ«vējumu no DDK, Å”is mainÄ«gais tiks deklarēts un norādÄ«s uz nepiecieÅ”amajām bibliotēkām.
  • AVOTI ā€” jÅ«su programmas avotu saraksts.
  • IEKÄ»AUJ ā€“ galvenes faili, kas nepiecieÅ”ami montāžai. Å eit tie parasti norāda ceļu uz failiem, kas tiek piegādāti kopā ar DDK, bet jÅ«s varat papildus norādÄ«t citus.
  • TARGETLIBS ā€“ to bibliotēku saraksts, kuras ir jāsaista.
  • USE_NTDLL ir obligāts lauks, kas acÄ«mredzamu iemeslu dēļ jāiestata uz 1.
  • USER_C_FLAGS ā€“ visi karodziņi, kurus varat izmantot priekÅ”apstrādātāja direktÄ«vās, sagatavojot lietojumprogrammas kodu.

Tātad, lai izveidotu, mums ir jāpalaiž x86 (vai x64) Checked Build, jāmaina darba direktorijs uz projekta mapi un jāpalaiž komanda Build. Ekrānuzņēmuma rezultāts parāda, ka mums ir viens izpildāms fails.

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums

Å o failu nevar tik vienkārÅ”i palaist, sistēma nolādē un sÅ«ta mums domāt par tā uzvedÄ«bu ar Ŕādu kļūdu:

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums

Kā palaist vietējo lietojumprogrammu?

Kad autochk startē, programmu startÄ“Å”anas secÄ«bu nosaka reÄ£istra atslēgas vērtÄ«ba:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Sesiju pārvaldnieks pa vienam izpilda programmas no Ŕī saraksta. Sesiju pārvaldnieks paÅ”i meklē izpildāmos failus system32 direktorijā. ReÄ£istra atslēgas vērtÄ«bas formāts ir Ŕāds:

autocheck autochk *MyNative

VērtÄ«bai ir jābÅ«t heksadecimālā formātā, nevis parastajā ASCII formātā, tāpēc iepriekÅ” redzamā atslēga bÅ«s Ŕādā 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 pārvērstu nosaukumu, varat izmantot tieÅ”saistes pakalpojumu, piemēram, Å”is.

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums
Izrādās, ka, lai palaistu vietējo lietojumprogrammu, mums ir nepiecieÅ”ams:

  1. Kopējiet izpildāmo failu mapē system32
  2. Pievienojiet reģistram atslēgu
  3. Pārstartējiet maŔīnu

ĒrtÄ«bas labad Å”eit ir gatavs skripts vietējās lietojumprogrammas instalÄ“Å”anai:

install.bat

@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pause

add.reg

REGEDIT4

[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession 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 atsāknÄ“Å”anas, pat pirms tiek parādÄ«ts lietotāja atlases ekrāns, mēs iegÅ«sim Ŕādu attēlu:

Windows vietējās lietojumprogrammas un Acronis Active Restore pakalpojums

Kopsavilkums

Izmantojot Ŕādas nelielas lietojumprogrammas piemēru, mēs pārliecinājāmies, ka ir pilnÄ«gi iespējams palaist lietojumprogrammu Windows Native lÄ«menÄ«. Tālāk mēs ar Innopolis Universitātes puiÅ”iem turpināsim veidot servisu, kas uzsāks mijiedarbÄ«bas procesu ar vadÄ«tāju daudz agrāk nekā mÅ«su projekta iepriekŔējā versijā. Un lÄ«dz ar win32 apvalka parādÄ«Å”anos bÅ«tu loÄ£iski pārcelt vadÄ«bu uz pilnvērtÄ«gu pakalpojumu, kas jau ir izstrādāts (vairāk par to Å”eit).

Nākamajā rakstā mēs pieskarsimies citai aktÄ«vās atjaunoÅ”anas pakalpojuma sastāvdaļai, proti, UEFI draiverim. Abonējiet mÅ«su emuāru, lai nepalaistu garām nākamo ierakstu.

Avots: www.habr.com

Pievieno komentāru