Недавно у меня возникла необходимость писать на русском в нативном приложении, но, как оказалось, добиться этого не так-то просто.
Для того, чтобы получить возможность вывода русских букв, нужно разобраться, где и в каком формате хранятся глифы символов, которые отображаются на экране. Если подробнее рассмотреть функцию winx_printf (в своем проекте я использовал ZenWINX и NDK для упрощения разработки приложения), то мы увидим, что она в свою очередь вызывает winx_print, далее вызывается NtDisplayString, которая преобразует входящую строку с помощью RtlUnicodeStringToOemString, далее идет вызов функции InbvDisplayString, которая обращается к VGA Boot Driver (bootvid.dll).
Отображаемые глифы хранятся в bootvid.dll в следующем формате (на примере английской буквы A):
00000000 - 0×00
00000000 - 0×00
00011000 - 0×18
00011000 - 0×18
00100100 - 0×24
00100100 - 0×24
00100100 - 0×24
01111110 - 0×7E
01000010 - 0×42
10000001 - 0×81
00000000 - 0×00
00000000 - 0×00
00000000 - 0×00
Посмотреть остальные символы можно с помощью следующего нехитрого скрипта на Perl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
open F, '<', 'bootvid.dll' or die $!; #0x1938 - начало таблицы с глифами в XP SP3 seek F, 0x1938, 1; read F, my $buf, 13 * 256; close F; for(my $i = 0; $i < 13 * 256; $i += 13) { my $symbol = substr $buf, $i, 13; my $hex = unpack 'H*', $symbol; for(my ($j, $k) = (0, length $hex); $j < $k; $j += 2) { my $line = hex substr $hex, $j, 2; printf "%08b - 0x%02X\n", $line, $line; } print "\n\n"; } |
Таким образом, каждый символ имеет размер 8x13 пикселей и, соответственно, занимает 13 байт. Всего под символы отведено 256 * 13 = 3328 байт. То есть, чтобы добавить поддержку русского, необходимо найти начало таблицы глифов в памяти и заменить неиспользуемые символы своими глифами. Начало таблицы может меняться в зависимости от версии ОС, например, в Windows 7 смещение от начала составляет 0x2610, в Vista 0x2420, а в XP SP3 0x1938. Найти таблицу довольно просто, для этого достаточно найти в памяти первый глиф (0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00).
Для начала необходимо составить свою таблицу глифов, чтобы заменить ею часть существующей таблицы. Вручную "рисовать" такое довольно муторно, поэтому я поступил следующим образом: вывел в консоли windows список необходимых символов, сделал скриншот и преобразовал его в эдакий ASCII-арт.
Делается это следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
use GD; my $im = GD::Image->newFromPng('image.png', 1) or die; for(my $x = 1; $x < $im->width(); $x += 8) { for(my $dy = 0; $dy < 13; $dy++) { for(my $dx = 0; $dx < 8; $dx++) { my $index = $im->getPixel($x + $dx, $dy); my ($r, undef, undef) = $im->rgb($index); if($r == 192) { print "1"; } else { print "0"; } } print "\n"; } print "\n\n"; } |
И сразу же сворачиваем получившуюся таблицу в массив байт:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
open F, '<', 'table.txt' or die $!; chomp(my @lines = <F>); close F; for(my ($i, $j) = (0, scalar @lines); $i < $j; $i += 15) { print "{"; for my $line(@lines[$i..$i + 12]) { my $int = unpack 'C', pack 'B8', $line; printf "0x%02X,", $int; } print "},\n"; } |
Конечно, последние два скрипта можно объединить в один, но так нагляднее. Также можно заметить, что у меня в консоли выведен не только русский алфавит. Это связано с тем, что по-умолчанию русские буквы не располагаются непрерывно в шрифте (0x80 - 0xAF и 0xE0 - 0xF1), поэтому проще захватить весь интервал (0x80 - 0xF1).
Теперь, когда у нас есть готовая таблица, нам необходимо написать код, который найдет и перезапишет необходимый фрагмент памяти.
Сначала мы должны найти bootvid.dll и адрес, по которому он загружен:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
NTSTATUS InitRussian() { PRTL_PROCESS_MODULE_INFORMATION minfo = NULL; NTSTATUS code; ULONG i, m_size, glyph_offset = 0, image_size = 0; PVOID image = NULL; /* Размер, необходимый для RTL_PROCESS_MODULE_INFORMATION */ code = NtQuerySystemInformation(SystemModuleInformation, minfo, 0, &m_size); if(code != STATUS_INFO_LENGTH_MISMATCH) { return code; } /* Выделяем память */ code = AllocMemory((PVOID *)&minfo, m_size); if(!NT_SUCCESS(code)) { return code; } /* Заполняем структуру */ code = NtQuerySystemInformation(SystemModuleInformation, minfo, m_size, NULL); if(!NT_SUCCESS(code)) { FreeMemory(minfo, m_size); return code; } /* Количество элементов в структуре */ m_size = *(PULONG)minfo; minfo = (PRTL_PROCESS_MODULE_INFORMATION)((PUCHAR)minfo + 4); /* Перечисляем модули */ for(i = 0; i < m_size; i++) { if(strstr(minfo[i].FullPathName, "BOOTVID")) { image_size = minfo[i].ImageSize; /* Выделяем память под содержимое bootvid */ code = AllocMemory(&image, image_size); if(!NT_SUCCESS(code)) { FreeMemory(minfo, m_size); return code; } |
Также нам понадобятся дополнительные функции, с помощью которых мы будем читать и писать в память:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
NTSTATUS ReadVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize) { SYSDBG_VIRTUAL MemoryChunks; MemoryChunks.Address = VirtualAddress; MemoryChunks.Buffer = Buffer; MemoryChunks.Request = BufferSize; return NtSystemDebugControl(SysDbgReadVirtual, &MemoryChunks, sizeof(MemoryChunks), NULL, 0, NULL); } NTSTATUS WriteVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize) { SYSDBG_VIRTUAL MemoryChunks; MemoryChunks.Address = VirtualAddress; MemoryChunks.Buffer = Buffer; MemoryChunks.Request = BufferSize; return NtSystemDebugControl(SysDbgWriteVirtual, &MemoryChunks, sizeof(MemoryChunks), NULL, 0, NULL); } |
Теперь прочитаем память bootvid и найдем начало таблицы:
1 2 |
#define GLYPH_SIZE 13 char first_glyph[13] = {0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00}; |
1 2 3 4 5 6 7 8 9 10 |
/* Читаем содержимое памяти bootvid в буфер */ code = ReadVirtualMemory(minfo[i].ImageBase, image, image_size); if(!NT_SUCCESS(code)) { FreeMemory(minfo, m_size); FreeMemory(image, image_size); return code; } /* Ищем начало таблицы глифов */ glyph_offset = search((char *)image, first_glyph, image_size, GLYPH_SIZE); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/* Функция поиска */ ULONG __stdcall search(char *x, char *y, unsigned int n, unsigned int m) { unsigned int i; char first, second, *third; first = y[0]; second = y[1]; third = &y[2]; for(i = 0; i < n; i++) { if(x[i] == first && x[i+1] == second) { if(RtlCompareMemory(&x[i+2], third, m - 2) == m - 2) { return i; } } } return 0; } |
И, наконец, переписываем часть памяти:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if(glyph_offset != 0) { /* Смещаемся на 128 глифов вперед */ glyph_offset += (128 * GLYPH_SIZE) + (ULONG)minfo[i].ImageBase; /* Записываем измененную таблицу в память */ return WriteVirtualMemory((PVOID)glyph_offset, ru_glyph, sizeof(ru_glyph)); } } } /* Освобождаем память */ FreeMemory(minfo, m_size); FreeMemory(image, image_size); return STATUS_NOT_FOUND; } |
Таким образом, мы получили готовую функцию для добавления поддержки русского языка. Следующий код позволяет убедиться в том, что она отлично работает на XP SP3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void __stdcall NtProcessStartup(PPEB Argument) { NTSTATUS code; char str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\nАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя"; zenwinx_native_init(); winx_init(Argument); winx_printf("%s\n\n", str); winx_getch(); code = InitRussian(); if(code != STATUS_SUCCESS) { winx_printf("Error: 0x%x - %d\n", code, RtlNtStatusToDosError(code)); } winx_printf("%s\n\n", str); winx_getch(); winx_exit(0); return; } |
А вот как выглядит результат работы:
Однако, у этого кода есть минус - он не работает под ОС выше XP SP3 и я пока что не разобрался, как адаптировать его под них.
Исходный код проекта: скачать.
кросавчег
Ага, вот кто пишет винлокеры!
>Ага, вот кто пишет винлокеры!
А Вы anonymous совсем не знаете Windows
WinLocker работает не как Native, он запускается уже после авторизации в системе
Ну, в общем то можно сделать нативный винлокер, вот только маловероятно, что антивирус даст изменить нужную ветку реестра и скинуть файл в system32.
если копироваться через файл посредник то антивирус и не посмотрит
А закидывать в system32 не обязательно. Можно прописать в BootExecute полный путь, приложение запустится из другого места. Остаётся только решить проблему доступа к самому ключу реестра.
Привет, нужно крякнуть прогу одну)
очень интересная вещица. но сложная. написал вам в асю)
Скорее мимо аси
Ф-ия "NtSystemDebugControl" - не работает начиная с XP SP3. И хз как теперь выводить кириллицу в native режиме. Может есть какие нибудь другие варианты?
Вариант очень простой: подмена оригинального bootvid.dll, либо на диске, либо в памяти.