Хакер - Заплатка на асме. Создаем панель инструментов для Windows на Flat Assembler
hacker_frei
Игорь Орещенков
Содержание статьи
- Нет в мире совершенства
- Выбор цели и средства
- Что умеет Flat Assembler?
- Создание обработчика сообщения WM_CREATE
- Подготовка к использованию функции SHAppBarMessage
- Резервирование площадки для панели
- Настройка окна панели
- Заключение
В этой статье я расскажу о том, как создать простое приложение — заготовку панели инструментов для рабочего стола Windows. По ходу дела мы вспомним Win32 API, разберемся, как его использовать на языке ассемблера, и познакомимся с Flat Assembler, который станет для нас основным инструментом разработки.
НЕТ В МИРЕ СОВЕРШЕНСТВА
Народная молва гласит, что в основе мироздания лежит закон подлости, из которого следуют все остальные фундаментальные законы природы. К таковым относится и закон неубывающей энтропии, в соответствии с которым материальные предметы со временем приходят в негодность. На моем мониторе этот закон проявился в виде горизонтальных полос в верхней части экрана, которые выглядят как нотный стан, если под ними радикально черный фон, и как интерференционная картина в двухщелевом эксперименте, если фон светлый.

Ознакомившись с обзорами аналогичных проблем по роликам на YouTube, я составил для себя общее представление об этом дефекте. Он может быть вызван либо плохим контактом шлейфа, соединяющего матрицу монитора с контроллером, либо неисправностью в электронных компонентах самого контроллера. Поскольку гарантийный срок уже закончился, я разобрал монитор и пришел к выводу, что с отсутствием опыта и инструментов шансы на успешный ремонт у меня примерно 50 на 50: либо устраню дефект, либо испорчу все окончательно.
Какие еще остаются варианты? Знаю по опыту, что официальный сервис, скорее всего, выставит заградительную цену, а отдавать боевого товарища в очумелые ручки кустаря‑одиночки совесть не позволяет. Наиболее здравой кажется мысль пойти в ближайший магазин, купить новый монитор и не дурить голову ни себе, ни читателям. Именно так я бы и поступил, если бы монитор не работал совсем. Но он почти работает, и выбрасывать его попросту жалко.
А ведь если подумать, ремонт необязательно предполагает восстановление исходного качества изделия — зачастую допускается некоторая утрата потребительских свойств. Как, например, поступает хакер, обнаружив в один прекрасный день дыру... нет, не в безопасности, а в любимых домашних брюках? Достает свой толстый кошелек и спешит в ближайший бутик за новыми? Нет, он вспоминает уроки Марьванны и, применяя методологию «вперед иголка», накладывает заплатку! Или другой пример. В не менее прекрасный день инженеры NASA обнаружили, что основная антенна радиосвязи запущенного к Юпитеру зонда не раскрылась полностью. Разве они бросили неисправное устройство на произвол судьбы и обратились к правительству за финансированием нового? Нет, они проявили находчивость и техническую смекалку, в результате чего пусть и не без труда, но успешно провели многолетнюю исследовательскую миссию.
ВЫБОР ЦЕЛИ И СРЕДСТВА
Рассматриваемая в статье ситуация находится где‑то между этими крайними случаями. Наибольшие неудобства описанный дефект доставляет при использовании развернутых на весь экран программ, потому что попадает либо на адресную строку браузера, либо на главное меню приложения. Использование же программ в оконном режиме, с подгонкой их местоположения после каждого запуска, грозит нервным расстройством. Я готов пожертвовать частью полезной площади экрана, если изображение не будет попадать на дефектную область.
Основная операционная система на моем компьютере — Windows. Как исключить полосу в верхней части экрана из доступного пространства рабочего стола, чтобы окна приложений при развертывании не попадали на нее? Идею мне подсказала панель задач: она монополизирует нижнюю часть экрана и никогда не перекрывается окнами программ. Может быть, достаточно будет прикрепить ее к верхней части экрана? Нет, во‑первых, она не совсем подходит по размеру, а во‑вторых, сама приобретает неприглядный вид из‑за дефекта. А нельзя ли сделать «заплатку» с такими же свойствами, но чтобы пользователь мог контролировать ее размер и цвет?
Оказывается, можно, и ответ быстро нашелся в справочнике по Win32 API — это панель инструментов рабочего стола. Направление работы прояснилось, осталось выбрать подходящий инструмент для ее выполнения. Основное средство разработки с использованием Win32 API — компилятор С. Но для вызова нескольких функций операционной системы хочется воспользоваться чем‑то более простым и изящным. Поэтому я отправился в темную кладовую своей памяти и нашел там пыльную шкатулку с плоским монтажником. Если кто‑то еще не догадался, то так звучит по‑русски Flat Assembler в варианте Яндекс‑переводчика. Удивительно, но продукт, с которым я познакомился еще в середине 2000-х, продолжает жить и здравствовать.
ЧТО УМЕЕТ FLAT ASSEMBLER?
Давай прямо сейчас разберемся со средой, в которой будем работать. На странице загрузки выбери архив с последней версией сборки для Windows, загрузи его и распакуй куда‑нибудь на диск. В трех мегабайтах папки FASMW есть все, что нам потребуется.
Создай пустую папку Appbar для рабочего проекта и скопируй в нее исходный текст шаблона типового приложения Windows FASMW\EXAMPLES\TEMPLATE\TEMPLATE.ASM. Запусти интегрированную среду разработки FASMW\FASMW.EXE и с помощью пункта меню File → Open... загрузи в нее этот файл. Обрати внимание, что в нашем распоряжении есть текстовый многооконный редактор с подсветкой синтаксиса ассемблера.

Шаблон Windows-приложения на ассемблере состоит из следующих основных частей:
- заголовка с указанием формата целевого исполняемого файла и точки входа в приложение;
- секции кода
.text, где меткаstartуказывает команду, с которой должно начинаться выполнение программы; - секции данных
.data, содержащей глобальные переменные программы; - секции импорта
.idata, в которой перечислены используемые программой динамические библиотеки и подключаются объявления содержащихся в них функций.
В целом текст программы должен быть понятен любому, кто использовал Win32 API. Во‑первых, сам API предельно прост. Параметры всех функций ожидают 32-битных значений аргументов, а если данные не укладываются в этот размер, то передается 32-битный указатель на массив или структуру опять же 32-битных значений. Исключение, пожалуй, только строки. Возвращаемое функцией значение (например, код завершения) передается через регистр EAX или, если оно превышает 32 бита, через структуру, на которую указывал один из аргументов.
Во‑вторых, Flat Assembler на основе своего набора макроинструкций предлагает синтаксический сахар, который делает использование API максимально приближенным к высокоуровневым языкам программирования. Скажем, довольно сложный вызов функции создания окна описывается одной строкой:
invoke CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,256,192,NULL,NULL,[wc.hInstance],NULL
Здесь invoke — это команда вызова подпрограммы в соответствии с соглашением STDCALL, CreateWindowEx — имя вызываемой API-функции, а далее через запятую следуют аргументы в том порядке, в котором они описаны в документации. С‑программисты могут считать, что имена всех переменных здесь — это указатели (_class, _title), для разыменования которых используются квадратные скобки ([wc.hInstance]). Отметим привычную «точечную» нотацию доступа к элементам структуры.
Описание оконной процедуры WindowProc тоже не должно вызвать затруднений:
proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam
...
ret
endp
Соглашение Win32 API требует, чтобы после возврата из процедур обратного вызова (callback-процедур) значения регистров EBX, ESI и EDI были такими же, как и перед вызовом. Для этого в заголовке присутствует указание на сохранение этих регистров в виде фразы uses ebx esi edi. А дальше через запятую идет список формальных параметров процедуры, которые соответствуют документации.
Теперь ты можешь скомпилировать и выполнить эту программу. Но сначала укажи путь к папке с подключаемыми директивой include файлами в пункте меню Options → Compiler setup так, чтобы он соответствовал фактическому местоположению FASMW\INCLUDE. После этого выполни пункт меню Run → Run или просто нажми клавишу F9. Если все было сделано правильно, то в рабочей папке Appbar появится свежесобранный файл TEMPLATE.EXE, а на экране отобразится крошечное окно Win32 program template.

СОЗДАНИЕ ОБРАБОТЧИКА СООБЩЕНИЯ WM_CREATE
Со средой разработки разобрались, приступим к работе над программой. Если раньше ты никогда не программировал панели рабочего стола Windows, то сейчас самое время изучить документацию. Но перед этим хочу обратить внимание на основные моменты. Панель рабочего стола не является каким‑то уникальным объектом операционной системы. Роль панели может играть любое окно, созданное функцией CreateWindowEx. Все средства Windows, обеспечивающие функционирование панели, сосредоточены в единственной функции SHAppBarMessage, с помощью которой можно:
- узнать границы области рабочего стола, в пределах которых можно разместить новую панель инструментов;
- зарезервировать на этой области участок для размещения новой панели;
- указать манипулятор (
handle) окна с панелью, которому будут отправляться системные уведомления, связанные с изменением обстановки на рабочем столе.
После резервирования новой области операционная система запрещает ее использование окнами приложений при их максимизации, освобождает от значков рабочего стола (если таковые на ней были) — и, в общем‑то, всё. За внешний вид панели отвечает ассоциированное с нею окно, которое теоретически должно закрыть собой освобожденное пространство и принять соответствующий стиль. Но по большому счету может этого и не делать.
С помощью пункта меню File → Save as... сохрани открытый в редакторе файл под именем appbar.asm. В нашей программе панелью будет главное окно приложения. Зарезервируем для нее полосу в верхней части рабочего стола. Это можно сделать в обработчике сообщения WM_CREATE, которое отправляется операционной системой окну приложения непосредственно перед тем, как отобразить его на экране. Для этого в начале оконной процедуры WindowProc вставим строки перехода к обработчику сообщения:
cmp [wmsg],WM_CREATE
je .wmcreate
и напишем сам обработчик перед меткой .wmdestroy:
.wmcreate:
stdcall wmCreateProc, [hwnd]
jmp .finish
INFO
Метки, которые начинаются с точки, являются локальными по отношению к процедуре, где они используются (в данном случае — WindowProc). Это еще одна фишка Flat Assembler, которая позволяет не беспокоиться о том, что в разных процедурах имена меток могут повторяться.
Наш обработчик вызывает пока еще не существующую процедуру wmCreateProc и передает ей значение манипулятора созданного окна. В этой процедуре надо описать действия по резервированию полосы и позиционированию главного окна, после чего обнулить регистр EAX для сигнализирования об успешном завершении процедуры. Если после обработки сообщения WM_CREATE значение регистра EAX будет равно (-1), операционная система воспримет это как сигнал о проблеме, что приведет к завершению работы программы. Текст процедуры wmCreateProc можно разместить после оператора endp, закрывающего блок описания оконной процедуры WindowProc:
proc wmCreateProc,hwnd
invoke MessageBox,[hwnd],_title,_class,MB_OK+MB_ICONINFORMATION
xor eax,eax
ret
endp
INFO
Вот промежуточный вариант программы, который выводит отладочное сообщение: appbar-ver1.asm.
Процедура wmCreateProc в таком виде не делает ничего полезного, но при запуске программы выводит окно с информационным сообщением, которое дает понять, что на этом этапе все работает так, как ожидается.
INFO
Внимательно следи за тем, чтобы количество и порядок аргументов в команде вызова процедуры всегда соответствовали количеству и порядку параметров в ее описании. FASMW это не контролирует, и, если допустить небрежность, можно получить трудно обнаруживаемые ошибки.
ПОДГОТОВКА К ИСПОЛЬЗОВАНИЮ ФУНКЦИИ SHAPPBARMESSAGE
Программа и операционная система договариваются о резервировании пространства для панели инструментов с помощью функции SHAppBarMessage через структуру APPBARDATA. Определение этой структуры отсутствует в подключаемых файлах из поставки FASMW, поэтому придется создать его самому на основе документации. Поместим его в отдельном файле. Для этого выбери пункт меню File → New и на открывшейся вкладке текстового редактора набери следующее:
struct APPBARDATA
cbSize dd ?
hWnd dd ?
uCallbackMessage dd ?
uEdge dd ?
rc RECT
lParam dd ?
ends
Так выглядит описание типа структуры в Flat Assembler. После названия поля следует спецификатор типа (dd означает 4-байтовое двойное слово, а знак вопроса — неопределенное значение), в качестве которого, в свою очередь, может использоваться имя известной структуры (например, RECT). Чтобы не возвращаться к данному вопросу еще раз, в этот же файл можно дописать определения констант, которые используются функцией SHAppBarMessage:
ABM_NEW = 0x0
ABM_REMOVE = 0x1
ABM_QUERYPOS = 0x2
ABM_SETPOS = 0x3
ABE_LEFT = 0
ABE_TOP = 1
ABE_RIGHT = 2
ABE_BOTTOM = 3
MSG_ABNOTIFY = WM_USER+1
APPBAR_THICKNESS = 64
Константы с префиксом ABM_ представляют собой коды услуг, запрашиваемых у операционной системы, а с префиксом ABE_ означают границы рабочего стола, у которых предполагается размещение панели. Константа MSG_ABNOTIFY соответствует «пользовательскому» (то есть нестандартному, выбираемому программистом) идентификатору сообщения, который мы предложим использовать операционной системе при отправке уведомлений окну панели инструментов. Тут же определим константу APPBAR_THICKNESS с желаемым значением ширины панели.
Сохрани набранный текст в файле с именем appbar.inc с помощью пункта меню File → Save As...
INFO
Подключаемый файл с объявлением структуры APPBARDATA и определениями констант: appbar.inc.
Теперь вернись к редактированию файла appbar.asm с текстом программы (для этого можешь нажать сочетание клавиш Ctrl-Tab) и в его начале перед секцией кода вставь строку подключения подготовленных описаний:
include 'appbar.inc'
INFO
После внесения в текст программы законченной порции изменений (как, например, сейчас) проверяй, компилируется ли она. При этом следи, чтобы активной в текстовом редакторе была вкладка с основным текстом программы, а не подключаемым модулем. Если FASM сообщает об ошибке, надо перепроверить последние изменения и устранить ошибку прежде, чем двигаться дальше.
Сейчас, когда у нас есть объявление типа APPBARDATA, мы можем определить глобальную переменную abd этого типа, для чего в секцию данных следует добавить такую строку:
abd APPBARDATA sizeof.APPBARDATA, 0, MSG_ABNOTIFY, ABE_TOP, <0, 0, 0, 0>, 0
Эта строка совмещает определение переменной (при котором резервируется память) с ее инициализацией (при которой полям присваиваются начальные значения). Обрати внимание, что инициализатор поля вложенной структуры abd.rc заключен в угловые скобки. Макроопределения FASM позволяют указать размер структуры APPBARDATA с помощью конструкции sizeof.APPBARDATA.
Функция SHAppBarMessage находится в системной динамической библиотеке Shell32.dll. Поэтому потребуется добавить строки в секцию импорта, в результате чего она приобретет следующий вид:
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
shell32,'SHELL32.DLL'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\shell32.inc'
INFO
Чтобы узнать, в какой динамической библиотеке находится функция Win32 API, надо открыть на официальном сайте Microsoft страницу документации с описанием этой функции и ближе к концу страницы отыскать секцию Requirements. Из этой секции можно узнать минимальную поддерживаемую версию Windows (Minimum supported client/server), имя заголовочного файла для языка программирования С с объявлением этой функции (Header, *.h), статическую библиотеку, которую надо подключить к исполняемому файлу для использования функции (Library, *.lib), и, наконец, имя файла динамической библиотеки (DLL, *.dll).
Убедись, что программа компилируется и работает.
INFO
Промежуточный вариант программы со всеми предварительными объявлениями и определениями: appbar-ver2.asm.
РЕЗЕРВИРОВАНИЕ ПЛОЩАДКИ ДЛЯ ПАНЕЛИ
Подготовительные мероприятия закончены, мы на финишной прямой. Не запускай программу, пока я не скажу. Для проверки синтаксиса можешь периодически компилировать исходный текст в исполняемый файл через пункт меню Run → Compile, но программу не запускай!
Вернемся к блоку с процедурой wmCreateProc, удалим из него строку вывода отладочного сообщения и наполним приложение смыслом. В соответствии с документацией, сначала декларируем желание создать панель инструментов рабочего стола, для чего вызовем функцию SHAppBarMessage с сообщением ABM_NEW и структурой APPBARDATA, в которой заполнены поля cbSize, uCallbackMessage (при инициализации переменной) и hWnd (непосредственно перед вызовом функции):
proc wmCreateProc,hwnd
mov eax, [hwnd]
mov [abd.hWnd], eax
invoke SHAppBarMessage, ABM_NEW, abd
cmp eax, TRUE
je @f
mov eax, -1
ret
@@:
Здесь мы впервые сталкиваемся с ограничением ассемблера, которое не позволяет напрямую пересылать данные из одной ячейки памяти в другую, в результате чего нам пришлось воспользоваться регистром EAX в качестве промежуточного хранилища. После вызова функции проверяем возвращенное ею значение. Если операционная система не возражает против использования окна в качестве панели, то регистр EAX будет содержать TRUE.
Flat Assembler предлагает удобную систему локальных меток для коротких переходов, которая позволяет «перепрыгивать» через несколько команд при подобных проверках. Так, команда je @f совершит переход на ближайшую следующую метку @@ в случае получения равенства при предыдущем сравнении cmp eax, TRUE. Если надо совершить переход на ближайшую предыдущую метку @@, то в команде перехода надо указать @b.
Таким образом, в случае неравенства в регистр EAX будет записано значение (-1) и возвращено операционной системе. Она, как было отмечено выше, воспримет его в качестве признака проблемы при обработке сообщения WM_CREATE, что приведет к завершению работы приложения.
Запросим у Windows границы рабочего стола, в пределах которых можно разместить панель. Без лишней скромности отправим заявку через abd.rc на весь размер экрана, а операционная система приведет значения полей структуры RECT в соответствие реальной обстановке. Для этого узнаем у системы, а потом запишем в abd.rc.right ширину экрана, уменьшенную на единицу, а в abd.rc.bottom — уменьшенную на единицу высоту экрана, после чего вызовем функцию SHAppBarMessage с сообщением ABM_QUERYPOS.
invoke GetSystemMetrics, SM_CXSCREEN
dec eax
mov [abd.rc.right], eax
invoke GetSystemMetrics, SM_CYSCREEN
dec eax
mov [abd.rc.bottom], eax
invoke SHAppBarMessage, ABM_QUERYPOS, abd
После возврата в структуре abd.rc будут находиться границы допустимой области. Нам же нужна только полоса шириной APPBAR_THICKNESS у верхнего края рабочего стола. Поэтому подтянем вверх нижнюю координату и затребуем у операционной системы получившуюся полосу через вызов функции SHAppBarMessage с сообщением ABM_SETPOS:
mov eax, [abd.rc.top]
add eax, APPBAR_THICKNESS
dec eax
mov [abd.rc.bottom], eax
invoke SHAppBarMessage, ABM_SETPOS, abd
xor eax,eax
ret
endp
В конце процедуры оставляем обнуление регистра EAX командой xor eax,eax и возврат этого нулевого значения операционной системе, которое она воспримет как успешное завершение обработчика WM_CREATE.
Пока предположим, что мы все сделали правильно. Результатом выполнения перечисленных действий должно стать отрезание полосы от верхней границы рабочего стола. Если запустить программу в таком виде, то зарезервированная область останется «не у дел» даже после ее завершения и придется перезагружать компьютер. Мы должны явным образом вернуть операционной системе полученную во временное пользование полосу, для чего дополним в оконной процедуре WindowProc блок обработки сообщения WM_DESTROY следующей строкой (ее надо вставить сразу после метки .wmdestroy):
invoke SHAppBarMessage, ABM_REMOVE, abd
INFO
Промежуточный вариант программы, который умеет резервировать и освобождать участок рабочего стола: appbar-ver3.asm.
А вот теперь можно и даже нужно запустить получившуюся программу и посмотреть, как она работает. Пробуй перемещать окна приложений, разворачивать их на весь экран и смотри, как они себя при этом ведут. Обрати внимание, что главное окно получившейся программы существует как будто отдельно от зарезервированной им на рабочем столе полосы. При завершении программы ситуация на рабочем столе должна восстанавливаться. Если остаются какие‑то особенности, то перезагрузи компьютер и внимательно проверь программу.
НАСТРОЙКА ОКНА ПАНЕЛИ
Осталось только привести главное окно нашего приложения в соответствие с его задачей. Для этого, во‑первых, подправим стили окна в строке с вызовом функции CreateWindowEx следующим образом:
invoke CreateWindowEx,WS_EX_TOPMOST+WS_EX_TOOLWINDOW,_class,_title,WS_POPUP,128,128,256,192,NULL,NULL,[wc.hInstance],NULL
А во‑вторых, дополним процедуру wmCreateProc строками, выполняющими позиционирование окна на зарезервированной полосе рабочего стола. Для этого сразу после ее заголовка поместим строку определения локальных переменных:
local width:DWORD, height:DWORD
После имени переменной через двоеточие указан ее тип (как в паскале). Аналогичным образом можно указывать типы параметров процедур. Мы этого не делали, потому что нас устраивает 32-битный тип, предполагаемый по умолчанию. Строки вычисления размеров и перемещения окна надо разместить в конце процедуры перед формированием возвращаемого значения в регистре EAX:
mov eax, [abd.rc.right]
sub eax, [abd.rc.left]
inc eax
mov [width], eax
mov eax, [abd.rc.bottom]
sub eax, [abd.rc.top]
inc eax
mov [height], eax
invoke SetWindowPos, [hwnd], HWND_TOP, [abd.rc.left], [abd.rc.top], [width], [height], SWP_NOACTIVATE+SWP_SHOWWINDOW
INFO
Окончательный вариант программы: appbar.asm.
Запусти получившееся приложение. Место зарезервированной полосы рабочего стола должно занять серое окно без рамок и заголовка — наша заплатка. Не пытайся завершить программу через диспетчер задач, потому что зарезервированная часть рабочего стола в этом случае не освободится. Вместо этого щелкни левой кнопкой мыши по этой полосе и нажми комбинацию клавиш Alt-F4. Программа завершится, а рабочий стол вернется в исходное состояние.
ЗАКЛЮЧЕНИЕ
Так мне удалось устранить главное неудобство от дефекта монитора при использовании развернутых на весь экран приложений. Попутно мы узнали о том, как создать свою панель инструментов для рабочего стола Windows.
Разработанное приложение можно взять в качестве заготовки для более функциональной панели, а не такой, которая, как картина в мультфильме про дядю Федора, «дырку на обоях загораживает». В качестве панели использовано главное окно приложения, что позволяет добавить на него элементы управления, текст, рисунки... Но первым делом я бы добавил защиту от повторного запуска программы (для этого надо воспользоваться именованным мьютексом, который создается с помощью функции CreateMutex). Не помешает добавить и реагирование на уведомления MSG_ABNOTIFY — их может отправлять нашему окну операционная система (например, при запуске полноэкранного приложения или активности соседних панелей).
А вот выбранный инструмент разработки Flat Assembler с ассемблером в качестве языка программирования оставляет противоречивые чувства. К положительным чертам можно отнести:
- компактный размер и быстрое развертывание на любом Windows-компьютере;
- возможность полноценно использовать методологию структурного программирования благодаря макроопределениям, что практически снимает ограничение на сложность проектов;
- одинаковая работа приложений на всей линейке Windows от Windows 2000 до Windows 10 x86-64 благодаря использованию Win32 API;
- малый размер генерируемых EXE-файлов (разработанное в статье приложение занимает 2,5 Кбайт на диске).
Спорные моменты, на мой взгляд, следующие:
- жесткая привязка к архитектуре процессоров Intel;
- слабая «защита от дурака» (практически отсутствует контроль типов переменных и соответствия фактических аргументов при вызове функций объявленным формальным параметрам).
Как бы то ни было, с использованием Flat Assembler вполне можно изучать основы программирования на ассемблере для архитектуры х86. Этот инструмент поможет освоить синтаксис и основные конструкции языка всем, кто еще не сталкивался с ассемблером, но желает использовать его в будущем для создания собственных приложений.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei