Windows Native Applications at serbisyo ng Acronis Active Restore

Ngayon ay ipinagpapatuloy namin ang kuwento kung paano kami, kasama ng mga lalaki mula sa Innopolis University, ay gumagawa ng teknolohiyang Active Restore upang payagan ang user na magsimulang magtrabaho sa kanilang makina sa lalong madaling panahon pagkatapos ng pagkabigo. Pag-uusapan natin ang tungkol sa mga katutubong application ng Windows, kabilang ang mga tampok ng kanilang paglikha at paglulunsad. Sa ibaba ng hiwa ay kaunti tungkol sa aming proyekto, pati na rin ang isang praktikal na gabay sa kung paano magsulat ng mga katutubong application.

Windows Native Applications at serbisyo ng Acronis Active Restore

Sa mga nakaraang post ay napag-usapan na natin kung ano ito Aktibong Ibalik, at kung paano umuunlad ang mga mag-aaral mula sa Innopolis serbisyo. Ngayon gusto kong tumuon sa mga katutubong aplikasyon, sa antas kung saan gusto naming "ilibing" ang aming aktibong serbisyo sa pagbawi. Kung maayos ang lahat, magagawa nating:

  • Ilunsad ang serbisyo mismo nang mas maaga
  • Makipag-ugnayan sa cloud kung saan matatagpuan ang backup nang mas maaga
  • Mas maaga upang maunawaan kung anong mode ang system - normal na boot o pagbawi
  • Mas kaunting mga file upang mabawi nang maaga
  • Payagan ang user na makapagsimula nang mas mabilis.

Ano pa rin ang katutubong app?

Upang masagot ang tanong na ito, tingnan natin ang pagkakasunud-sunod ng mga tawag na ginagawa ng system, halimbawa, kung ang isang programmer sa kanyang aplikasyon ay sumusubok na lumikha ng isang file.

Windows Native Applications at serbisyo ng Acronis Active Restore
Pavel Yosifovich - Windows Kernel Programming (2019)

Ginagamit ng programmer ang function CreateFile, na idineklara sa header file na fileapi.h at ipinatupad sa Kernel32.dll. Gayunpaman, ang function na ito mismo ay hindi lumilikha ng file, sinusuri lamang nito ang mga argumento ng input at tinawag ang function NtCreateFile (Ang prefix na Nt ay nagpapahiwatig lamang na ang function ay native). Idineklara ang function na ito sa winternl.h header file at ipinatupad sa ntdll.dll. Naghahanda itong tumalon sa nuclear space, pagkatapos nito ay gumawa ng system call para gumawa ng file. Sa kasong ito, lumalabas na ang Kernel32 ay isang wrapper lamang para sa Ntdll. Isa sa mga dahilan kung bakit ito ginawa ay ang Microsoft sa gayon ay may kakayahang baguhin ang mga function ng katutubong mundo, ngunit hindi hawakan ang mga karaniwang interface. Hindi inirerekomenda ng Microsoft ang direktang pagtawag sa mga native na function at hindi idodokumento ang karamihan sa mga ito. Sa pamamagitan ng paraan, ang mga hindi dokumentadong function ay matatagpuan dito.

Ang pangunahing bentahe ng mga katutubong aplikasyon ay ang ntdll ay na-load sa system nang mas maaga kaysa sa kernel32. Ito ay lohikal, dahil ang kernel32 ay nangangailangan ng ntdll upang gumana. Bilang resulta, ang mga application na gumagamit ng mga native na function ay maaaring magsimulang gumana nang mas maaga.

Kaya, ang Windows Native Applications ay mga program na maaaring magsimula nang maaga sa Windows boot. Gumagamit LAMANG sila ng mga function mula sa ntdll. Isang halimbawa ng naturang aplikasyon: autochk kung sino ang gumaganap chkdisk utility upang suriin ang disk para sa mga error bago simulan ang mga pangunahing serbisyo. Ito ang eksaktong antas na gusto naming maging ang aming Active Restore.

Ano ang kailangan namin?

  • DDK (Driver Development Kit), na kilala rin ngayon bilang WDK 7 (Windows Driver Kit).
  • Virtual machine (halimbawa, Windows 7 x64)
  • Hindi kinakailangan, ngunit maaaring makatulong ang mga file ng header na maaaring ma-download dito

Ano ang nasa code?

Magsanay tayo ng kaunti at, halimbawa, sumulat ng isang maliit na application na:

  1. Nagpapakita ng mensahe sa screen
  2. Naglalaan ng ilang memorya
  3. Naghihintay para sa input ng keyboard
  4. Nagpapalaya ng ginamit na memorya

Sa mga katutubong application, ang entry point ay hindi pangunahing o winmain, ngunit ang function na NtProcessStartup, dahil direkta kaming naglulunsad ng mga bagong proseso sa system.

Magsimula tayo sa pamamagitan ng pagpapakita ng mensahe sa screen. Para dito mayroon kaming katutubong function NtDisplayString, na kumukuha bilang argumento ng isang pointer sa isang UNICODE_STRING structure object. Tutulungan kami ng RtlInitUnicodeString na simulan ito. Bilang resulta, upang ipakita ang teksto sa screen maaari naming isulat ang maliit na function na ito:

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

Dahil ang mga function lamang mula sa ntdll ang magagamit sa amin, at wala pang ibang mga aklatan sa memorya, tiyak na magkakaroon kami ng mga problema sa kung paano maglaan ng memorya. Ang bagong operator ay hindi pa umiiral (dahil ito ay nagmula sa masyadong mataas na antas ng mundo ng C++), at walang malloc function (ito ay nangangailangan ng runtime C library). Siyempre, maaari ka lamang gumamit ng isang stack. Ngunit kung kailangan nating dynamic na maglaan ng memorya, kakailanganin nating gawin ito sa heap (i.e. heap). Kaya't gumawa tayo ng isang bunton para sa ating sarili at kunin ang memorya mula dito sa tuwing kailangan natin ito.

Ang pag-andar ay angkop para sa gawaing ito RtlCreateHeap. Susunod, gamit ang RtlAllocateHeap at RtlFreeHeap, sasakupin namin at libreng memorya kapag kailangan namin ito.

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

Lumipat tayo sa paghihintay para sa input ng keyboard.

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

Ang kailangan lang natin ay gamitin NtReadFile sa isang bukas na device, at maghintay hanggang sa ibalik ng keyboard ang anumang pindutin sa amin. Kung pinindot ang ESC key, magpapatuloy kami sa pagtatrabaho. Upang buksan ang device, kakailanganin naming tawagan ang function na NtCreateFile (kailangan naming buksan ang DeviceKeyboardClass0). Tatawag din kami NtCreateEventpara simulan ang wait object. Kami mismo ang magdedeklara ng KEYBOARD_INPUT_DATA structure, na kumakatawan sa data ng keyboard. Mapapadali nito ang ating trabaho.

Ang katutubong application ay nagtatapos sa isang function na tawag NtTerminateProcessdahil pinapatay lang natin ang sarili nating proseso.

Lahat ng code para sa aming maliit na application:

#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: Madali naming magagamit ang function na DbgBreakPoint() sa aming code upang ihinto ito sa debugger. Totoo, kakailanganin mong ikonekta ang WinDbg sa isang virtual machine para sa pag-debug ng kernel. Ang mga tagubilin kung paano gawin ito ay matatagpuan dito o gamitin lang VirtualKD.

Compilation at pagpupulong

Ang pinakamadaling paraan upang bumuo ng isang katutubong application ay ang paggamit DDK (Driver Development Kit). Kailangan namin ang sinaunang ikapitong bersyon, dahil ang mga susunod na bersyon ay may bahagyang naiibang diskarte at gumagana nang malapit sa Visual Studio. Kung gagamitin namin ang DDK, kailangan lang ng aming proyekto ang Makefile at mga mapagkukunan.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

mga mapagkukunan:

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

Ang iyong Makefile ay magiging eksaktong pareho, ngunit tingnan natin ang mga mapagkukunan nang mas detalyado. Tinutukoy ng file na ito ang mga source ng iyong program (.c file), mga opsyon sa build, at iba pang mga parameter.

  • TARGETNAME – ang pangalan ng executable file na dapat gawin sa dulo.
  • TARGETTYPE – uri ng executable file, maaari itong maging driver (.sys), kung gayon ang field na value ay DRIVER, kung library (.lib), kung gayon ang value ay LIBRARY. Sa aming kaso, kailangan namin ng executable file (.exe), kaya itinakda namin ang halaga sa PROGRAM.
  • UMTYPE – posibleng mga halaga para sa field na ito: console para sa isang console application, mga window para sa pagtatrabaho sa windowed mode. Ngunit kailangan nating tukuyin ang nt upang makakuha ng katutubong aplikasyon.
  • BUFFER_OVERFLOW_CHECKS – sinusuri ang stack para sa buffer overflow, sa kasamaang palad ay hindi ang kaso namin, pinapatay namin ito.
  • MINWIN_SDK_LIB_PATH – ang value na ito ay tumutukoy sa SDK_LIB_PATH variable, huwag mag-alala na wala kang ganyang system variable na idineklara, kapag nagpatakbo kami ng checked build mula sa DDK, ang variable na ito ay idedeklara at ituturo ang mga kinakailangang library.
  • SOURCES – isang listahan ng mga source para sa iyong programa.
  • KASAMA – mga file ng header na kinakailangan para sa pagpupulong. Dito karaniwang ipinapahiwatig nila ang landas patungo sa mga file na kasama ng DDK, ngunit maaari mong dagdagan ang anumang iba pa.
  • TARGETLIBS – listahan ng mga aklatan na kailangang i-link.
  • Ang USE_NTDLL ay isang kinakailangang field na dapat itakda sa 1 para sa mga malinaw na dahilan.
  • USER_C_FLAGS – anumang mga flag na magagamit mo sa mga preprocessor na direktiba kapag naghahanda ng code ng aplikasyon.

Kaya para makabuo, kailangan nating patakbuhin ang x86 (o x64) Checked Build, palitan ang gumaganang direktoryo sa folder ng proyekto at patakbuhin ang utos na Build. Ang resulta sa screenshot ay nagpapakita na mayroon kaming isang executable file.

Windows Native Applications at serbisyo ng Acronis Active Restore

Ang file na ito ay hindi madaling mailunsad, ang system ay nagmumura at nagpapadala sa amin upang isipin ang tungkol sa pag-uugali nito na may sumusunod na error:

Windows Native Applications at serbisyo ng Acronis Active Restore

Paano maglunsad ng katutubong application?

Kapag nagsimula ang autochk, ang pagkakasunud-sunod ng startup ng mga programa ay tinutukoy ng halaga ng registry key:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Ang session manager ay isa-isang nagpapatupad ng mga programa mula sa listahang ito. Hinahanap ng session manager ang mga executable file mismo sa system32 directory. Ang format ng halaga ng registry key ay ang mga sumusunod:

autocheck autochk *MyNative

Ang halaga ay dapat nasa hexadecimal na format, hindi ang karaniwang ASCII, kaya ang key na ipinapakita sa itaas ay nasa format:

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

Upang i-convert ang pamagat, maaari kang gumamit ng online na serbisyo, halimbawa, ito.

Windows Native Applications at serbisyo ng Acronis Active Restore
Lumalabas na upang ilunsad ang isang katutubong application, kailangan namin:

  1. Kopyahin ang executable file sa system32 folder
  2. Magdagdag ng susi sa pagpapatala
  3. I-reboot ang makina

Para sa kaginhawahan, narito ang isang handa na script para sa pag-install ng isang katutubong application:

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

Pagkatapos ng pag-install at pag-reboot, bago pa man lumitaw ang screen ng pagpili ng user, makukuha namin ang sumusunod na larawan:

Windows Native Applications at serbisyo ng Acronis Active Restore

Kabuuan

Gamit ang halimbawa ng tulad ng isang maliit na application, kami ay kumbinsido na ito ay lubos na posible na patakbuhin ang application sa Windows Native na antas. Susunod, ang mga lalaki mula sa Innopolis University at ako ay magpapatuloy na bumuo ng isang serbisyo na magpapasimula sa proseso ng pakikipag-ugnayan sa driver nang mas maaga kaysa sa nakaraang bersyon ng aming proyekto. At sa pagdating ng win32 shell, magiging lohikal na ilipat ang kontrol sa isang ganap na serbisyo na binuo na (higit pa tungkol dito dito).

Sa susunod na artikulo tatalakayin natin ang isa pang bahagi ng serbisyo ng Active Restore, katulad ng driver ng UEFI. Mag-subscribe sa aming blog upang hindi mo makaligtaan ang susunod na post.

Pinagmulan: www.habr.com

Magdagdag ng komento