Perkhidmatan Windows Native Applications dan Acronis Active Restore

Hari ini kami meneruskan kisah bagaimana kami, bersama-sama dengan kakitangan dari Universiti Innopolis, membangunkan teknologi Pemulihan Aktif untuk membolehkan pengguna mula bekerja pada mesin mereka secepat mungkin selepas kegagalan. Kami akan bercakap tentang aplikasi Windows asli, termasuk ciri penciptaan dan pelancarannya. Di bawah potongan adalah sedikit tentang projek kami, serta panduan praktikal tentang cara menulis aplikasi asli.

Perkhidmatan Windows Native Applications dan Acronis Active Restore

Dalam entri sebelum ini kita sudah bercakap tentang apa itu Pemulihan Aktif, dan cara pelajar dari Innopolis berkembang perkhidmatan. Hari ini saya ingin memberi tumpuan kepada aplikasi asli, ke tahap yang kami mahu "menguburkan" perkhidmatan pemulihan aktif kami. Jika semuanya berjaya, maka kita akan dapat:

  • Lancarkan perkhidmatan itu sendiri lebih awal
  • Hubungi awan di mana sandaran terletak lebih awal
  • Jauh lebih awal untuk memahami mod apa sistem berada - but normal atau pemulihan
  • Lebih sedikit fail untuk dipulihkan terlebih dahulu
  • Benarkan pengguna bermula dengan lebih pantas.

Apakah apl asli?

Untuk menjawab soalan ini, mari kita lihat urutan panggilan yang dibuat oleh sistem, sebagai contoh, jika pengaturcara dalam aplikasinya cuba mencipta fail.

Perkhidmatan Windows Native Applications dan Acronis Active Restore
Pavel Yosifovich - Pengaturcaraan Kernel Windows (2019)

Pengaturcara menggunakan fungsi tersebut BuatFile, yang diisytiharkan dalam fail pengepala fileapi.h dan dilaksanakan dalam Kernel32.dll. Walau bagaimanapun, fungsi ini sendiri tidak mencipta fail, ia hanya menyemak argumen input dan memanggil fungsi tersebut NtCreateFile (awalan Nt hanya menunjukkan bahawa fungsi itu asli). Fungsi ini diisytiharkan dalam fail pengepala winternl.h dan dilaksanakan dalam ntdll.dll. Ia bersedia untuk melompat ke ruang nuklear, selepas itu ia membuat panggilan sistem untuk mencipta fail. Dalam kes ini, ternyata Kernel32 hanyalah pembungkus untuk Ntdll. Salah satu sebab mengapa ini dilakukan ialah Microsoft mempunyai keupayaan untuk menukar fungsi dunia asal, tetapi tidak menyentuh antara muka standard. Microsoft tidak mengesyorkan memanggil fungsi asli secara langsung dan tidak mendokumenkan kebanyakannya. Dengan cara ini, fungsi tidak berdokumen boleh didapati di sini.

Kelebihan utama aplikasi asli ialah ntdll dimuatkan ke dalam sistem lebih awal daripada kernel32. Ini adalah logik, kerana kernel32 memerlukan ntdll untuk berfungsi. Akibatnya, aplikasi yang menggunakan fungsi asli boleh mula berfungsi lebih awal.

Oleh itu, Aplikasi Asli Windows ialah program yang boleh bermula awal dalam but Windows. Mereka HANYA menggunakan fungsi dari ntdll. Contoh aplikasi sedemikian: autochk yang membuat persembahan utiliti chkdisk untuk menyemak cakera untuk ralat sebelum memulakan perkhidmatan utama. Ini betul-betul tahap yang kami mahukan Pemulihan Aktif kami.

Apa yang kita perlukan?

  • DDK (Driver Development Kit), kini juga dikenali sebagai WDK 7 (Windows Driver Kit).
  • Mesin maya (contohnya, Windows 7 x64)
  • Tidak perlu, tetapi fail pengepala yang boleh dimuat turun boleh membantu di sini

Apa yang ada dalam kod itu?

Mari kita berlatih sedikit dan, sebagai contoh, tulis aplikasi kecil yang:

  1. Memaparkan mesej pada skrin
  2. Memperuntukkan sedikit ingatan
  3. Menunggu input papan kekunci
  4. Membebaskan memori terpakai

Dalam aplikasi asli, titik masuk bukanlah utama atau winmain, tetapi fungsi NtProcessStartup, kerana kami sebenarnya melancarkan proses baharu secara langsung dalam sistem.

Mari kita mulakan dengan memaparkan mesej pada skrin. Untuk ini kami mempunyai fungsi asli NtDisplayString, yang mengambil sebagai hujah penunjuk kepada objek struktur UNICODE_STRING. RtlInitUnicodeString akan membantu kami memulakannya. Akibatnya, untuk memaparkan teks pada skrin kita boleh menulis fungsi kecil ini:

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

Oleh kerana hanya fungsi daripada ntdll tersedia untuk kami, dan tiada perpustakaan lain dalam ingatan, kami pasti akan menghadapi masalah dengan cara memperuntukkan memori. Pengendali baharu belum wujud (kerana ia berasal dari dunia C++ peringkat terlalu tinggi), dan tiada fungsi malloc (ia memerlukan perpustakaan C masa jalan). Sudah tentu, anda hanya boleh menggunakan timbunan. Tetapi jika kita perlu memperuntukkan memori secara dinamik, kita perlu melakukannya pada timbunan (iaitu timbunan). Jadi mari kita buat timbunan untuk diri kita sendiri dan ambil ingatan daripadanya bila-bila masa kita memerlukannya.

Fungsi ini sesuai untuk tugasan ini RtlCreateHeap. Seterusnya, menggunakan RtlAllocateHeap dan RtlFreeHeap, kami akan mengisi dan membebaskan memori apabila kami memerlukannya.

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

Mari kita teruskan untuk menunggu input papan kekunci.

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

Apa yang kita perlukan adalah menggunakan NtReadFile pada peranti terbuka dan tunggu sehingga papan kekunci mengembalikan sebarang tekanan kepada kami. Jika kekunci ESC ditekan, kami akan terus bekerja. Untuk membuka peranti, kami perlu memanggil fungsi NtCreateFile (kami perlu membuka DeviceKeyboardClass0). Kami juga akan menghubungi NtCreateEventuntuk memulakan objek tunggu. Kami akan mengisytiharkan sendiri struktur KEYBOARD_INPUT_DATA, yang mewakili data papan kekunci. Ini akan memudahkan kerja kita.

Aplikasi asli berakhir dengan panggilan fungsi NtTerminateProcesskerana kita hanya membunuh proses kita sendiri.

Semua kod untuk aplikasi kecil kami:

#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: Kami boleh menggunakan fungsi DbgBreakPoint() dengan mudah dalam kod kami untuk menghentikannya dalam penyahpepijat. Benar, anda perlu menyambungkan WinDbg ke mesin maya untuk penyahpepijatan kernel. Arahan tentang cara melakukan ini boleh didapati di sini atau guna sahaja VirtualKD.

Penyusunan dan pemasangan

Cara paling mudah untuk membina aplikasi asli ialah menggunakan DDK (Kit Pembangunan Pemandu). Kami memerlukan versi ketujuh purba, kerana versi kemudian mempunyai pendekatan yang sedikit berbeza dan berfungsi rapat dengan Visual Studio. Jika kami menggunakan DDK, maka projek kami hanya memerlukan Makefile dan sumber.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

sumber:

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 anda akan sama, tetapi mari kita lihat sumber dengan lebih terperinci. Fail ini menentukan sumber program anda (fail.c), pilihan binaan dan parameter lain.

  • NAMA SASARAN – nama fail boleh laku yang sepatutnya dihasilkan pada akhirnya.
  • TARGETTYPE – jenis fail boleh laku, ia boleh menjadi pemacu (.sys), maka nilai medan hendaklah DRIVER, jika perpustakaan (.lib), maka nilainya ialah PERPUSTAKAAN. Dalam kes kami, kami memerlukan fail boleh laku (.exe), jadi kami menetapkan nilai kepada PROGRAM.
  • UMTYPE – nilai yang mungkin untuk medan ini: konsol untuk aplikasi konsol, tingkap untuk bekerja dalam mod bertingkap. Tetapi kita perlu menentukan nt untuk mendapatkan aplikasi asli.
  • BUFFER_OVERFLOW_CHECKS – menyemak tindanan untuk limpahan penimbal, malangnya bukan kes kami, kami mematikannya.
  • MINWIN_SDK_LIB_PATH – nilai ini merujuk kepada pembolehubah SDK_LIB_PATH, jangan risau bahawa anda tidak mempunyai pembolehubah sistem sedemikian yang diisytiharkan, apabila kami menjalankan binaan yang disemak dari DDK, pembolehubah ini akan diisytiharkan dan akan menunjuk ke perpustakaan yang diperlukan.
  • SUMBER – senarai sumber untuk program anda.
  • TERMASUK – fail pengepala yang diperlukan untuk pemasangan. Di sini mereka biasanya menunjukkan laluan ke fail yang disertakan dengan DDK, tetapi anda juga boleh menentukan mana-mana yang lain.
  • TARGETLIBS – senarai perpustakaan yang perlu dipautkan.
  • USE_NTDLL ialah medan yang diperlukan yang mesti ditetapkan kepada 1 atas sebab yang jelas.
  • USER_C_FLAGS – sebarang bendera yang boleh anda gunakan dalam arahan prapemproses semasa menyediakan kod aplikasi.

Jadi untuk membina, kita perlu menjalankan x86 (atau x64) Checked Build, tukar direktori kerja ke folder projek dan jalankan arahan Build. Keputusan dalam tangkapan skrin menunjukkan bahawa kami mempunyai satu fail boleh laku.

Perkhidmatan Windows Native Applications dan Acronis Active Restore

Fail ini tidak boleh dilancarkan dengan begitu mudah, sistem mengutuk dan menghantar kami untuk memikirkan kelakuannya dengan ralat berikut:

Perkhidmatan Windows Native Applications dan Acronis Active Restore

Bagaimana untuk melancarkan aplikasi asli?

Apabila autochk bermula, urutan permulaan program ditentukan oleh nilai kunci pendaftaran:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Pengurus sesi melaksanakan program daripada senarai ini satu demi satu. Pengurus sesi mencari sendiri fail boleh laku dalam direktori system32. Format nilai kunci pendaftaran adalah seperti berikut:

autocheck autochk *MyNative

Nilai mestilah dalam format perenambelasan, bukan ASCII biasa, jadi kunci yang ditunjukkan di atas adalah dalam 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

Untuk menukar tajuk, anda boleh menggunakan perkhidmatan dalam talian, contohnya, ini.

Perkhidmatan Windows Native Applications dan Acronis Active Restore
Ternyata untuk melancarkan aplikasi asli, kami memerlukan:

  1. Salin fail boleh laku ke folder system32
  2. Tambah kunci pada pendaftaran
  3. But semula mesin

Untuk kemudahan, berikut ialah skrip sedia untuk memasang aplikasi asli:

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

Selepas pemasangan dan but semula, walaupun sebelum skrin pemilihan pengguna muncul, kami akan mendapat gambar berikut:

Perkhidmatan Windows Native Applications dan Acronis Active Restore

Jumlah

Dengan menggunakan contoh aplikasi kecil sedemikian, kami yakin bahawa sangat mungkin untuk menjalankan aplikasi di peringkat Windows Native. Seterusnya, saya dan rakan-rakan dari Universiti Innopolis akan terus membina perkhidmatan yang akan memulakan proses interaksi dengan pemandu lebih awal daripada versi sebelumnya projek kami. Dan dengan kemunculan shell win32, adalah logik untuk memindahkan kawalan ke perkhidmatan penuh yang telah dibangunkan (lebih lanjut mengenai ini di sini).

Dalam artikel seterusnya kami akan menyentuh satu lagi komponen perkhidmatan Pemulihan Aktif, iaitu pemacu UEFI. Langgan blog kami supaya anda tidak terlepas entri seterusnya.

Sumber: www.habr.com

Tambah komen