วันนี้เราจะมาเล่าต่อว่าเราร่วมมือกับทีมงานจากมหาวิทยาลัย Innopolis ในการพัฒนาเทคโนโลยี Active Restore เพื่อให้ผู้ใช้สามารถเริ่มทำงานกับเครื่องของตนโดยเร็วที่สุดหลังจากเกิดข้อผิดพลาด เราจะพูดถึงแอปพลิเคชัน Windows ดั้งเดิมรวมถึงคุณสมบัติของการสร้างและการเปิดตัว ด้านล่างนี้เป็นข้อมูลเล็กน้อยเกี่ยวกับโปรเจ็กต์ของเรา รวมถึงคำแนะนำเชิงปฏิบัติเกี่ยวกับวิธีเขียนแอปพลิเคชันแบบเนทีฟ
ในกระทู้ที่แล้วเราได้คุยกันไปแล้วว่ามันคืออะไร
- เปิดตัวบริการเร็วกว่ามาก
- ติดต่อคลาวด์ที่ข้อมูลสำรองตั้งอยู่เร็วกว่ามาก
- เร็วกว่ามากในการทำความเข้าใจว่าระบบอยู่ในโหมดใด - การบูตหรือการกู้คืนตามปกติ
- ไฟล์ที่ต้องกู้คืนล่วงหน้าน้อยกว่ามาก
- อนุญาตให้ผู้ใช้เริ่มต้นได้เร็วยิ่งขึ้น
Native App คืออะไร?
เพื่อตอบคำถามนี้ ลองดูลำดับการโทรที่ระบบทำ เช่น หากโปรแกรมเมอร์ในแอปพลิเคชันของเขาพยายามสร้างไฟล์
Pavel Yosifovich - การเขียนโปรแกรมเคอร์เนล Windows (2019)
โปรแกรมเมอร์ใช้ฟังก์ชันนี้
ข้อได้เปรียบหลักของแอปพลิเคชันเนทิฟคือ ntdll ถูกโหลดเข้าสู่ระบบเร็วกว่าเคอร์เนล 32 มาก นี่เป็นตรรกะ เนื่องจาก kernel32 ต้องใช้ ntdll ในการทำงาน เป็นผลให้แอปพลิเคชันที่ใช้ฟังก์ชันดั้งเดิมสามารถเริ่มทำงานได้เร็วกว่ามาก
ดังนั้น Windows Native Applications จึงเป็นโปรแกรมที่สามารถเริ่มทำงานได้ตั้งแต่เนิ่นๆ ในการบูต Windows พวกเขาใช้ฟังก์ชันจาก ntdll เท่านั้น ตัวอย่างของแอปพลิเคชันดังกล่าว:
สิ่งที่เราต้องการ?
ดีดีเค (ชุดพัฒนาไดรเวอร์) ปัจจุบันรู้จักกันในชื่อ WDK 7 (ชุดไดรเวอร์ Windows)- เครื่องเสมือน (เช่น Windows 7 x64)
- ไม่จำเป็น แต่ไฟล์ส่วนหัวที่สามารถดาวน์โหลดได้อาจช่วยได้
ที่นี่
อะไรอยู่ในรหัส?
มาฝึกกันสักหน่อยและยกตัวอย่าง เขียนแอปพลิเคชันเล็กๆ ที่:
- แสดงข้อความบนหน้าจอ
- จัดสรรหน่วยความจำบางส่วน
- รอการป้อนข้อมูลด้วยแป้นพิมพ์
- เพิ่มหน่วยความจำที่ใช้แล้ว
ในแอปพลิเคชันแบบเนทิฟ จุดเข้าไม่ใช่จุดหลักหรือ winmain แต่เป็นฟังก์ชัน NtProcessStartup เนื่องจากเราเปิดตัวกระบวนการใหม่ในระบบโดยตรง
เริ่มต้นด้วยการแสดงข้อความบนหน้าจอ สำหรับสิ่งนี้ เรามีฟังก์ชันเนทิฟ
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}
เนื่องจากเราใช้งานได้เฉพาะฟังก์ชันจาก ntdll เท่านั้น และยังไม่มีไลบรารีอื่นในหน่วยความจำ เราจึงจะมีปัญหากับวิธีจัดสรรหน่วยความจำอย่างแน่นอน ยังไม่มีตัวดำเนินการใหม่ (เนื่องจากมาจากโลก C++ ระดับสูงเกินไป) และไม่มีฟังก์ชัน malloc (ต้องใช้ไลบรารี C รันไทม์) แน่นอน คุณสามารถใช้ได้เฉพาะสแต็กเท่านั้น แต่ถ้าเราต้องการจัดสรรหน่วยความจำแบบไดนามิก เราก็จะต้องจัดสรรหน่วยความจำบนฮีป (เช่น ฮีป) ดังนั้นเรามาสร้างฮีปสำหรับตัวเราเอง และใช้ความทรงจำจากมันทุกครั้งที่เราต้องการ
ฟังก์ชั่นนี้เหมาะกับงานนี้
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;
}
}
สิ่งที่เราต้องมีคือการใช้
แอปพลิเคชันเนทิฟจะลงท้ายด้วยการเรียกใช้ฟังก์ชัน
รหัสทั้งหมดสำหรับแอปพลิเคชันขนาดเล็กของเรา:
#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 กับเครื่องเสมือนเพื่อการดีบักเคอร์เนล คำแนะนำเกี่ยวกับวิธีการทำเช่นนี้สามารถพบได้
การรวบรวมและการประกอบ
วิธีที่ง่ายที่สุดในการสร้างแอปพลิเคชันแบบเนทีฟคือการใช้งาน
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 – ค่าที่เป็นไปได้สำหรับฟิลด์นี้: คอนโซลสำหรับแอปพลิเคชันคอนโซล, windows สำหรับการทำงานในโหมดหน้าต่าง แต่เราต้องระบุ nt เพื่อรับแอปพลิเคชันเนทิฟ
- BUFFER_OVERFLOW_CHECKS – ตรวจสอบสแต็กเพื่อหาบัฟเฟอร์ล้น น่าเสียดายไม่ใช่กรณีของเรา เราปิดมันแล้ว
- MINWIN_SDK_LIB_PATH – ค่านี้อ้างอิงถึงตัวแปร SDK_LIB_PATH ไม่ต้องกังวลว่าคุณจะไม่มีการประกาศตัวแปรระบบดังกล่าว เมื่อเรารันบิลด์ที่ตรวจสอบแล้วจาก DDK ตัวแปรนี้จะถูกประกาศและจะชี้ไปยังไลบรารีที่จำเป็น
- แหล่งที่มา – รายการแหล่งที่มาสำหรับโปรแกรมของคุณ
- รวม – ไฟล์ส่วนหัวที่จำเป็นสำหรับการประกอบ โดยปกติจะระบุเส้นทางไปยังไฟล์ที่มาพร้อมกับ DDK ที่นี่ แต่คุณสามารถระบุเส้นทางอื่นเพิ่มเติมได้
- TARGETLIBS – รายการไลบรารีที่ต้องเชื่อมโยง
- USE_NTDLL เป็นฟิลด์บังคับที่ต้องตั้งค่าเป็น 1 ด้วยเหตุผลที่ชัดเจน
- USER_C_FLAGS - แฟล็กใด ๆ ที่คุณสามารถใช้ในคำสั่งตัวประมวลผลล่วงหน้าเมื่อเตรียมโค้ดแอปพลิเคชัน
ดังนั้นในการสร้าง เราจำเป็นต้องรัน x86 (หรือ x64) Checked Build เปลี่ยนไดเร็กทอรีการทำงานเป็นโฟลเดอร์โปรเจ็กต์ และรันคำสั่ง Build ผลลัพธ์ในภาพหน้าจอแสดงว่าเรามีไฟล์ปฏิบัติการหนึ่งไฟล์
ไฟล์นี้ไม่สามารถเปิดได้อย่างง่ายดาย ระบบสาปแช่งและส่งให้เราพิจารณาพฤติกรรมของมันโดยมีข้อผิดพลาดต่อไปนี้:
จะเปิดแอปพลิเคชั่นเนทิฟได้อย่างไร?
เมื่อ 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
หากต้องการแปลงชื่อเรื่อง คุณสามารถใช้บริการออนไลน์ได้ เช่น
ปรากฎว่าในการเปิดแอปพลิเคชันแบบเนทีฟเราจำเป็นต้องมี:
- คัดลอกไฟล์ปฏิบัติการไปยังโฟลเดอร์ system32
- เพิ่มคีย์ลงในรีจิสทรี
- รีบูตเครื่อง
เพื่อความสะดวก นี่คือสคริปต์สำเร็จรูปสำหรับการติดตั้งแอปพลิเคชันเนทิฟ:
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 ได้ค่อนข้างมาก ต่อไป พวกจากมหาวิทยาลัย Innopolis และฉันจะยังคงสร้างบริการที่จะเริ่มต้นกระบวนการโต้ตอบกับคนขับเร็วกว่าในเวอร์ชันก่อนหน้าของโครงการของเรา และด้วยการถือกำเนิดของเชลล์ win32 จึงสมเหตุสมผลที่จะถ่ายโอนการควบคุมไปยังบริการเต็มรูปแบบที่ได้รับการพัฒนาแล้ว (เพิ่มเติมเกี่ยวกับเรื่องนี้
ในบทความถัดไป เราจะพูดถึงองค์ประกอบอื่นของบริการ Active Restore นั่นคือไดรเวอร์ UEFI สมัครสมาชิกบล็อกของเราเพื่อให้คุณไม่พลาดโพสต์ถัดไป
ที่มา: will.com