Windows Native հավելվածներ և Acronis Active Restore ծառայություն

Այսօր մենք շարունակում ենք պատմությունն այն մասին, թե ինչպես ենք մենք, Իննոպոլիսի համալսարանի տղաների հետ միասին, զարգացնում ենք Active Restore տեխնոլոգիան, որը թույլ կտա օգտվողին հնարավորինս շուտ սկսել աշխատել իր մեքենայի վրա ձախողումից հետո: Մենք կխոսենք մայրենի Windows հավելվածների մասին, ներառյալ դրանց ստեղծման և գործարկման առանձնահատկությունները: Ստորև բերված է մի փոքր մեր նախագծի մասին, ինչպես նաև գործնական ուղեցույց, թե ինչպես գրել հայրենի հավելվածներ:

Windows Native հավելվածներ և Acronis Active Restore ծառայություն

Նախորդ գրառումներում մենք արդեն խոսել ենք այն մասին, թե ինչ է դա Ակտիվ վերականգնում, և ինչպես են զարգանում Իննոպոլիսի ուսանողները ծառայություն. Այսօր ես ուզում եմ կենտրոնանալ հայրենի հավելվածների վրա, որոնց մակարդակին մենք ցանկանում ենք «թաղել» մեր ակտիվ վերականգնման ծառայությունը։ Եթե ​​ամեն ինչ ստացվի, ապա մենք կկարողանանք.

  • Գործարկեք ծառայությունը շատ ավելի վաղ
  • Կապվեք ամպի հետ, որտեղ պահուստավորումը գտնվում է շատ ավելի վաղ
  • Շատ ավելի վաղ հասկանալու համար, թե ինչ ռեժիմում է համակարգը՝ նորմալ բեռնում կամ վերականգնում
  • Նախապես վերականգնելու համար շատ ավելի քիչ ֆայլեր
  • Թույլ տվեք օգտվողին ավելի արագ սկսել:

Ինչ է ամեն դեպքում հայրենի հավելվածը:

Այս հարցին պատասխանելու համար եկեք նայենք զանգերի հաջորդականությանը, որ կատարում է համակարգը, օրինակ, եթե ծրագրավորողն իր հավելվածում փորձում է ֆայլ ստեղծել։

Windows Native հավելվածներ և Acronis Active Restore ծառայություն
Պավել Յոսիֆովիչ - Windows Kernel Programming (2019)

Ծրագրավորողն օգտագործում է ֆունկցիան Ստեղծել Ֆայլ, որը հայտարարված է fileapi.h վերնագրի ֆայլում և ներդրված Kernel32.dll-ում։ Այնուամենայնիվ, այս ֆունկցիան ինքնին չի ստեղծում ֆայլը, այն միայն ստուգում է մուտքային փաստարկները և կանչում է գործառույթը NtCreateFile (Nt նախածանցը պարզապես ցույց է տալիս, որ ֆունկցիան բնիկ է): Այս գործառույթը հայտարարված է winternl.h վերնագրի ֆայլում և ներդրված ntdll.dll-ում: Այն պատրաստվում է ցատկել միջուկային տարածություն, որից հետո համակարգային կոչ է անում ֆայլ ստեղծելու համար: Այս դեպքում պարզվում է, որ Kernel32-ը պարզապես փաթաթող է Ntdll-ի համար։ Պատճառներից մեկը, թե ինչու դա արվեց, այն է, որ Microsoft-ն այսպիսով հնարավորություն ունի փոխել հայրենի աշխարհի գործառույթները, բայց չդիպչել ստանդարտ միջերեսներին: Microsoft-ը խորհուրդ չի տալիս ուղղակիորեն զանգահարել հայրենի գործառույթները և չի փաստաթղթավորում դրանց մեծ մասը: Ի դեպ, կարելի է գտնել չփաստաթղթավորված գործառույթներ այստեղ.

Native հավելվածների հիմնական առավելությունն այն է, որ ntdll-ը բեռնվում է համակարգում շատ ավելի վաղ, քան kernel32-ը։ Սա տրամաբանական է, քանի որ kernel32-ը պահանջում է ntdll-ն աշխատելու համար: Արդյունքում, հավելվածները, որոնք օգտագործում են բնիկ ֆունկցիաներ, կարող են սկսել աշխատել շատ ավելի վաղ:

Այսպիսով, Windows Native Applications-ը ծրագրեր են, որոնք կարող են սկսել Windows-ի բեռնման սկզբում: Նրանք օգտագործում են ՄԻԱՅՆ ntdll-ի գործառույթները: Նման դիմումի օրինակ. autochk ով կատարում է chkdisk կոմունալ հիմնական ծառայությունները սկսելուց առաջ սկավառակը սխալների համար ստուգելու համար: Սա հենց այն մակարդակն է, որը մենք ցանկանում ենք, որ լինի մեր Active Restore-ը:

Ի՞նչ է մեզ պետք։

  • DDK (Driver Development Kit), այժմ հայտնի է նաև որպես WDK 7 (Windows Driver Kit):
  • Վիրտուալ մեքենա (օրինակ, Windows 7 x64)
  • Անհրաժեշտ չէ, բայց վերնագրի ֆայլերը, որոնք կարելի է ներբեռնել, կարող են օգնել այստեղ

Ի՞նչ կա կոդում:

Եկեք մի փոքր պարապենք և, օրինակ, գրենք մի փոքրիկ հավելված, որը.

  1. Ցուցադրում է հաղորդագրություն էկրանին
  2. Որոշակի հիշողություն է հատկացնում
  3. Սպասում է ստեղնաշարի մուտքագրմանը
  4. Ազատում է օգտագործված հիշողությունը

Մայրենի հավելվածներում մուտքի կետը հիմնական կամ winmain չէ, այլ NtProcessStartup ֆունկցիան, քանի որ մենք իրականում ուղղակիորեն գործարկում ենք նոր գործընթացներ համակարգում:

Սկսենք էկրանին հաղորդագրություն ցուցադրելով: Դրա համար մենք ունենք հայրենի գործառույթ NtDisplayString, որը որպես արգումենտ ընդունում է UNICODE_STRING կառուցվածքի օբյեկտի ցուցիչը: RtlInitUnicodeString-ը կօգնի մեզ այն սկզբնավորել: Արդյունքում, էկրանին տեքստ ցուցադրելու համար մենք կարող ենք գրել այս փոքրիկ ֆունկցիան.

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

Քանի որ մեզ հասանելի են միայն ntdll-ի գործառույթները, իսկ հիշողության մեջ այլ գրադարաններ պարզապես չկան, մենք անպայման խնդիրներ կունենանք հիշողությունը տեղաբաշխելու հետ կապված: Նոր օպերատորը դեռ գոյություն չունի (քանի որ այն գալիս է C++-ի չափազանց բարձր մակարդակի աշխարհից), և չկա malloc ֆունկցիա (այն պահանջում է աշխատաժամանակի C գրադարաններ): Իհարկե, դուք կարող եք օգտագործել միայն կույտ: Բայց եթե մեզ անհրաժեշտ է դինամիկ կերպով տեղաբաշխել հիշողությունը, մենք ստիպված կլինենք դա անել կույտի վրա (այսինքն՝ կույտ): Ուրեմն եկեք մեզ համար մի կույտ ստեղծենք և հիշողություն վերցնենք դրանից, երբ դրա կարիքը ունենանք:

Ֆունկցիան հարմար է այս առաջադրանքի համար RtlCreateHeap. Հաջորդը, օգտագործելով RtlAllocateHeap և RtlFreeHeap, մենք կզբաղեցնենք և կազատենք հիշողությունը, երբ դա մեզ անհրաժեշտ լինի:

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);

Եկեք անցնենք ստեղնաշարի մուտքագրման սպասմանը:

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

Մեզ միայն պետք է օգտագործել NtReadFile բաց սարքի վրա և սպասեք, մինչև ստեղնաշարը մեզ վերադարձնի որևէ սեղմում: Եթե ​​ESC ստեղնը սեղմվի, մենք կշարունակենք աշխատել: Սարքը բացելու համար մեզ անհրաժեշտ կլինի զանգահարել NtCreateFile ֆունկցիան (մեզ անհրաժեշտ կլինի բացել DeviceKeyboardClass0): Մենք էլ կզանգենք NtCreateEventսպասման օբյեկտը սկզբնավորելու համար: Մենք ինքներս կհայտարարենք KEYBOARD_INPUT_DATA կառուցվածքը, որը ներկայացնում է ստեղնաշարի տվյալները: Սա կհեշտացնի մեր աշխատանքը:

Մայրենի հավելվածն ավարտվում է ֆունկցիայի կանչով NtTerminateProcessքանի որ մենք պարզապես սպանում ենք մեր սեփական գործընթացը:

Մեր փոքր հավելվածի ամբողջ կոդը.

#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: Մենք հեշտությամբ կարող ենք օգտագործել DbgBreakPoint() ֆունկցիան մեր կոդի մեջ՝ այն դադարեցնելու վրիպազերծիչում։ Ճիշտ է, միջուկի վրիպազերծման համար ձեզ հարկավոր է միացնել WinDbg-ը վիրտուալ մեքենային: Հրահանգներ, թե ինչպես դա անել, կարելի է գտնել այստեղ կամ պարզապես օգտագործել Վիրտուալ KD.

Կազմում և հավաքում

Մայրենի հավելված ստեղծելու ամենահեշտ ձևը օգտագործելն է DDK (Driver Development Kit): Մեզ անհրաժեշտ է հնագույն յոթերորդ տարբերակը, քանի որ հետագա տարբերակները մի փոքր այլ մոտեցում ունեն և սերտորեն համագործակցում են Visual Studio-ի հետ: Եթե ​​մենք օգտագործում ենք DDK-ն, ապա մեր նախագծին անհրաժեշտ են միայն Makefile և աղբյուրներ:

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

աղբյուրները:

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-ը կլինի նույնը, բայց եկեք մի փոքր ավելի մանրամասն նայենք աղբյուրներին: Այս ֆայլը նշում է ձեր ծրագրի աղբյուրները (.c ֆայլերը), կառուցման ընտրանքները և այլ պարամետրեր:

  • TARGETNAME – գործարկվող ֆայլի անվանումը, որը պետք է ստեղծվի վերջում:
  • TARGETTYPE – գործարկվող ֆայլի տեսակ, այն կարող է լինել դրայվեր (.sys), ապա դաշտի արժեքը պետք է լինի DRIVER, եթե գրադարան (.lib), ապա արժեքը՝ LIBRARY: Մեր դեպքում մեզ անհրաժեշտ է գործարկվող ֆայլ (.exe), ուստի արժեքը սահմանում ենք PROGRAM:
  • UMTYPE – այս դաշտի համար հնարավոր արժեքներ՝ վահանակ կոնսոլային հավելվածի համար, պատուհաններ՝ պատուհանների ռեժիմում աշխատելու համար: Բայց մենք պետք է նշենք nt՝ հայրենի հավելված ստանալու համար։
  • BUFFER_OVERFLOW_CHECKS – ստուգում ենք բուֆերի գերհոսքի բուրգը, ցավոք, դա մեր դեպքը չէ, մենք այն անջատում ենք:
  • MINWIN_SDK_LIB_PATH – այս արժեքը վերաբերում է SDK_LIB_PATH փոփոխականին, մի անհանգստացեք, որ դուք չունեք նման համակարգի փոփոխական հայտարարագրված, երբ մենք գործարկենք ստուգված build-ը DDK-ից, այս փոփոխականը կհայտարարվի և ցույց կտա անհրաժեշտ գրադարանները:
  • ԱՂԲՅՈՒՐՆԵՐ – ձեր ծրագրի աղբյուրների ցանկը:
  • ՆԵՐԱՌՈՒՄ Է – վերնագրի ֆայլերը, որոնք անհրաժեշտ են հավաքման համար: Այստեղ նրանք սովորաբար նշում են DDK-ով ուղեկցվող ֆայլերի ուղին, բայց դուք կարող եք լրացուցիչ նշել ցանկացած այլ:
  • TARGETLIBS – գրադարանների ցանկ, որոնք պետք է կապվեն:
  • USE_NTDLL-ը պարտադիր դաշտ է, որը հասկանալի պատճառներով պետք է սահմանվի 1:
  • USER_C_FLAGS – ցանկացած դրոշակ, որը կարող եք օգտագործել նախապրոցեսորային հրահանգներում, երբ պատրաստվում եք հավելվածի կոդը:

Այսպիսով, կառուցելու համար մենք պետք է գործարկենք x86 (կամ x64) Checked Build-ը, փոխենք աշխատանքային գրացուցակը նախագծի թղթապանակով և գործարկենք Build հրամանը: Սքրինշոթի արդյունքը ցույց է տալիս, որ մենք ունենք մեկ գործարկվող ֆայլ:

Windows Native հավելվածներ և Acronis Active Restore ծառայություն

Այս ֆայլը չի ​​կարող այդքան հեշտությամբ գործարկվել, համակարգը հայհոյում է և ուղարկում մեզ մտածելու իր վարքագծի մասին հետևյալ սխալով.

Windows Native հավելվածներ և Acronis Active Restore ծառայություն

Ինչպե՞ս գործարկել հայրենի հավելված:

Երբ autochk-ը սկսվում է, ծրագրերի գործարկման հաջորդականությունը որոշվում է ռեեստրի բանալի արժեքով.

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Նիստի մենեջերը մեկ առ մեկ իրականացնում է ծրագրեր այս ցանկից: Նիստի կառավարիչն ինքն է փնտրում գործարկվող ֆայլերը system32 գրացուցակում: Ռեեստրի բանալու արժեքի ձևաչափը հետևյալն է.

autocheck autochk *MyNative

Արժեքը պետք է լինի տասնվեցական ձևաչափով, այլ ոչ թե սովորական ASCII, այնպես որ վերևում ցուցադրված բանալին կլինի հետևյալ ձևաչափով.

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

Վերնագիրը փոխարկելու համար կարող եք օգտվել առցանց ծառայությունից, օրինակ. այս.

Windows Native հավելվածներ և Acronis Active Restore ծառայություն
Պարզվում է, որ հայրենի հավելված գործարկելու համար մեզ անհրաժեշտ է.

  1. Պատճենեք գործարկվող ֆայլը system32 թղթապանակում
  2. Ավելացնել բանալի ռեեստրում
  3. Վերագործարկեք մեքենան

Հարմարության համար այստեղ կա պատրաստի սկրիպտ՝ հայրենի հավելված տեղադրելու համար.

install.bat

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

ավելացնել.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

Տեղադրվելուց և վերագործարկվելուց հետո, նույնիսկ մինչև օգտագործողի ընտրության էկրանը հայտնվի, մենք կստանանք հետևյալ պատկերը.

Windows Native հավելվածներ և Acronis Active Restore ծառայություն

Լրիվ

Օգտագործելով նման փոքր հավելվածի օրինակը՝ մենք համոզվեցինք, որ միանգամայն հնարավոր է հավելվածը գործարկել Windows Native մակարդակով։ Հաջորդը, Իննոպոլիսի համալսարանի տղաները և ես կշարունակենք կառուցել մի ծառայություն, որը կսկսի վարորդի հետ փոխգործակցության գործընթացը շատ ավելի վաղ, քան մեր նախագծի նախորդ տարբերակում: Իսկ win32 shell-ի հայտնվելով տրամաբանական կլիներ հսկողությունը փոխանցել արդեն իսկ մշակված լիարժեք ծառայությանը (ավելին այս մասին այստեղ).

Հաջորդ հոդվածում մենք կանդրադառնանք Active Restore ծառայության մեկ այլ բաղադրիչի, այն է՝ UEFI դրայվերին։ Բաժանորդագրվեք մեր բլոգին, որպեսզի բաց չթողնեք հաջորդ գրառումը:

Source: www.habr.com

Добавить комментарий