- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 737
- Реакции
- 19
Если вы искали отправную точку для создания своего гипервизора или просто хотите сидеть ниже уровня ядра Windows, то Twizzy — это тот самый скелет, который можно допилить до рабочего продукта. Это буткит, который реализует общение Usermode <-> Bootkit через хук системного вызова в ntoskrnl.
Основная ценность здесь не в «готовой кнопке», а в реализации чтения и записи физической памяти, что до сих пор является базой для обхода большинства современных античитов уровня BattlEye или EAC.
Техническая начинка:
Как завести это чудо:
Пример Usermode части (коммуникация):
Сурс открытый, компилить можно через EDK2. Для тех, кто не хочет возиться со сборкой окружения, в архиве обычно валяется готовый билд, но крайне советую пересобрать со своими изменениями, чтобы не отлететь по сигнатурам в первый же час.
Любителям «пастить и не думать» тут делать нечего, а вот как база для изучения UEFI-разработки и Kernel-моддинга проект более чем достойный. Интересно посмотреть, как этот метод будет чувствовать себя после очередного патча защиты ntoskrnl.
Основная ценность здесь не в «готовой кнопке», а в реализации чтения и записи физической памяти, что до сих пор является базой для обхода большинства современных античитов уровня BattlEye или EAC.
Техническая начинка:
- Чтение и запись физической памяти (Physical R/W).
- Получение Base Address процесса.
- Извлечение CR3 (актуально для обхода защиты памяти в EAC).
- Чтение/запись памяти ядра и процессов.
- Общение через хук функции NtAcquireProcessActivityReference в ntoskrnl.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Как завести это чудо:
- Берем флешку, форматируем в FAT32.
- Переименовываем скомпилированный бинарник bootkit.efi в bootx64.efi.
- Создаем структуру папок: EFI/BOOT/bootx64.efi.
- В BIOS выставляем флешку в приоритет загрузки.
- Запускаемся. Если все ок, буткит подгрузится до старта основной ОС.
На данный момент все оффсеты жестко забиты под Windows 10. Если планируете использовать это на 11-й винде или других билдах, придется руками лезть в сурс и обновлять смещения, иначе словите BSOD при первом же вызове. Автор также советует переделать проект в полноценное EFI-приложение, чтобы Windows корректно возвращала память, а не считала её занятой рантайм-драйвером.
Пример Usermode части (коммуникация):
Код:
#pragma once
#include <Windows.h>
#include <TlHelp32.h>
#include <cstdint>
#include <Psapi.h>
//twizzy
constexpr uint64_t luh_magic = 0x12E7A12D; //thx sleepy
constexpr uint64_t luh_kernel_cr3_w10 = 0x1ad000;
constexpr uint64_t luh_kernel_cr3_w11 = 0x1ae000;
typedef struct cmd_t
{
enum operations : int
{
write_physical = 0x0,
read_physical = 0x1,
get_cr3 = 0x2,
get_base = 0x3
};
int operation;
uint64_t address;
uint64_t value;
uint64_t pid;
uint64_t magic;
uint64_t buffer;
uint64_t size;
uint64_t base;
uint64_t cr3;
};
typedef long( *nt_acquire_process_activity_reference_t )( int64_t, void*, int );
class c_bootkit
{
public:
nt_acquire_process_activity_reference_t hooked_function = 0;
uint64_t luh_pid = 0;
uint64_t luh_cr3 = 0;
bool init( )
{
auto ntdll = LoadLibrary( L"ntdll.dll" );
if ( !ntdll )
return false;
hooked_function = ( nt_acquire_process_activity_reference_t ) GetProcAddress( ntdll, "NtAcquireProcessActivityReference" );
if ( !hooked_function )
return false;
return true;
}
void create_request( cmd_t* cmd )
{
cmd->magic = luh_magic;
hooked_function( ( int64_t ) cmd, 0, 0 );
}
uint64_t get_process_id( const wchar_t* name )
{
uint64_t pid = 0;
auto snapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
PROCESSENTRY32W entry = {};
entry.dwSize = sizeof( PROCESSENTRY32W );
if ( Process32FirstW( snapshot, &entry ) )
{
do
{
if ( wcscmp( entry.szExeFile, name ) == 0 )
{
pid = entry.th32ProcessID;
break;
}
} while ( Process32NextW( snapshot, &entry ) );
}
luh_pid = pid;
return pid;
}
uint64_t get_image_base( )
{
cmd_t cmd = { 0 };
cmd.operation = cmd_t::operations::get_base;
cmd.pid = luh_pid;
this->create_request( &cmd );
return cmd.base;
}
uint64_t get_cr3( ) //works on eac
{
if ( luh_cr3 )
return luh_cr3;
cmd_t cmd = { 0 };
cmd.operation = cmd_t::operations::get_cr3;
cmd.pid = luh_pid;
create_request( &cmd );
luh_cr3 = cmd.cr3;
return cmd.cr3;
}
template <typename t>
t read_physical( uint64_t address )
{
cmd_t cmd = { 0 };
cmd.address = address;
t buffer{ };
cmd.buffer = ( uint64_t ) &buffer;
cmd.size = sizeof( t );
cmd.operation = cmd_t::operations::read_physical;
create_request( &cmd );
return buffer;
}
template <typename t>
void write_physical( uint64_t address, t value )
{
cmd_t cmd = { 0 };
cmd.address = address;
t buffer = value;
cmd.value = ( uint64_t ) &buffer;
cmd.size = sizeof( t );
cmd.operation = cmd_t::operations::write_physical;
create_request( &cmd );
}
uint64_t translate_virtual( uint64_t virtual_address, uint64_t cr3 )
{
if ( !virtual_address || !cr3 )
return 0;
uint64_t pml4_idx = ( virtual_address >> 39 ) & 0x1FF;
uint64_t pdpt_idx = ( virtual_address >> 30 ) & 0x1FF;
uint64_t pd_idx = ( virtual_address >> 21 ) & 0x1FF;
uint64_t pt_idx = ( virtual_address >> 12 ) & 0x1FF;
uint64_t page = virtual_address & 0xFFF;
uint64_t pml4_base = cr3 & 0x000FFFFFFFFFF000ULL;
uint64_t pml4e_raw = pml4_base + ( pml4_idx * 8 );
uint64_t pml4e = this->read_physical<uint64_t>( pml4e_raw );
if ( !( pml4e & 1 ) )
return 0;
uint64_t pdpt_base = pml4e & 0x000FFFFFFFFFF000ULL;
uint64_t pdpte_raw = pdpt_base + ( pdpt_idx * 8 );
uint64_t pdpte = this->read_physical<uint64_t>( pdpte_raw );
if ( !( pdpte & 1 ) )
return 0;
if ( pdpte & ( 1ULL << 7 ) )
{
uint64_t physical = ( pdpte & 0x000FFFFFC0000000ULL ) | ( virtual_address & 0x000000003FFFFFFFULL );
return physical;
}
uint64_t pd_base = pdpte & 0x000FFFFFFFFFF000ULL;
uint64_t pde_raw = pd_base + ( pd_idx * 8 );
uint64_t pde = this->read_physical<uint64_t>( pde_raw );
if ( !( pde & 1 ) )
return 0;
if ( pde & ( 1ULL << 7 ) )
{
uint64_t physical = ( pde & 0x000FFFFFFFE00000ULL ) | ( virtual_address & 0x00000000001FFFFFULL );
return physical;
}
uint64_t pt_base = pde & 0x000FFFFFFFFFF000ULL;
uint64_t pte_raw = pt_base + ( pt_idx * 8 );
uint64_t pte = this->read_physical<uint64_t>( pte_raw );
if ( !( pte & 1 ) )
return 0;
uint64_t physical = ( pte & 0x000FFFFFFFFFF000ULL ) | ( virtual_address & 0x0000000000000FFFULL );
return physical;
}
template <typename t>
t read_kernel( uint64_t address )
{
uint64_t physical = this->translate_virtual( address, luh_kernel_cr3_w10 );
if ( !physical )
return t{ };
return this->read_physical<t>( physical );
}
template <typename t>
void write_kernel( uint64_t address, t value )
{
uint64_t physical = this->translate_virtual( address, luh_kernel_cr3_w10 );
if ( !physical )
return;
this->write_physical<t>( physical, value );
}
template <typename t>
t read_virtual( uint64_t address )
{
uint64_t physical = this->translate_virtual( address, luh_cr3 );
if ( !physical )
return t{ };
return this->read_physical<t>( physical );
}
template <typename t>
void write_virtual( uint64_t address, t value )
{
uint64_t physical = this->translate_virtual( address, luh_cr3 );
if ( !physical )
return;
this->write_physical<t>( physical, value );
}
uint64_t get_ntoskrnl_base( )
{
uint64_t base = 0;
uint32_t yea = 0;
EnumDeviceDrivers( nullptr, 0, reinterpret_cast< unsigned long* >( &yea ) );
uint32_t count = yea / sizeof( void* );
void** drivers = new void* [ count ];
if ( EnumDeviceDrivers( drivers, yea, reinterpret_cast< unsigned long* >( &yea ) ) )
base = reinterpret_cast< uint64_t >( drivers[ 0 ] );
delete[] drivers;
return base;
}
};
Сурс открытый, компилить можно через EDK2. Для тех, кто не хочет возиться со сборкой окружения, в архиве обычно валяется готовый билд, но крайне советую пересобрать со своими изменениями, чтобы не отлететь по сигнатурам в первый же час.
Любителям «пастить и не думать» тут делать нечего, а вот как база для изучения UEFI-разработки и Kernel-моддинга проект более чем достойный. Интересно посмотреть, как этот метод будет чувствовать себя после очередного патча защиты ntoskrnl.