...
...

Ассемблер под Windows для чайников. Часть 17

В предыдущей части мы познакомились с основными функциями для работы с реестром Windows. Сегодня научимся их использовать на примерах простейших программ. Не забывайте, что реестр — достаточно серьезная вещь, поэтому будьте предельно внимательны: ошибка при работе с реестром может привести к отказу всей операционной системы. Тем не менее, волков бояться — в лес не ходить. Так что приступим!

Для начала создадим ключ реестра в ветке HKEY_CURRENT_USER:
format PE GUI 4.0
entry start

include 'win32a.inc'
include 'macro/if.inc'

ERROR_SUCCESS = 0

section '.data' data readable writeable

szREGSZ db 'REG_SZ',0
szTestKey db 'Test Key',0
szOK db 'Ключ был создан или уже существовал.',0

section '.data?' readable writeable

hKey dd ?
lpdwDisp dd ?

section '.code' code readable executable

start:

;Открываем или создаем ключ реестра
invoke RegCreateKeyEx,HKEY_CURRENT_USER,szTestKey,0,szREGSZ,0,KEY_WRITE or KEY_READ,0,hKey,lpdwDisp

.if eax = ERROR_SUCCESS
invoke MessageBox,0,szOK,szTestKey,MB_OK + MB_ICONASTERISK
;Закрываем ключ реестра
invoke RegCloseKey,hKey.else
invoke MessageBox,0,0,0,0
.endif

exit:
invoke ExitProcess,0

section '.idata' import data readable writeable

library advapi32,'ADVAPI32.DLL',\
kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'

include 'api\advapi32.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'

Если ключ 'Test Key' уже присутствует в ветке HKEY_CURRENT_USER, например, если вы запустите программу повторно, то данный ключ не будет создан заново, а просто будет открыт. Если такого ключа нет, то программа создаст его. В случае невозможности по каким-либо причинам создать или открыть ключ программа выдаст ошибку. После запуска программы убедитесь, что ключ действительно был создан. Для этого запустите regedit.exe: выберите в меню Пуск пункт Выполнить, введите команду regedit и подтвердите нажатием кнопки OK или Enter на клавиатуре. В редакторе реестра раскройте дерево ключей в ветке HKEY_CURRENT_USER и найдите в нем ключ Test Key. Если вы еще не в курсе: ключ в regedit отображается в виде желтой папочки. Если вы открыли редактор реестра еще до запуска программы, вам может понадобиться нажать F5 для обновления выведенной информации. Для того, чтобы программно удалить ключ из реестра, нам необходимо открыть ключ, удалить его, закрыть дескриптор ключа. Создадим копию нашей программы создания ключа и внесем в нее необходимые изменения:


szOK db 'Ключ успешно удален.',0
szError db 'Ключ не существует.',0

start:
;Открываем ключ реестра
invoke RegOpenKeyEx,HKEY_CURRENT_USER,szTestKey,0,KEY_WRITE or KEY_READ,hKey
.if eax = ERROR_SUCCESS
invoke RegDeleteKey,HKEY_CURRENT_USER,szTestKey
.if eax = ERROR_SUCCESS
invoke MessageBox,0,szOK,szTestKey,MB_OK + MB_ICONASTERISK
.else
invoke MessageBox,0,0,0,0
.endif
;Закрываем ключ реестра
invoke RegCloseKey,hKey
.else
invoke MessageBox,0,szError,0,0
.endif
exit:
invoke ExitProcess,0


Здесь мы пытаемся открыть ключ, и если это нам удается — пытаемся его удалить. Если попытка открытия или удаления открытого ключа не завершилась значением ERROR_SUCCESS, выводим сообщение об ошибке. Причем, если открытие ключа удалось произвести, то закрытие ключа необходимо произвести в любом случае независимо от результата его удаления. Если же не удалось произвести открытие ключа, то и закрывать нечего. Просто выводим сообщение и выходим из программы. Следующий пункт — создание значения в ключе реестра. Необходимо всегда перед созданием значения, его чтением, изменением или удалением убедиться, что в реестре присутствует ключ, значение которого будет создаваться, читаться, изменяться или удаляться. В любом случае в первом параметре функции RegSetValueEx необходимо указать дескриптор открытого ключа, полученный при помощи RegCreateKeyEx или RegOpenKeyEx. Мы уже знаем, как создавать и открывать ключи, поэтому следует просто не забывать делать это перед добавлением или изменением значения. Внесем необходимые изменения в нашу программу:


szOK db 'Значение успешно создано или изменено.',0
ddValue dd 01020304h
ValSize dd 4

start:
;Открываем или создаем ключ реестра
invoke RegCreateKeyEx,HKEY_CURRENT_USER,szTestKey,0,szREGSZ,0,KEY_WRITE or KEY_READ,0,hKey,lpdwDisp
.if eax = ERROR_SUCCESS
;Создаем значение
invoke RegSetValueEx,[hKey],szValueName,0,REG_DWORD,ddValue,[ValSize]
.if eax = ERROR_SUCCESS
invoke MessageBox,0,szOK,szTestKey,MB_OK + MB_ICONASTERISK
.else
invoke MessageBox,0,0,0,0
.endif
;Закрываем ключ реестра
invoke RegCloseKey,hKey
.else
invoke MessageBox,0,0,0,0
.endif
exit:
invoke ExitProcess,0


Данная программа создает значение в заданном ключе, а также может создать и сам ключ в случае его отсутствия. Если бы ключ создавать не требовалось, а необходимо было создать значение в уже существующем ключе, следовало бы использовать функцию RegOpenKeyEx. Она не станет создавать ключ, а выдаст ошибку, если не удастся его открыть. Откройте regedit и убедитесь, что программа создала значение в заданном ключе реестра. Вы можете увидеть, что в том же ключе существует еще и безымянное значение (по умолчанию). Для того, чтобы изменить его тип и содержимое, необходимо лишь опустить второй параметр функции RegSetValueEx, то есть указать ноль вместо имени значения. Все значения в ключах реестра создаются для того, чтобы когда-нибудь быть прочитанными. Следующий шаг — научиться получать содержимое заданного значения. Для этого используется функция RegQueryValueEx. Следующие изменения в программе позволят нам получить содержимое значения и его тип:


szError db 'Ключ не существует.',0
form db 'Тип: %u Значение: %08X',0

buffer rb 256
ddValue dd ?
lpType dd ?

start:
;Открываем ключ реестра
invoke RegOpenKeyEx,HKEY_CURRENT_USER,szTestKey,0,KEY_READ,hKey
.if eax = ERROR_SUCCESS
;Получаем значение
invoke RegQueryValueEx,[hKey],szValueName,0,lpType,ddValue,ValSize
.if eax = ERROR_SUCCESS
invoke wsprintf,buffer,form,[lpType],[ddValue]
invoke MessageBox,0,buffer,szTestKey,MB_OK + MB_ICONASTERISK
.else
invoke MessageBox,0,0,0,0
.endif
;Закрываем ключ реестра
invoke RegCloseKey,hKey
.else
invoke MessageBox,0,szError,0,0
.endif
exit:
invoke ExitProcess,0


Здесь мы только открываем ключ, потому что, если он не существует, то его значение мы уж точно не узнаем. Если ключ открыт, и значение получено — преобразуем его содержимое и тип при помощи функции wsprintf. %u означает беззнаковое целое (unsigned), а %08X — шестнадцатеричное целое, выводимое в восьми знаках, чтобы не потерялись нолики в начале содержимого, если они там есть. Тип мы выводим в цифровом виде. Четверка соответствует значению REG_DWORD или REG_DWORD_LITTLE_ENDIAN. Остальные значения вы можете найти в файле KERNEL32.INC в папке ..\FASM\INCLUDE\EQUATES\ вашего компилятора. Можно было бы организовать вывод типа значения и в текстовом виде, но в данном случае это лишняя работа, да и сейчас разговор не об этом. Работа со строковыми типами значений не сильно отличается от вышеприведенных примеров, поэтому на них мы тоже останавливаться не будем. Возникнут вопросы — пишите, отвечу. Рассмотрим лучше способ получения сводной информации об открытом ключе и его значениях:


form db 'Подразделов: %u',13,'Значений: %u',0

SubKeysNumber dd ?
ValuesNumber dd ?

start:
invoke RegOpenKeyEx,HKEY_CURRENT_USER,szTestKey,0,KEY_READ,hKey
.if eax = ERROR_SUCCESS
;Получаем информацию о ключе
invoke RegQueryInfoKey,[hKey],0,0,0,SubKeysNumber,0,0,ValuesNumber,0,0,0,0
.if eax = ERROR_SUCCESS
invoke wsprintf,buffer,form,[SubKeysNumber],[ValuesNumber]
invoke MessageBox,0,buffer,szTestKey,MB_OK + MB_ICONASTERISK
.else
invoke MessageBox,0,0,0,0
.endif
invoke RegCloseKey,hKey
.else
invoke MessageBox,0,szError,0,0
.endif
exit:
invoke ExitProcess,0


В данном примере мы получаем информацию о количестве подразделов открытого ключа и количестве значений в данном ключе. Полное описание параметров функции RegQueryInfoKey можно прочитать в предыдущей части статьи. Добавьте в тестовый ключ вручную или при помощи измененной программы несколько подразделов и несколько значений и убедитесь, что выдаваемые данной программой сведения о ключе верны. Я не зря попросил вас создать в тестовом ключе несколько подразделов, потому что сейчас мы попробуем программно перечислить подразделы ключа. Надеюсь, вы еще не забыли азы организации программного цикла. Если вдруг забыли, то сейчас заодно и вспомните:


ERROR_NO_MORE_ITEMS = 259

bufsize dd 256
index dd 0

start:
invoke RegOpenKeyEx,HKEY_CURRENT_USER,szTestKey,0,KEY_READ,hKey
.if eax = ERROR_SUCCESS
;Перечислим подразделы ключа
cycl:
mov [bufsize],256
invoke RegEnumKeyEx,[hKey],[index],buffer,bufsize,0,0,0,0
.if eax = ERROR_SUCCESS
inc [index]
invoke MessageBox,0,buffer,szTestKey,MB_OK + MB_ICONASTERISK
jmp cycl
.elseif eax = ERROR_NO_MORE_ITEMS
.else
invoke MessageBox,0,0,0,0
.endif
invoke RegCloseKey,hKey
.else
invoke MessageBox,0,szError,0,0
.endif
exit:
invoke ExitProcess,0


После того, как ключ открыт, начинается цикл. В цикле нам необходимо каждый раз устанавливать значение переменной [bufsize] в размер буфера в байтах. Этим нельзя пренебрегать, потому что после вызова функции в этой переменной уже будет находиться количество скопированных символов без учета завершающего нуля, и при следующем вызове функция может подумать, что в буфере не хватает места, опираясь на возвращенное в предыдущем вызове значение. Если после вызова функции мы получаем ответ ERROR_SUCCESS, то увеличиваем индекс на единицу, выводим сообщение с именем подраздела и повторяем цикл. Если ответ функции оказался ERROR_NO_MORE_ITEMS, то мы ничего не делаем, а просто вываливаемся из второго макроса ".if" прямо на функцию RegCloseKey. В остальных случаях — выводим сообщение об ошибке и, опять же, на функцию RegCloseKey. Здесь, правда, не совсем корректно учтена ситуация, когда подразделов изначально не окажется: программа не выдаст вообще никаких сообщений. Но это ведь всего лишь учебный пример, который предполагает определенные исходные условия. Кроме того, я думаю, вам и самим не составит труда включить в этот пример корректную обработку отсутствия подразделов в заданном ключе. Перечисление значений ключа осуществляется аналогично. Для того, чтобы перечислить только имена значений, в вышеприведенной программе перечисления подразделов потребуется лишь заменить RegEnumKeyEx на RegEnumValue. Если же понадобится получить еще и типы значений, и их содержимое, то придется немного доработать программку. Это не сложно, и при необходимости вы легко справитесь с этой задачей.

В принципе, про работу с реестром можно говорить долго и нудно, но про основные методы я вам все рассказал и показал. Теперь в этой области вы имеете хорошую точку опоры, оттолкнувшись от которой, можно продолжать плавание в любом разумном направлении. Подробно расписывать способы удаления целых разделов я не стану, чтобы такой "халявой" не соблазнить вас на написание вредоносных программ. Тот, кому это действительно понадобится на деле, всегда сможет додуматься сам или спросить у более опытных товарищей по коду. Тот, кто хочет отомстить соседу, пусть лучше поразит его своими полезными программами. На этом и распрощаемся. Всех вам благ, и успехов в понимании собственноручно написанного кода! Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт

BarMentaLisk, SASecurity gr., q@sa-sec.org

© Компьютерная газета

полезные ссылки
Охранные видеокамеры