Виртуальная ферма x86-ботов
Низкоуровневое программированиеia32 winapi win32 windows xp windows 8 vmware visual studio assembler debug
Готовые эмуляторы Bochs/QEmu для этих целей никак не подходят, так как я уже писал 12 лет назад подобный усечённый эмулятор.
А так как, начиная уже с i386, у процессоров имеется вся необходимая встроенная аппаратная поддержка полной виртуализации, то хотелось бы просто обойтись собственным гипервизором, вместо полной эмуляции каждой команды.
Прикладнaя API-функция CreateProcess достаточно сложная и выполняет четыре действия автоматически и без возможности опционального контроля:
- Создать адресное пространство процесса (размером 4Gb)
- Резервировать в адресном пространстве процесса регион размером, достаточным для размещения исполняемого файла. Начальный адрес региона определяется в заголовке EXE-модуля. Обычно он равен 0x00400000, но может быть изменен при построении файла параметром /BASE компоновщика.
- Отобразить исполняемый файл на зарезервированное адресное пространство. Тем самым VMM распределяет физические страницы не из файла подкачки, а непосредственно из EXE-модуля.
- Таким же образом отобразить на адресное пространство процесса необходимые ему динамически связываемые библиотеки. Информация о необходимых библиотеках находится в заголовке EXE-модуля. Желательное расположение региона адресов описано внутри библиотеки. Visual C++, например, устанавливает по умолчанию адрес 0x10000000. Этот адрес может так же изменяться параметром /BASE компоновщика. Если при загрузке выясняется, что данный регион занят, то система попытается переместить библиотеку в другой регион адресов, на основе настроечной информации, содержащейся в DLL-модуле. Однако эта операция снижает эффективность системы и, кроме того, если настроечная информация удалена при компоновке библиотеки параметром /FIXED, то загрузка становится вообще невозможной.
По-видимому, для этого нужно вызвать функцию NtSetLdtEntries/ZwSetLdtEntries и подготовить таблицу селекторов сегментов соответствующим образом.
(В документациях говорится, что начиная с процессора i386 можно настроить таблицу 1024 селекторов ссылками на 1024 сегмента по 4096 байта каждый и создать виртуальное адресное пространство в 4 Гб. При этом можно переназначать сегменты так, чтобы они в адресном пространстве могли повторяться несколько раз в разных местах или располагались нелинейно.)
Затем мне необходимо создать некий «бот-процесс»…
Под новый «бот-процесс» необходимо через системные функции ядра в адресном пространстве создать 65536 зеркал на один регион памяти в 64 Кб, чтобы на протяжении всего 32-битного пространства повторялся 16-битный регион ОЗУ.
В моём случае, мне не нужно резервировать в адресном пространстве создаваемого «бот-процесса» регион под системное API и необходимости в верхних 2 Гб под системное API в «бот-процессе» у меня нет, так как вызовов API-функций «бот-процесс» совершать не должен: Все 4 Гб должны быть одним регионом 64 Кб, повторяющимся на всём протяжении.
То есть, созданный «бот-процесс» должен работать «в собственном соку» и никак не взаимодействовать с системой, так как любое чтение памяти и её перезапись будет в инкапсуляции модифицировать всё тот же регион 64 Кб самого «бот-процесса»…
Также, через биты регистра CR0 необходимо исключить FPU-функционал Escape-инструкций у «бот-процесса», чтобы никаких операций вещественной арифметики он не совершал…
Естественно, обращение к портам ввода/вывода также не должно никак эмулироваться или симулироваться, а просто генерировать исключение для моего сборщика статистики.
Как результат, с помощью моего гипервизора я должен просто следить за работой «бот-процесса» и обрабатывать любые исключения самостоятельно…
Мною уже построен обработчик SEH, который перехватывает исключения локального «бота», который я встроил инлайн-вставками «asm { … … }» прямо в саму оболочку.
Но мне нужно, как уже можете понимать, просто создавать сотни виртуальных пространств в 4 Гб и загружать в них сотни «ботов», а затем просто собирать статистику…
На данный момент я попытался построить оболочку функциями пришлось воспользоваться ресурсами ядра через функции NtOpenFile/NtCreateSection/NtMapViewOfSection/NtCreateProcess.
Код:
OBJECT_ATTRIBUTES ObjAttr;
IO_STATUS_BLOCK StatusBlock;
HANDLE hSection;
HANDLE hProcess;
PVOID ImageBaseAddr;
SIZE_T ViewSize;
HANDLE hFile;
OBJECT_ATTRIBUTES ofs;
UNICODE_STRING dn;
IO_STATUS_BLOCK State;
DWORD Error;
//
ZeroMemory(&State,sizeof(IO_STATUS_BLOCK));
WCHAR *ch1 = L"\??\C:\the_bot.x86"; // В этом файле случайный мусор
RtlInitUnicodeString(&dn, ch1);
InitializeObjectAttributes(&ofs, &dn, 0, 0, 0);
// Начинаю сложный (для меня) процесс работы с функциями ядра
Error = NtCreateFile(&hFile,GENERIC_WRITE|GENERIC_READ,&ofs, &State,0,FILE_ATTRIBUTE_NORMAL,0,0/*FILE_SUPERSEDE*/,0,0,0);
printf("%08X %08X:hFilern", Error, hFile); // Здесь EAX возвращает 0 и в hFile загружается дескриптор файла.
Error = NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, NULL, PAGE_EXECUTE_READWRITE, SEC_IMAGE, hFile);
printf("%08X %08X:hSectionrn", Error, hSection); // Здесь уже возвращается код 0xC0000022 и я не понимаю, почему...
// Соответственно, дальше происходит исключение, предупреждение и закрытие всей программы
Error = NtMapViewOfSection(hSection, (void *)-1, &ImageBaseAddr, NULL, &ViewSize, 0, PAGE_EXECUTE_READWRITE, NULL, 0);
printf("%08X %08X:ImageBaseAddrrn", Error, ImageBaseAddr);
Error = NtCreateProcess(&hProcess, PROCESS_ALL_ACCESS, NULL, (void *)-1, PROCESS_CREATE_FLAGS_BREAKAWAY, hSection, NULL, NULL);
printf("%08X %08X:hProcessrn", Error, hProcess);
IO_STATUS_BLOCK StatusBlock;
HANDLE hSection;
HANDLE hProcess;
PVOID ImageBaseAddr;
SIZE_T ViewSize;
HANDLE hFile;
OBJECT_ATTRIBUTES ofs;
UNICODE_STRING dn;
IO_STATUS_BLOCK State;
DWORD Error;
//
ZeroMemory(&State,sizeof(IO_STATUS_BLOCK));
WCHAR *ch1 = L"\??\C:\the_bot.x86"; // В этом файле случайный мусор
RtlInitUnicodeString(&dn, ch1);
InitializeObjectAttributes(&ofs, &dn, 0, 0, 0);
// Начинаю сложный (для меня) процесс работы с функциями ядра
Error = NtCreateFile(&hFile,GENERIC_WRITE|GENERIC_READ,&ofs, &State,0,FILE_ATTRIBUTE_NORMAL,0,0/*FILE_SUPERSEDE*/,0,0,0);
printf("%08X %08X:hFilern", Error, hFile); // Здесь EAX возвращает 0 и в hFile загружается дескриптор файла.
Error = NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, NULL, PAGE_EXECUTE_READWRITE, SEC_IMAGE, hFile);
printf("%08X %08X:hSectionrn", Error, hSection); // Здесь уже возвращается код 0xC0000022 и я не понимаю, почему...
// Соответственно, дальше происходит исключение, предупреждение и закрытие всей программы
Error = NtMapViewOfSection(hSection, (void *)-1, &ImageBaseAddr, NULL, &ViewSize, 0, PAGE_EXECUTE_READWRITE, NULL, 0);
printf("%08X %08X:ImageBaseAddrrn", Error, ImageBaseAddr);
Error = NtCreateProcess(&hProcess, PROCESS_ALL_ACCESS, NULL, (void *)-1, PROCESS_CREATE_FLAGS_BREAKAWAY, hSection, NULL, NULL);
printf("%08X %08X:hProcessrn", Error, hProcess);
В сети готовых примеров, отдалённо напоминающих искомое, нету. Кроме, например, KlbrInWin-исходников, для запуска KolibriOS-приложений под Windows.
P.S.: В моей оболочке должно запускать одновременно до нескольких сотен подобных «бот-процессов», машинный код которых будет генерироваться генератором случайных чисел, так как мне нужно собрать статистическую картину генерируемых ими исключений…
Спасибо!
PS: А пенсионеры добавят, что x86 не соответствует Критериям виртуализации Попека и Голдберга в принципе, и никакой поддержки "полной виртуализации" в i386 так же нет, да и какие то мутные игры с виртуальной памятью гипервизором не являются.
Цитата: Ramon
Боюсь, что уважаемая аудитория ничего из ваших объяснений не поняла.
Этo я уже понял…
Цитата: Ramon
PS: А пенсионеры добавят, что x86 не соответствует Критериям виртуализации Попека и Голдберга в принципе, и никакой поддержки "полной виртуализации" в i386 так же нет, да и какие то мутные игры с виртуальной памятью гипервизором не являются.
Спасибо!
Однако, ещё 17 лет назад прорывом было запуск Linux и Windows одновременно на одном хосте - VMware ESX.
По телеканалу Rambler-TV тогда впервые это увидел.