היינט וועלן מיר פאָרזעצן אונדזער געשיכטע וועגן ווי מיר אַרבעטן מיט Innopolis University צו אַנטוויקלען Active Restore טעכנאָלאָגיע צו דערלויבן באַניצער צו ווידער אָנהייבן אַרבעטן אויף זייערע מאַשינען אַזוי שנעל ווי מעגלעך נאָך אַ קראַך. מיר וועלן רעדן וועגן נאַטיווע אַפּפּס. Windows, включая особенности их создания и запуска. Под катом – немного о нашем проекте, а также практическое руководство как писать нативные приложения.

אין די פריערדיקע אַרטיקלען מיר האָבן שוין גערעדט וועגן וואָס עס איז , און ווי סטודענטן פון יננאָפּאָליס אַנטוויקלען . הייַנט איך ווילן צו פאָקוס אויף געבוירן אַפּלאַקיישאַנז, צו די מדרגה פון וואָס מיר ווילן צו "באַגראָבן" אונדזער אַקטיוו אָפּזוך דינסט. אויב אַלץ אַרבעט אויס, מיר קענען:
- קאַטער די דינסט זיך פיל פריער
- פיל פריער צו קאָנטאַקט די וואָלקן ווו די באַקאַפּ איז ליגן
- פיל פריער צו פֿאַרשטיין וואָס מאָדע די סיסטעם איז אין - נאָרמאַל שטיוול אָדער אָפּזוך
- פיל ווייניקער טעקעס צו צוריקקריגן אין שטייַגן
- לאָזן די באַניצער צו אָנהייבן אפילו פאַסטער.
וואָס איז אַ געבוירן אַפּ סייַ ווי סייַ?
צו ענטפֿערן דעם קשיא, לאָמיר קוקן אין די סיקוואַנס פון קאַללס אַז די סיסטעם מאכט, למשל, אויב אַ פּראָגראַמיסט אין זיין אַפּלאַקיישאַן פרוווט צו שאַפֿן אַ טעקע.

Pavel Yosifovich — Windows Kernel Programming (2019)
דער פּראָגראַמיסט ניצט די פֿונקציע , וואָס איז דערקלערט אין די fileapi.h כעדער טעקע און ימפּלאַמענאַד אין Kernel32.dll. אָבער, די פֿונקציע זיך טוט נישט מאַכן אַ טעקע, עס נאָר טשעקס די אַרייַנשרייַב אַרגומענטן און רופט די פֿונקציע (די פּרעפיקס Nt ינדיקייץ אַז די פֿונקציע איז געבוירן). די פֿונקציע איז דערקלערט אין דער כעדער טעקע winternl.h און ימפּלאַמענאַד אין ntdll.dll. עס פּריפּערז צו שפּרינגען אין יאָדער פּלאַץ, און דעמאָלט מאכט אַ סיסטעם רופן צו שאַפֿן אַ טעקע. אין דעם פאַל, עס טורנס אויס אַז Kernel32 איז נאָר אַ ראַפּער פֿאַר Ntdll. איינער פון די סיבות וואָס דאָס איז געטאן איז אַז מייקראָסאָפֿט האט די פיייקייט צו טוישן די פאַנגקשאַנז פון די געבוירן וועלט, אָבער אין דער זעלביקער צייט נישט פאַרבינדן די נאָרמאַל ינטערפייסיז. מייקראָסאָפֿט טוט נישט רעקאָמענדירן צו רופן געבוירן פאַנגקשאַנז גלייַך און טוט נישט דאָקומענט רובֿ פון זיי. דורך דעם וועג, אַנדאַקיאַמעניד פאַנגקשאַנז קענען זיין געפֿונען .
דער הויפּט מייַלע פון געבוירן אַפּלאַקיישאַנז איז אַז ntdll איז לאָודיד אין די סיסטעם פיל פריער ווי Kernel32. דאָס מאכט זינען, ווייַל Kernel32 ריקווייערז ntdll צו אַרבעטן. ווי אַ רעזולטאַט, אַפּלאַקיישאַנז וואָס נוצן געבוירן פֿעיִקייטן קענען אָנהייבן ארבעטן פיל פריער.
אזוי, דער Windows Native Applications – это программы, способные запускаться на раннем этапе загрузки Windows. Они используют ТОЛЬКО функции из ntdll. Пример такого приложения: וואָס פּערפאָרמז צו קאָנטראָלירן די דיסק פֿאַר ערראָרס איידער איר אָנהייבן די הויפּט באַדינונגס. דאָס איז פּונקט דער מדרגה אין וואָס מיר ווילן צו זען אונדזער אַקטיוו ריסטאָר.
וואָס טאָן מיר דאַרפֿן?
- (Driver Development Kit), ныне также известный под названием WDK 7 (Windows Driver Kit).
- Виртуальная машина (например, Windows 7 X64)
- ניט נייטיק, אָבער כעדער טעקעס וואָס קענען זיין דאַונלאָודיד קען העלפן
וואָס איז אין די קאָד?
זאל ס פיר אַ ביסל און שרייַבן אַ קליין אַפּלאַקיישאַן ווי אַ בייַשפּיל אַז:
- דיספּלייז אַ אָנזאָג אויף דעם עקראַן
- אַלאַקייץ עטלעכע זכּרון
- ווארטן פֿאַר קלאַוויאַטור אַרייַנשרייַב
- באפרײ ט זי ך פארנומע ן זכרון
אין געבוירן אַפּלאַקיישאַנז, די פּאָזיציע פונט איז נישט הויפּט אָדער ווינמאַין, אָבער די NtProcessStartup פונקציע, ווייַל מיר זענען אַקשלי גלייך סטאַרטינג אַ נייַע פּראָצעס אין די סיסטעם.
זאל ס אָנהייבן מיט ווייַזנדיק אַ אָנזאָג אויף דעם עקראַן. פֿאַר דעם מיר האָבן אַ געבוירן פֿונקציע , וואָס נעמט ווי אַן אַרגומענט אַ טייַטל צו אַ UNICODE_STRING סטרוקטור כייפעץ. RtlInitUnicodeString וועט העלפֿן אונדז ינישאַלייז עס. ווי אַ רעזולטאַט, צו אַרויסווייַזן טעקסט אויף דעם עקראַן, מיר קענען שרייַבן אַ קליין פונקציע ווי דאָס:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}זינט מיר האָבן בלויז אַקסעס צו פאַנגקשאַנז פֿון ntdll, און עס זענען פשוט קיין אנדערע לייברעריז אין זכּרון נאָך, מיר וועלן באשטימט האָבן פּראָבלעמס מיט ווי צו אַלאַקייט זכּרון. דער נייַע אָפּעראַטאָר טוט נישט עקסיסטירן נאָך (ווייַל עס קומט פון די צו הויך-מדרגה C ++ וועלט), און עס איז קיין מאַללאָק פֿונקציע (עס ריקווייערז די C רונטימע לייברעריז). פון קורס, איר קענען נוצן בלויז די אָנלייגן. אבער אויב מיר דאַרפֿן צו דינאַמיש אַלאַקייט זכּרון, מיר וועלן האָבן צו טאָן דאָס אין די קופּע. אַזוי לאָמיר מאַכן אַ קופּע פֿאַר זיך און נעמען זכּרון דערפון ווען מיר דאַרפֿן עס.
די פונקציע וואָס איז פּאַסיק פֿאַר דעם אַרבעט איז . דערנאָך, ניצן 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;
}
}אַלע מיר דאַרפֿן איז צו נוצן אויף אַן אָפֿן מיטל, און וואַרטן ביז די קלאַוויאַטור קערט אונדז אַ טיפּ פון דרוק. אויב די ESC שליסל איז געדריקט, מיר וועלן פאָרזעצן צו אַרבעטן. צו עפֿענען די מיטל, מיר דאַרפֿן צו רופן די NtCreateFile פונקציע (מיר דאַרפֿן צו עפֿענען DeviceKeyboardClass0). מיר וועלן אויך רופן צו ינישאַלייז די כייפעץ פֿאַר ווארטן. מיר וועלן דערקלערן די KEYBOARD_INPUT_DATA סטרוקטור זיך, וואָס רעפּראַזענץ די קלאַוויאַטור דאַטן. דאָס וועט מאַכן אונדזער אַרבעט גרינגער.
די געבוירן אַפּלאַקיישאַן טערמאַנייץ מיט אַ פונקציע רופן , ווייַל מיר זענען פשוט מאָרד אונדזער אייגן פּראָצעס.
די גאנצע קאָד פֿאַר אונדזער קליין אַפּלאַקיישאַן:
#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);
}פּס: מיר קענען לייכט נוצן די DbgBreakPoint () פֿונקציע אין אונדזער קאָד צו האַלטן אין די דיבוגגער. אמת, איר דאַרפֿן צו פאַרבינדן WinDbg צו די ווירטואַל מאַשין פֿאַר קערן דיבאַגינג. איר קענען געפֿינען ינסטראַקשאַנז אויף ווי צו טאָן דאָס אָדער נאָר נוצן .
זאַמלונג און פֿאַרזאַמלונג
די יזיאַסט וועג צו בויען אַ געבוירן אַפּ איז צו נוצן (דרייווער אנטוויקלונג קיט). מיר דאַרפֿן די אלטע זיבעטער ווערסיע, זינט שפּעטער ווערסיעס האָבן אַ ביסל אַנדערש צוגאַנג און אַרבעט ענג מיט וויסואַל סטודיאָ. אויב מיר נוצן DDK, אונדזער פּרויעקט דאַרף בלויז 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 - דער טיפּ פון עקסעקוטאַבלע טעקע, עס קען זיין אַ שאָפער (.סיס), דער פעלד ווערט זאָל זיין דרייווער, אויב אַ ביבליאָטעק (.ליב), די ווערט איז ביבליאָטעק. אין אונדזער פאַל, מיר דאַרפֿן אַ עקסעקוטאַבלע טעקע (.עקסע), אַזוי מיר שטעלן די ווערט צו PROGRAM.
- UMTYPE - מעגלעך וואַלועס פון דעם פעלד: קאַנסאָול פֿאַר אַ קאַנסאָול אַפּלאַקיישאַן, פֿענצטער פֿאַר ארבעטן אין ווינדאָוד מאָדע. אָבער מיר דאַרפֿן צו ספּעציפיצירן NT צו באַקומען די געבוירן אַפּלאַקיישאַן.
- BUFFER_OVERFLOW_CHECKS - קאָנטראָלירן דעם אָנלייגן פֿאַר באַפער לויפן, ליידער נישט אונדזער פאַל, מיר קער עס אַוועק.
- MINWIN_SDK_LIB_PATH - דער ווערט רעפערס צו די SDK_LIB_PATH בייַטעוודיק, טאָן ניט זאָרג אַז איר האָט נישט דערקלערט אַזאַ אַ סיסטעם בייַטעוודיק, ווען מיר לויפן אָפּגעשטעלט בויען פֿון DDK, דעם בייַטעוודיק וועט זיין דערקלערט און וועט פונט צו די נייטיק לייברעריז.
- SOURCES - אַ רשימה פון דיין פּראָגראַם קוואלן.
- ינקלודז - כעדער טעקעס וואָס זענען פארלאנגט פֿאַר פֿאַרזאַמלונג. דאָ איר יוזשאַוואַלי ספּעציפיצירן די דרך צו די טעקעס וואָס קומען מיט די DDK, אָבער איר קענען אויך ספּעציפיצירן אנדערע.
- TARGETLIBS - רשימה פון לייברעריז צו לינק.
- USE_NTDLL איז אַ פארלאנגט פעלד וואָס מוזן זיין שטעלן צו 1 פֿאַר קלאָר ווי דער טאָג סיבות.
- USER_C_FLAGS - קיין פלאַגס וואָס איר קענען נוצן אין פּריפּראַסעסער דיירעקטיווז ווען פּריפּערינג אַפּלאַקיישאַן קאָד.
אַזוי, צו בויען מיר דאַרפֿן צו לויפן x86 (אָדער x64) אָפּגעשטעלט בויען, טוישן די אַרבעט וועגווייַזער צו די פּרויעקט טעקע און לויפן די בילד באַפֿעל. דער רעזולטאַט אויף די סקרעענשאָט ווייזט אַז מיר האָבן צונויפגעשטעלט איין עקסעקוטאַבלע טעקע.

דער טעקע קענען ניט זיין לאָנטשט אַזוי לייכט, די סיסטעם קאַמפּליינז און סענדז אונדז צו טראַכטן וועגן אונדזער נאַטור מיט די פאלגענדע טעות:

ווי צו קאַטער אַ געבוירן אַפּלאַקיישאַן?
ווען אַוטאָטשק סטאַרץ, די סיקוואַנס פון פּראָגראַם לאָנטשיז איז באשלאסן דורך די ווערט פון די רעגיסטרי שליסל:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteדער סעסיע פאַרוואַלטער עקסאַקיוץ די מגילה פון דער רשימה איינער דורך איינער. דער סעסיע פאַרוואַלטער זיך זוכט פֿאַר עקסעקוטאַבלע טעקעס אין די סיסטעם32 וועגווייַזער. דער פֿאָרמאַט פון די רעגיסטרי שליסל ווערט איז ווי גייט:
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צו גער דעם נאָמען, איר קענט נוצן אַן אָנליין דינסט, למשל, .

עס טורנס אויס אַז אין סדר צו קאַטער אַ געבוירן אַפּלאַקיישאַן, מיר דאַרפֿן:
- נאָכמאַכן די עקסעקוטאַבלע טעקע צו די סיסטעם32 טעקע
- לייג שליסל צו רעגיסטרי
- רעבאָאָט די מאַשין
פֿאַר קאַנוויניאַנס, דאָ איז אַ פאַרטיק שריפט פֿאַר ינסטאָלינג אַ געבוירן אַפּלאַקיישאַן:
ינסטאַלל.באַט
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pauseadd.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 вполне возможно. Дальше мы с ребятами из Университета Иннополис продолжим строить сервис, который будет инициировать процесс взаимодействия с драйвером намного раньше, чем в предыдущей версии нашего проекта. А с появлением оболочки win32 логично будет передать управление полноценному сервису, который уже был разработан (об этом подробнее ).
אין דער ווייַטער אַרטיקל מיר וועלן אָנרירן אן אנדער קאָמפּאָנענט פון די אַקטיוו ריסטאָר דינסט, ניימלי די UEFI שאָפער. אַבאָנירן צו אונדזער בלאָג צו נישט פאַרפירן דעם ווייַטער פּאָסטן.
מקור: www.habr.com
