Анимация текста
Очень интересного эффекта можно добиться с помощью анимации текста иконок. Для этого достаточно знать, как изменить цвет, а анимацию после этого можно сделать любым циклом, который по определенному алгоритму будет изменять цвет текста.
Чтобы изменить цвет текста, нужно послать с помощью функции SendMessage сообщение LVM_SETITEMTEXT. В качестве третьего параметра указывается 0, а последний параметр — это цвет. Например, чтобы сделать цвет текста черным, нужно выполнить следующий код:
HWND DesktopHandle = FindWindow("ProgMan", 0);
DesktopHandle = GetWindow(DesktopHandle, GW_CHILD);
DesktopHandle = GetWindow(DesktopHandle, GW_CHILD);
SendMessage(DesktopHandle, LVM_SETITEMTEXT, 0, (LPARAM) (COLORREF)0);
Единственное, что может вызвать трудность, — обновление Рабочего стола, потому что изменения будут видны только после перерисовки экрана.
Безбашенные окна
Вы уже научились делать окна на основе простых фигур (овал, прямоугольник) и их сочетания, и теперь нам предстоит узнать, как создать окно совершенно произвольной формы. Такое окно уже невозможно будет сделать парой комбинаций. Тут уже нужны более сложные манипуляции.
На 3.9 представлена картинка с красным фоном. Как сделать окно, которое будет содержать это изображение, а красный цвет сделать прозрачным, чтобы окно приняло форму картинки? Сложно себе представить, как это сделать с помощью комбинирования регионов из простых фигур (хотя и возможно, просто их понадобится много).
3.9. Маска для будущего окна
В WinAPI есть еще регионы типа полигонов, но это не сильно упростит задачу.
Итак, создадим один большой регион, который опишет наше изображение. Код будет универсальным, и вы сможете подставить вместо моей картинки что-то другое. Как мы будем это делать? Очень просто.
Что представляет собой изображение? Это двухмерный массив из точек. Нам нужно взглянуть на каждую строку, как на отдельный элемент региона, т. е. мы будем строить прямоугольный регион по каждой строке, а потом скомбинируем все вместе. Алгоритм будет выглядеть следующим образом:
Сканируем строку и находим первый пиксел, отличный от прозрачного. Это будет начало нашего прямоугольника (координата X1).
Сканируем остаток строки, чтобы найти границу прозрачности (последний непрозрачный пиксел, координата Х2). Если прозрачных пикселов нет, то регион строится до конца строки.
Координату Y1 принимаем равной номеру строки, a Y2 — равной Y1+1. Таким образом, высота прямоугольника, описывающего одну строку, равна одному пикселу.
Строим регион по найденным координатам.
Переходим к следующей строке.
Объединяем созданные регионы и назначаем их окну.
Это упрощенный алгоритм, а в реальной жизни может быть два и более прямоугольных региона на одну строку, когда в середине строки встречаются прозрачные области.
Описанный алгоритм реализован в виде кода на языке C++ и представлен в листинге 3.4. Чуть позже я его рассмотрю.
А пока поговорим о том, каким должен быть графический файл. Это может быть любой Windows BITMAP-файл. Его размеры можно рассчитать в зависимости от величины изображения, но в данном случае ограничимся заранее определенными значениями (200x200 пикселов). Самостоятельно попробуйте сделать код еще более универсальным.
В самой картинке цвет пиксела с координатами 0:0 считается прозрачным, поэтому при подготовке изображения надо учесть, что все прозрачные области в окне должны быть окрашены этим цветом. Это более универсально, чем использовать заранее определенный цвет, потому что он может быть необходим изображению. А вот левый верхний угол чаще всего свободен, но даже если и нет, один пиксел всегда можно сделать прозрачным (т.е. не учитывать). На общую картину это не повлияет.
Создайте новый проект Win32 Project. Найдите функцию InitInstance и измените функцию создания окна следующим образом:
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 200, 200, NULL, NULL, hInstance, NULL);
Здесь числа 200 указывают ширину и высоту окна. Если ваше изображение другого размера, то измените эти значения.
Кроме того, нужно убрать меню, потому что использовать его нет смысла. Для этого найдите функцию MyRegisterClass и строку, где изменяется свойство wcex.lpszMenuName. Ему нужно присвоить нулевое значение:
wcex.lpszMenuName = 0;
В разделе глобальных переменных нужно добавить следующие две переменные:
HBITMAP maskBitmap;
HWND hWnd;
Первая переменная будет использоваться для хранения изображения, а вторую мы уже неоднократно использовали для хранения указателя на окно. Объявление переменной hWnd надо удалить из функции InitInstance, чтобы использовать глобальную переменную.
Теперь измените функцию _tWinMain в соответствии с листингом 3.4, и можно считать, что ваша программа готова.
Прежде чем запускать программу, просто откомпилируйте ее и перейдите в папку, в которой находятся исходные коды. Если вы будете работать с программой в режиме отладки, то у вас должна появиться в этом месте подпапка Debug, иначе — Release. Поместите в нее подготовленный графический файл. Если файла не будет, то произойдет ошибка. Если вы компилировали в разных режимах, то лучше поместить файл сразу в обе папки на случай повторного переключения режима.
Листинг 3.4. Создание окна произвольной формы на основе маски |
// Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_MASKWINDOW, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
// Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MASKWINDOW);
// Следующий код вы должны добавить // Сначала убираем обрамление int Style; Style = GetWindowLong(hWnd, GWL_STYLE); Style=Style || WS_CAPTION; Style=Style || WS_SYSMENU; SetWindowLong(hWnd, GWL_STYLE, Style); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
// Загружаем картинку maskBitmap = (HBITMAP)LoadImage(NULL, "mask.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!maskBitmap) return NULL;
// Описание необходимых переменных BITMAP bi; BYTE bpp; DWORD TransPixel; DWORD pixel; int startx; INT i, j;
HRGN Rgn, ResRgn = CreateRectRgn(0, 0, 0, 0);
GetObject(maskBitmap, sizeof( BITMAP ), bi);
bpp = bi.bmBitsPixel 3; BYTE *pBits = new BYTE[ bi.bmWidth * bi.bmHeight * bpp ];
// Получаем битовый массив int p = GetBitmapBits( maskBitmap, bi.bmWidth * bi.bmHeight * bpp, pBits );
// Определяем цвет прозрачного символа TransPixel = *(DWORD*)pBits;
TransPixel = 32 - bi.bmBitsPixel;
// Цикл сканирования строк for (i = 0; i bi.bmHeight; i++) { startx=-1; for (j = 0; j bi.bmWidth; j++) { pixel = *(DWORD*)(pBits + (i * bi.bmWidth + j) * bpp) (32 - bi.bmBitsPixel); if (pixel != TransPixel) { if (startx0) { startx = j; } else if (j == (bi.bmWidth - 1)) { Rgn = CreateRectRgn( startx, i, j, i + 1 ); CombineRgn( ResRgn, ResRgn, Rgn, RGN_OR); startx=-1; } } else if (startx=0) { Rgn = CreateRectRgn(startx, i, j, i + 1); CombineRgn(ResRgn, ResRgn, Rgn, RGN_OR); startx=-1; } } } delete pBits; SetWindowRgn(hWnd, ResRgn, TRUE); InvalidateRect(hWnd, 0, false); //Конец добавляемого кода
// Main message loop: while (GetMessage(msg, NULL, 0, false)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } }
return (int) msg.wParam; }
В самом начале из окна убирается системное меню и обрамление. После этого загружается картинка уже знакомой функцией LoadImage. Изображение читается из файла, поэтому первый параметр равен NULL, второй — содержит имя файла, а в последнем — указан флаг LR_LOADFROMFILE. Так как мы указали только имя файла (без полного пути), то программа будет искать его в том же каталоге, где находится программа. Именно поэтому мы должны были скопировать mask.bmp в папку Debug или/и Release.
Необходимо проверить наличие файла изображения. Если переменная maskBitmap равна нулю, то картинка не была найдена, и произойдет выход из программы:
if (!maskBitmap) return NULL;
Это обязательная проверка, потому что дальнейшее обращение к памяти, где должны быть данные, приведет к ненужной ошибке.
Последующий код довольно сложный, для его понимания нужно достаточно хорошо знать работу с указателями, поэтому описывать его в данной книге не имеет смысла.
Если вы сейчас запустите пример, то увидите окно, как на 3.10. Окно действительно приняло форму изображения, но оно пустое. Был создан только регион, но в самом окне ничего не нарисовано. Чтобы содержимое окна наполнить изображением картинки, надо в обработчик события WM_PAINT функции wndProc добавить следующий код (полный код примера смотрите на компакт-диске):
case WM_PAINT: hdc = BeginPaint(hWnd, ps); // TODO: Add any drawing code here... hdcBits=::CreateCompatibleDC(hdc); SelectObject(hdcBits, maskBitmap); BitBlt(hdc, 0, 0, 200, 200, hdcBits, 0, 0, SRCCOPY); DeleteDC(hdcBits); EndPaint(hWnd, ps); break;
Здесь просто выводится изображение точно так же, как при рисовании кнопки Пуск. Вот теперь программа закончена, и вы можете увидеть результат ее работы на 3.11.
3.10. Окно в форме рисунка
3.11. Приложение с окном произвольной формы
Примечание |
Исходный код примера , описанного в этом разделе , вы можете найти на компакт - диске в каталоге \Demo\Chapter3\MaskWindow. |
Динамическая библиотека для расшифровки паролей
Для этого примера я написал DLL-файл, процесс создания которого будет сейчас расписан на ваших глазах. Создайте новый проект Win32 Project в Visual C++ и назовите его OpenPassDLL. В Мастере настроек приложения (Application Settings) выберите тип приложения — DLL ( 3.12).
3.12. Окно Мастера настроек нового приложения DLL
В новом проекте у вас будет только один файл OpenPassDLL.cpp (не считая стандартного stdafx.cpp ), но заголовочного файла для него не будет. В заголовочных файлах принято писать все объявления, а нам они понадобятся, поэтому давайте создадим такой файл. Для этого щелкните правой кнопкой мыши в окне Solution Explorer по пункту Header Files и в появившемся меню выберите пункт Add/Add New Item. Перед вами должно открыться окно, как на рисунке 3.13. Выберите в правой части окна тип файла Header File (.h), а в поле Name укажите OpenPassDLL.h. Нажмите кнопку Open, чтобы добавить новый файл к проекту.
3.13. Окно создания заголовочного файла
Щелкните дважды по созданному файлу, чтобы открыть его в редакторе, и напишите в нем следующий код:
// Macro for DLL exports in Win32, replaces Win16 __export
// (Макрос для экспорта DLL в Win32 вместо 16-битной версии)
#define DllExport extern "С" __declspec(dllexport)
// Prototype
// (Прототип)
DllExport void RunStopHook(bool State, HINSTANCE hInstance);
Сначала описывается макрос DllExport, с помощью которого можно указывать, какие процедуры будут экспортироваться (можно вызывать из других приложений).
Во второй строке кода описывается сама экспортируемая процедура. Как видите, объявление похоже на реализацию, но отсутствует сам код процедуры, а есть только название и передаваемые значения. Сама процедура должна быть написана в файле OpenPassDLL.cpp.
Переходим к файлу OpenPassDLL.cpp. Его содержимое вы можете увидеть в листинге 3.6. Повторите этот код в своем файле и внимательно изучите.
Листинг 3.6. Код файла OpenPassDLL.cpp |
#include windows.h #include "stdafx.h" #include "OpenPassDLL.h"
HHOOK SysHook; HWND Wnd; HINSTANCE hInst;
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { hInst=(HINSTANCE)hModule; return TRUE; }
LRESULT CALLBACK SysMsgProc(
int code, // hook code (код ловушки) WPARAM wParam, // removal flag (флаг) LPARAM lParam // address of structure with message // (адрес структуры с сообщением) ) { //Передать сообщение другим ловушкам в системе CallNextHookEx(SysHook, code, wParam, lParam);
//Проверяю сообщение if (code == HC_ACTION) { //Получаю идентификатор окна сгенерировавшего сообщение Wnd=((tagMSG*)lParam)-hwnd;
//Проверяю тип сообщения. //Если была нажата левая кнопка мыши if (((tagMSG*)lParam)-message == WM_RBUTTONDOWN) { SendMessage(Wnd, EM_SETPASSWORDCHAR, 0, 0); InvalidateRect(Wnd, 0, true); } }
return 0; }
///////////////////////////////////////////////////////////////////
DllExport void RunStopHook(bool State, HINSTANCE hInstance) { if (true) SysHook = SetWindowsHookEx(WH_GETMESSAGE, SysMsgProc, hInst, 0); else UnhookWindowsHookEx(SysHook); }
Разберем подробно исходный код динамической библиотеки. В самом начале подключаются заголовочные файлы. OpenPassDLL.h — это созданный нами файл, в котором объявлены макрос и функция, которая будет экспортирована.
Далее идет определение глобальных переменных библиотеки. Их будет три:
SysHook — идентификатор ловушки системных сообщений;
Wnd — указатель на окно (со звездочками), в котором щелкнул пользователь. Эту переменную можно было сделать и локальной, но я решил ее вынести в глобальную область для последующего использования;
HInst — идентификатор экземпляра библиотеки.
Описания закончены. В области программы первой идет функция DllMain. Это стандартная функция, которая выполняется при загрузке библиотеки. В ней можно производить действия по начальной инициализации. В нашем случае ничего инициализировать не надо, но в качестве первого параметра этой функции мы получаем экземпляр библиотеки, который сохраняем в переменной hInst.
Теперь рассмотрим функцию RunStopHook. Она будет запускать и останавливать системную ловушку. В качестве параметров нужно передать два значения:
логический параметр — значение true, если надо запустить ловушку, иначе — остановить;
идентификатор экземпляра приложения, вызвавшего эту функцию, — пока не будем использовать этот параметр.
Если в качестве первого параметра передано значение true , то регистрируется ловушка, которая будет принимать все сообщения Windows на себя. Для этого используется функция SetWindowsHookEx. У этой функции должно быть четыре параметра:
тип ловушки — в данном случае WH_GETMESSAGE;
указатель на функцию, которой будут пересылаться сообщения Windows;
указатель на экземпляр приложения — переменная, в которой сохранен экземпляр библиотеки;
идентификатор потока — если указан ноль, то используются все существующие потоки.
В качестве второго параметра указано имя функции SysMsgProc. Она также описана в этой библиотеке. Но ее мы рассмотрим чуть позже.
Значение, которое возвращает функция SetWindowsHookEx, сохраняется в переменной SysHook. Оно понадобится при отключении ловушки.
Если процедура RunStopHook получила в качестве параметра значение false, то нужно отключить ловушку. Для этого вызывается процедура UnhookWindowsHookEx, которой передается значение переменной SysHook. Это то значение, которое было получено при создании ловушки.
Теперь давайте посмотрим на функцию SysMsgProc, которая будет вызываться при наступлении системных событий.
В первой строке пойманное сообщение передается остальным ловушкам, установленным в системе с помощью CallNextHookEx. Если этого не сделать, то другие обработчики не смогут узнать о наступившем событии, и система будет работать некорректно.
Далее проверяется тип полученного сообщения. Нам нужно обрабатывать событие нажатия кнопки мышки, значит, параметр code должен быть равен HC_ACTION; сообщения другого типа нам нет смысла обрабатывать.
После этого определяется окно, сгенерировавшее событие, и проверяется тип события. Указатель на окно можно получить так: ((tagMSG*)lParam)-hwnd. На первый взгляд запись абсолютно непонятная, но попробуем в ней разобраться. Основа этой записи — переменная lParam, которая получена в качестве последнего параметра нашей функции-ловушки SysMsgProc. Запись ((tagMSG*)lParam) обозначает, что структура типа tagMSG находится по адресу памяти, указатель на который передан через параметр lParam. У этой структуры есть параметр hwnd, в котором находится указатель на окно, сгенерировавшее сообщение.
Следующим этапом проверяется событие нажатия кнопки мыши: если была нажата правая кнопка мышки, то в этом окне нужно убрать звездочки. Для этого проверяется содержимое поля message все той же структуры ((tagMSG*)lParam).
Если это свойство равно WM_RBUTTONDOWN, т o нажата правая кнопка мыши, и надо убрать звездочки. Для этого окну посылается сообщение SendMessage со следующими параметрами:
Wnd — окно, которому предназначено сообщение;
EM_SETPASSWORDCHAR — тип сообщения. Этот тип говорит о том, что надо изменить символ, который будет использоваться для того, чтобы спрятать пароль;
0 — новый символ, при отправке которого текущий символ-маска просто исчезнет, и будет восстановлен реальный текст;
0 — зарезервировано.
Напоследок вызывается функция InvalidateRect, которая заново прорисует указанное в первом параметре окно. Это все то же окно, в котором произведен щелчок. Во втором параметре указывается область, которую надо прорисовать, значение 0 равносильно прорисовке всего окна. Если последний параметр равен true, то надо перерисовать и фон.
Примечание |
Исходный код библиотеки вы можете найти на компакт - диске в каталоге \Demo\Chapter3\OpenPassDLL. |
Дрожь в ногах
Теперь усложним написанный в предыдущем разделе пример и напишем программу, которая будет перебирать все окна и изменять их размеры и положение, чтобы создавалось впечатление дрожания.
Создайте в Visual C++ новый проект типа Win32 Project. Добавьте пуню-меню для вызова команды дрожания, потом найдите в исходном коде функцию WndProc, где обрабатываются все события окна. Между дрожаниями понадобится задержка, чтобы пользователь успел увидеть изменения, но они не казались слишком частыми, поэтому в начале функции объявляется переменная типа HANDLE, которая инициализируется функцией CreateEvent:
HANDLE h;
h = CreateEvent(0, true, false, "et ");
Теперь обработчик события будет выглядеть следующим образом:
case ID_MYCOMMAND_VIBRATION: while (TRUE) { EnumWindows(EnumWindowsWnd, 0); WaitForSingleObject(h, 10); // Задержка в 500 миллисекунд }
Так же как и в предыдущем примере, сначала запускается бесконечный цикл, внутри которого вызывается функция перебора всех окон и создается задержка уже знакомой нам функцией WaitForSingleObject.
Самое интересное скрывается в функции EnumWindowsWnd, код которой вы можете увидеть в листинге 3.1.
Листинг 3.1. Код функции EnumWindowsWnd |
BOOL CALLBACK EnumWindowsWnd( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ) { if (IsWindowVisible(hwnd)==FALSE) return TRUE;
RECT rect; GetWindowRect(hwnd, rect);
int index=rand()%2; if (index==0) { rect.top=rect.top+3; rect.left=rect.left+3; } else { rect.top=rect.top-3; rect.left=rect.left-3; }
MoveWindow(hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE);
return TRUE; }
Теперь посмотрим на функцию-ловушку EnumWindowsWnd, которая будет вызываться каждый раз, когда найдено окно. Тут первой запускается функция IsWindowVisible, которая проверяет, является ли найденное окно видимым. Если нет, то возвращается значение TRUE, происходит выход из ловушки, и поиск следующего окна будет продолжен, иначе он остановится, и следующее окно не будет найдено. Если окно невидимо, то нет смысла его двигать или изменять размер.
После этого вызывается функция GetWindowRect. Этой функции передается в первом параметре идентификатор найденного окна, а она возвращает во втором параметре размеры этого окна в структуре RECT, описывающей прямоугольную область на экране с параметрами left, top, bottom, right.
После получения величины окна генерируется случайное число от 0 до 1 с помощью функции rand. После этого необходимо проверить, если сгенерированное число равно 0, то увеличиваем свойства top и left структуры rect на 3 пиксела, иначе эти значения уменьшаем.
Изменив значения переменных структуры, в которой хранились размеры найденного окна, перемещаем это окно с помощью функции MoveWindow. Эта функция имеет следующие параметры:
идентификатор окна, позицию которого надо изменить (h);
новая позиция левого края (rect.left);
новая позиция верхнего края (rect.top);
новая ширина (rect.right-rect.left);
новая высота (rect.bottom-rect.top).
Ну, и напоследок, результату работы функции присваиваем значение TRUE, чтобы поиск продолжился.
Получается, что если запустить программу, то вы увидите дрожание всех запущенных окон. Программа будет перебирать все окна и случайным образом изменять их положение. Попробуйте запустить пример и посмотреть этот эффект в действии, он потрясающий, т. е. сотрясающий.
Примечание |
Исходный код этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter3\Vibration. |
Использование буфера обмена
Шутить можно над чем угодно, и буфер обмена тут не исключение. Вроде безобидная вещь, а может стать очень мощным инструментом в руках хакера. Главное — творческий подход.
Итак, буфер используется для того, чтобы пользователь мог переносить данные из программы в программу или копировать несколько раз одинаковый текст. Что ожидает пользователь? Вставляемые данные должны соответствовать скопированным. Вот тут мы можем сделать неожиданный ход.
В Windows есть функция и события, с помощью которых можно отслеживать состояние системного буфера. Это необходимо, чтобы кнопка Вставить из буфера обмена была доступна, только когда в буфере есть данные необходимого формата. Можно воспользоваться этими возможностями в своих целях.
Давайте создадим программу, которая будет следить за буфером, а при его изменении — портить содержимое. Создайте новое MFC-приложение (можно на основе диалогового окна) с именем ClipboardChange.
Добавим два новых события, которые должна будет обрабатывать наша программа, чтобы следить за состоянием буфера: ON_WM_CHANGECBCHAIN и ON_WM_DRAWCLIPBOARD. Для этого откройте файл ClipboardChangeDlg.cpp, найдите карту сообщений и добавьте туда названия необходимых нам событий:
BEGIN_MESSAGE_MAP(CClipboardChangeDlg, CDialog) ON_WM_CHANGECBCHAIN() ON_WM_DRAWCLIPBOARD() ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP END_MESSAGE_MAP()
Теперь откройте файл ClipboardChangeDlg.h и добавьте в него описания функций, которые будут вызываться в ответ на события буфера обмена. Их нужно объявить в разделе protected нашего класса следующим образом:
afx_msg void OnChangeCbChain(HWND hWndRemove, HWND hWndAfter);
afx_msg void OnDrawClipboard();
Нам также понадобится переменная типа HWND, в которой будет храниться указатель на окно-просмотрщик буфера. Назовите ее ClipboardViewer.
Снова вернитесь в файл ClipboardChangeDlg.cpp, где нужно добавить код этих функций. Но они не будут вызываться, пока мы не сделаем нашу программу наблюдателем за буфером обмена. Для этого в функции OnInitDialog добавьте строку:
CiipboardViewer = SetClipboardViewer();
Вот теперь можно перейти к рассмотрению двух функций, которые вызываются на события буфера. Код обеих функций приведен в листинге 3.10.
Листинг 3.10. Функции наблюдения за буфером |
if ( NULL != ClipboardViewer ) { ::SendMessage ( ClipboardViewer, WM_CHANGECBCHAIN, (WPARAM) hWndRemove, (LPARAM) hWndAfter ); }
CClipboardChangeDlg::OnChangeCbChain(hWndRemove, hWndAfter); }
void CClipboardChangeDlg::OnDrawClipboard() { if (!OpenClipboard()) { MessageBox("The clipboard is temporarily unavailable"); return; } if (!EmptyClipboard()) { CloseClipboard(); MessageBox("The clipboard cannot be emptied"); return; }
CString Text="You are hacked"; HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, Text.GetLength()+1);
if (!hGlobal) { CloseClipboard(); MessageBox(CString("Memory allocation error")); return; }
strcpy((char *)GlobalLock(hGlobal), Text); GlobalUnlock(hGlobal); if (!SetClipboardData(CF_TEXT, hGlobal)) { MessageBox("Error setting clipboard"); } CloseClipboard(); }
Самое интересное происходит в функции OnDrawClipboard, которая вызывается каждый раз, когда в буфер попадают новые данные. По этому событию надо очищать содержимое буфера обмена и помещать туда свои данные, т.е. пользователь не сможет воспользоваться операцией копирования.
Прежде чем работать с буфером, его необходимо открыть. Для этого используется функция OpenClipboard. Если открытие прошло успешно, то она возвращает TRUE.
После этого очищается буфер обмена с помощью функции EmptyClipboard. Если функция отработала успешно, то она возвращает TRUE, иначе буфер закрывается, и выводится сообщение об ошибке. Для закрытия используется функция CloseClipboard.
Теперь можно копировать свои данные в буфер обмена. Для этого нужно в глобальной области вьщелить память необходимого объема и скопировать туда нужный текст. Я поместил туда сообщение "You are hacked". После этого переносим данные из памяти в буфер с помощью функции SetClipboardData, у которой есть два параметра:
константа, определяющая тип данных — CF_TEXT, соответствует текстовым данным;
указатель на данные, которые должны быть помещены в буфер обмена.
После работы следует обязательно закрыть буфер с помощью функции CloseClipboard.
Вот таким простым способом, благодаря нашему воображению, абсолютно безобидный буфер обмена превратился в интересную шутку.
Попробуйте запустить программу и скопировать что-нибудь в буфер обмена. После вставки вместо скопированных данных вы увидите текст " You are hacked".
Примечание |
Исходный код программы, описанной в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter3\ClipboardChange. |
Мониторинг исполняемых файлов
Часто в жизни возникают ситуации, когда необходимо определить, какие программы запускает пользователь и сколько времени он работает. Этот вопрос интересует не только хакеров, но и администраторов сетей, и руководителей предприятий.
Хакер может ожидать, когда запустится определенная программа, чтобы произвести с ней какие-нибудь манипуляции. Администратора сети интересует, что сделал пользователь, прежде чем завис компьютер. Начальника интересует использование рабочего времени.
Именно на этих задачах мне пришлось разобраться, как отследить, какие программы запускаются и сколько времени находятся в рабочем состоянии.
Данная проблема решается достаточно просто, и программа будет похожа на разгадывание паролей: также необходимо создать ловушку, которая будет отслеживать определенные системные сообщения. В предыдущем примере ловушка устанавливалась с помощью API-функции SetWindowsHookEx и регистрировались сообщения типа WH_GETMESSAGE. Если этот параметр изменить на WH_CBT, то такая ловушка сможет фиксировать следующие сообщения:
HCBT_ACTIVATE — приложение активизировалось;
HCBT_CREATEWND — создано новое окно;
HCBT_DESTROYWND — уничтожено существующее окно;
HCBT_MINMAX — окно свернули или развернули на весь экран;
HCBT_MOVESIZE — окно переместили или изменили размер.
Таким образом, динамическая библиотека для мониторинга запускаемых программ должна соответствовать коду в листинге 3.9. Пока остановимся на поиске событий без их обработки. В реальном приложении может понадобиться сохранение полученных событий и названий окон, с которыми работал пользователь, в каком-нибудь файле. Впоследствии по этой информации можно легко узнать, с чем и сколько работал пользователь.
Листинг 3.9. Библиотека мониторинга запускных файлов |
#include windows.h #include "stdafx.h" #include "FileMonitor.h"
HHOOK SysHook; HINSTANCE hInst;
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { hInst=(HINSTANCE)hModule; return TRUE; }
LRESULT CALLBACK SysMsgProc(
int code, // hook code WPARAM wParam, // removal flag LPARAM lParam // address of structure with message ) { //Передать сообщение другим ловушкам в системе CallNextHookEx(SysHook, code, wParam, lParam);
if (code == HCBT_ACTIVATE) { char windtext[255]; HWND Wnd=((tagMSG*)lParam)-hwnd; GetWindowText(Wnd, windtext, 255);
// Here you can save active window title // (Здесь можно сохранить заголовок активного окна) }
if (code == HCBT_CREATEWND) { char windtext[255]; HWND Wnd=((tagMSG*)lParam)-hwnd; GetWindowText(Wnd, windtext, 255);
// Here you can save New file title // (Здесь можно сохранить заголовок нового окна) } return 0; }
///////////////////////////////////////////////////////////////////
DllExport void RunStopHook(bool State, HINSTANCE hInstance) { if (true) SysHook = SetWindowsHookEx(WH_CBT, SysMsgProc, hInst, 0); else UnhookWindowsHookEx(SysHook); }
Когда создано новое окно или активировано уже существующее, то вызывается наша ловушка с добавленным кодом определения названия окна, которое сгенерировало событие. Дальше вы можете добавить свой код, который будет выполнять необходимые действия (например, сохранять в файле дату и время создания или активации окна). А я в целях экономии места в книге опущу этот момент и оставлю на ваше усмотрение, потому что дальнейшие действия зависят от поставленной цели.
Вот таким нехитрым способом можно получить доступ к сообщениям о событиях, произошедших с окнами, и тем самым контролировать, что происходит с программами на компьютере пользователя. Вот так мы.
Примечание |
Исходный код библиотеки, описанный в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter3\FileMonitor, а исходный код примера для тестирования этой библиотеки — в каталоге \Demo\Chapter3\FileMonitorTest. Для запуска программы необходимо, чтобы библиотека FileMonitor.dll находилась в каталоге, из которого запускается тестовый пример. |
Нестандартные окна
Еще в 1995 году почти все окна были прямоугольными, и всех это устраивало. Но несколько лет назад начался самый настоящий бум на создание окон неправильной формы. Любой хороший программист считает своим долгом сделать свое окно непрямоугольной формы, чтобы его программа явно выделялась среди всех конкурентов.
Лично я против нестандартных окон, и использую их очень редко. Об этом мы уже говорили в начале книги, но я напоминаю, потому что этот вопрос очень важен для любого коммерческого продукта. Но иногда сталкиваешься с ситуацией, когда необходимо сделать окно красивым и произвольной формы. Да и специфика рассматриваемых в книге примеров позволяет использовать воображение, как угодно. Поэтому мы просто обязаны рассмотреть данную тему очень подробно.
Для начала попробуем создать окно овальной формы. Создайте новый проект Win32 Project и подкорректируйте функцию InitInstance в соответствии с листингом 3.3. Код, который вам надо добавить, выделен комментариями.
Листинг 3.3. Создание окна овальной формы |
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd) { return FALSE; }
// Начало кода, который надо добавить HRGN FormRgn, RectRgn; RECT WRct; GetWindowRect(hWnd, WRct); FormRgn=CreateEllipticRgn(0,0,WRct.right-WRct.left,WRct.bottom-WRct.top);
RectRgn=CreateRectRgn(100, 100, WRct.right-WRct.left-100,WRct.bottom-WRct.top-100); CombineRgn(FormRgn,FormRgn,RectRgn,RGN_DIFF); SetWindowRgn(hWnd, FormRgn, TRUE); // Конец кода
ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
return TRUE; }
Первым делом надо объявить две переменные:
FormRgn типа HRGN — используется для хранения регионов, которые описывают внешний вид окна;
WRect типа RECT — для хранения размеров и положения окна, чтобы знать область, по которой строить овал.
На следующем этапе получаем размеры окна с помощью уже знакомой функции GetWindowRect. Теперь все готово для построения овальной области. Для этого используются две функции: CreateEllipticRgn И SetWindowRgn. Рассмотрим их подробнее:
HRGN CreateEllipticRgn( int nLeftRect, // х - координата левого верхнего угла int nTopRect, // у - координата левого верхнего угла int nRightRect, // х - координата правого нижнего угла int nBottomRect // у - координата правого нижнего угла );
Данная функция создает регион окна (область) в виде эллипса. В качестве параметров передаются размеры эллипса.
int SetWindowRgn( HWND hWnd, // Указатель на окно HRGN hRgn, // Предварительно созданный регион BOOL bRedraw // Флаг перерисовки окна );
Эта функция назначает указанному в качестве первого параметра окну созданный регион, который передается во втором параметре. Если последний параметр (флаг) равен TRUE, то окно после назначения нового региона будет перерисовано, иначе это придется сделать в явном виде самостоятельно. В предложенном коде после установки региона есть вызов функции UpdateWindow, которая перерисовывает окно, поэтому последний параметр можно было бы установить и в FALSE.
Запустите приложение, и вы увидите окно овальной формы ( 3.3).
Теперь немного усложним задачу и попробуем создать овальное окно с прямоугольным отверстием в центре. Для этого нужно изменить код следующим образом:
HRGN FormRgn, RectRgn; RECT WRct; GetwindowRect(hWnd, WRct); FormRgn=CreateEllipticRgn(0,0,WRct.right-WRct.left,WRct.bottom-WRct.top);
RectRgn=CreateRectRgn(100, 100, WRct.right-WRct.left-100, WRct.bottom-WRct.top-100); CombineRgn(FormRgn,FormRgn,RectRgn,RGN_DIFF); SetWindowRgn(hWnd, FormRgn, TRUE);
3.3. Окно овальной формы
Здесь объявлены две переменные типа HRGN. В первой (FormRng) создается овальный регион функцией CreateEllipticRgn, а во второй — прямоугольный с помощью функции CreateRectRgn. Для функции CreateRectRgn так же как и при создании овального региона указываются четыре координаты, задающие размер прямоугольника. Результат сохраняется в переменной RectRng.
После создания двух областей они объединяются с помощью функции CombineRng:
int CombineRgn( HRGN hrgnDest, // Указатель на результирующий регион HRGN hrgnSrc1, // Указатель на первый регион HRGN hrgnSrc2, // Указатель на второй регион int fnCombineMode // Метод комбинирования );
Эта функция комбинирует два региона hrgnSrc1 и hrgnSrc2 и помещает результат в переменную HrgnDest.
В функции необходимо задать режим слияния (переменная fnCombineMode). Можно указать один из следующих вариантов:
RGN_AND — объединить два региона (область перекрывания);
RGN_COPY — копировать (копия первой области);
RGN_DIFF — объединить разницей (удаление второй области из первой);
RGN_OR — объединить области;
RGN_XOR — объединить области, исключая все пересечения.
Результат работы программы вы можете увидеть на 3.4.
3.4. Овальное окно с прямоугольным отверстием в центре
Примечание |
Исходный код этого примера вы можете найти в каталоге \Demo\Chapter3\NoneUniformWindow. |
Создайте новый проект типа MFC Application. Нам сейчас не понадобится минимальный код, поэтому для упрощения воспользуемся объектной библиотекой MFC.
В Мастере создания проекта откройте раздел Application Type и выберите тип приложения Dialog based ( 3.5). Остальные настройки можно не менять. Я дал проекту имя None.
Откройте файл ресурсов и в разделе DIALOG дважды щелкните по пункту IDD_NONE_DIALOG. Поместите на форму ( 3.6) один компонент List Control.
Теперь, чтобы с этим элементом управления можно было работать, щелкните по нему правой кнопкой мыши и выберите в появившемся меню пункт Add Variable.... В открывшемся окне достаточно ввести имя переменной в поле Variable name. Укажите имя ItemsList ( 3.7). Нажмите кнопку Finish, чтобы завершить создание переменной.
З.5. Выбор типа приложения в окне Мастера создания проекта
З.6. Форма будущей программы None
З.7. Окно создания переменной для элементов управления
3.8. Результат работы программы None
Откройте файл NoneDlg.cpp и найдите здесь функцию CNoneDlg::OnInitDialog(). В самый конец функции, где написан комментарий // TODO: Add extra initialization here, добавьте следующий код:
// TODO: Add extra initialization here RECT WRct; HRGN FormRgn; ::GetWindowRect(ItemsList, WRct); FormRgn=CreateEllipticRgn(0,0,WRct.right-WRct.left,WRct.bottom-WRct.top); ::SetWindowRgn(ItemsList, FormRgn, TRUE);
Здесь выполняется уже знакомый код, только вместо указателя на окно используется переменная элемента управления List Control. Перед функциями GetWindowRect и SetWindowRect стоит знак "::", который указывает на необходимость использования этих функций из набора WinAPI, а не MFC.
Примечание |
Исходный код этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter3\None. |
Обновление иконки
Код, который мы рассматривали в разд. 3.9.1 для анимации иконки, не эффективен, потому что реально не будет видно движения. Пользователь увидит только начальное и конечное положение, а перемещение останется за кадром. Чтобы исправить этот недостаток, нужно после изменения позиции иконки обновлять ее. Для этого нужно послать сообщение LVM_UPDATE:
HWND DesktopHandle = FindWindow("ProgMan", 0); DesktopHandle = GetWindow(DesktopHandle, GW_CHILD); DesktopHandle = GetWindow(DesktopHandle, GW_CHILD); for (int i=0; i100; i++) { SendMessage(DesktopHandle, LVM_SETITEMPOSITION, 0, MAKELPARAM(10, i)); SendMessage(DesktopHandle, LVM_UPDATE, 0, 0); Sleep(10); }
Здесь внутри цикла изменяется позиция нулевой иконки и обновляется ее изображение с помощью сообщения LVM_UPDATE. В данном случае третий параметр функци SendMessage также указывает на номер обновляемого элемента. Если нужно перерисовать вторую иконку, то код будет выглядеть так:
SendMessage(DesktopHandle, LVM_UPDATE, 2, 0);
От пользы к шутке
Этот пример очень легко превратить в шуточный. Достаточно только поменять в динамической библиотеке пару параметров, и работа программы изменится. Попробуем обрабатывать нажатие левой кнопки мышки и не уничтожать символ пароля, а устанавливать его. В этом случае, при любом щелчке пользователя, весь текст будет замещаться установленным символом. В листинге 3.8 показано, как будет выглядеть функция SysMsgProc.
Листинг 3.8. Ловушка сообщений, в которой любой текст замещается символом "d" |
int code, // hook code WPARAM wParam, // removal flag LPARAM lParam // address of structure with message ) { //Передать сообщение другим ловушкам в системе CallNextHookEx(SysHook, code, wParam, lParam);
//Проверяю сообщение if (code == HC_ACTION) { //Получаю идентификатор окна сгенерировавшего сообщение Wnd=((tagMSG*)lParam)-hwnd;
//Проверяю тип сообщения. //Если была нажата левая кнопка мыши if (((tagMSG*)lParam)-message == WM_LBUTTONDOWN) { SendMessage(Wnd, EM_SETPASSWORDCHAR, 100, 0); InvalidateRect(Wnd, 0, true); } }
return 0; }
Здесь проверяется нажатие левой кнопки мышки, и функция SendMessage отправляет в качестве третьего параметра число 100, что соответствует символу "d". Можно указать код любого другого символа. Результат — в каком поле пользователь не щелкнет мышкой, весь текст заместится указанным символом. На 3.15 показано окно свойств документа программы MS Word, в котором вся информация отображается символом "d".
3.15. Превращение свойств документа
Примечание |
Исходный код примера , описанного в этом разделе , вы можете найти на компакт - диске в каталоге \Demo\Chapter3\SetPassDLL. |
Переключение экранов
Помнится, когда появилась первая версия программы Dashboard (она была еще под Windows 3.1), меня очень сильно заинтересовала возможность переключения экранов, и я долго искал готовую WinAPI-функцию, которой достаточно указать, какой экран надо показать, и все готово. Но это оказалось не так.
Немного позже я узнал, что эта возможность была слизана с ОС Linux, где виртуальные консоли (экраны) реализованы на уровне ядра. Я некоторое время помучился, но написал собственную маленькую утилиту для переключения экранов под Windows 9x. Сейчас я воспользуюсь этим нехитрым приемом для написания небольшой программы-шутки.
Как работает переключение экранов? Сразу открою вам секрет, никакого переключения реально не происходит. Просто все видимые окна убираются с Рабочего стола за его пределы так, чтобы вы их не видели. После этого перед пользователем остается чистый Рабочий стол. Когда нужно вернуться к старому экрану, то все возвращается обратно. Как видите, все гениальное — просто.
При переключении окна мгновенно перемещаются за границы видимости. Мы же будем перемещать все плавно, чтобы видеть, как все окна двигаются за левый край экрана. Тем самым будет создаваться эффект, будто окна убегают от нашего взора. Программа будет невидима, поэтому закрыть ее можно только снятием задачи. Самое интересное — наблюдать за этим процессом, потому что если вы не успеете снять задачу за 10 секунд, то окно Диспетчера задач тоже убежит, и придется начинать все заново.
Только вот перемещать окна надо не теми функциями, которые нам уже знакомы. Простые функции установки позиции тут не подойдут, потому что после изменения расположения каждого окна оно перерисовывается и отнимает много процессорного времени. Если у вас открыто 20 программ, то с помощью функции SetWindowPos перемещение будет слишком медленным и заметным.
Для того чтобы лже-переключения происходили быстро, в Windows есть несколько специальных функций, которые перемещают все указанные окна сразу. Рассмотрим пример использования этих функций.
Создайте новый проект Win32 Project и перейдите в функцию _tWinMain. Воспользуйтесь листингом 3.2 и до цикла обработки сообщений напишите необходимый для перемещения окон код.
Листинг 3.2. Код перемещения окон |
//Бесконечный цикл while (TRUE) { int windowCount; int index; HWND winlist[10000]; HWND w; RECT WRct;
for (int i=0; iGetSystemMetrics(SM_CXSCREEN); i++) { //Считаем окна windowCount=0; w=GetWindow(GetDesktopWindow(),GW_CHILD); while (w!=0) { if (IsWindowVisible(w)) { winlist[windowCount]=w; windowCount++; } w=GetWindow(w,GW_HWNDNEXT);//Искать следующее окно } // Начало сдвига HDWP MWStruct=BeginDeferWindowPos(windowCount);//Начинаем сдвиг
// Определяем окна, которые надо сдвигать for (int index=0; indexwindowCount; index++) { GetWindowRect(winlist[index], WRct); MWStruct=DeferWindowPos(MWStruct, winlist[index], HWND_BOTTOM, WRct.left-10, WRct.top, WRct.right-WRct.left, WRct.bottom-WRct.top, SWP_NOACTIVATE || SWP_NOZORDER); } // Конец сдвига EndDeferWindowPos(MWStruct);//Конец сдвига }
WaitForSingleObject(h,2000); //Задержка в 2000 миллисекунд }
В самом начале создается пустое событие, которое в дальнейшем будет использоваться для задержки.
После этого запускается бесконечный цикл с помощью вызова while (true). Внутри цикла код делится на три маленькие части: сбор указателей на окна, сдвиг окон и задержка в 10 секунд. С задержкой мы уже сталкивались не один раз, и она вам уже должна быть знакома.
Сбор активных окон происходит следующим образом:
// Считаем окна w=GetWindow(GetDesktopWindow(), GW_CHILD); while (w!=0) { if (IsWindowVisible(w)) { winlist[windowCount]=w; windowCount++; }
w=GetWindow(w, GW_HWNDNEXT); // Искать следующее окно }
В первой строчке получаем указатель первого окна на Рабочем столе и записываем его в переменную w. Потом начинается цикл, который будет выполняться, пока полученный указатель не станет равным нулю, т.е. пока не переберем все окна.
В этом цикле, прежде чем запомнить указатель, происходит проверка видимости окна с помощью функции IsWindowVisible с параметром w. Если окно невидимо или свернуто (функция возвращает FALSE), то нет смысла его перемещать, в противном случае — указатель сохраняется в массиве winlist и увеличивается счетчик windowCount.
Итак, для поиска видимых окон используется функция GetWindow, которая может искать все окна, включая главные и подчиненные. Идентификатор найденного окна сохраняется в переменной w.
В данном случае для хранения указателей на окна используется массив заранее определенной длины (HWND winlist[10000]). В качестве длины я взял 10 000 элементов, и этого достаточно для хранения всех запущенных программ, потому что даже больше 100 окон запускать никто не будет.
В идеальном случае надо использовать динамические массивы (массив с динамически изменяемым количеством элементов), но я не стал применять их, чтобы не усложнять пример. Наша задача — посмотреть интересные алгоритмы, а вы потом можете сами доработать их до универсального вида.
После выполнения этого кода в массиве winlist будут храниться указатели всех запущенных и видимых программ, а в переменной windowCount — количество указателей в массиве.
А теперь о самом сдвиге. Он начинается с вызова API-функции BeginDeferWindowPos. Эта функция выделяет память для нового окна рабочего стола, куда мы будем сдвигать все видимые окна. В качестве параметра нужно указать, сколько окон мы будем двигать.
Для сдвига окна в подготовленную область памяти используется функция DeferWindowPos. В данный момент не происходит никаких реальных перемещений, а изменяется только информация о позиции и размерах. У этой функции следующие параметры:
результат выполнения функции BeginDeferWindowPos;
указатель на окно, которое надо переместить, — очередной элемент из массива;
номер по порядку (после какого окна должно быть помещено указанное);
следующие четыре параметра указывают координаты левой верхней позиции, ширину и высоту окна — получены с помощью функции GetWindowRect, и левая позиция для последующего сдвига уменьшена на 10;
флаги — указываем, что не надо активировать окно и упорядочивать.
После перемещения всех окон вызывается API-функция EndDeferWindowPos. Вот тут окна реально перескакивают в новое место. Это происходит практически моментально. Если бы вы использовали простую API-функцию SetwindowPos для установки позиции каждого окна в отдельности, то отрисовка происходила бы намного медленнее.
Хороший стиль программирования подразумевает, что все переменные, требующие значительной памяти (например, объекты), должны инициализироваться и уничтожаться. Во время инициализации память выделяется, а во время уничтожения — освобождается. Если не освобождать запрошенные ресурсы, то через какое-то время компьютер начнет очень медленно работать или может потребовать перезагрузку.
В примере я создавал объект, но нигде его не уничтожал, потому что программа выполняется бесконечно, и ее работа может прерваться только по двум причинам:
Выключили компьютер. В этом случае, даже если мы будем освобождать память, то она никому не понадобится.
Сняли процесс. Если кто-то додумается до этого, то программа закончит выполнение аварийно, а память все равно не освободится, даже если вставлен соответствующий код.
Получается, что освобождать объект бесполезно. Но все же я не советую вам пренебрегать такими вещами и в любом случае выполнять уничтожение объектов. Лишняя строчка кода никому не помешает, даже если вы думаете, что она никогда не выполнится. Зато это приучит вас всегда писать правильный код.
Попробуйте запустить программу, и все окна моментально улетят влево. Попытайтесь вызвать меню (щелкнуть правой кнопкой на Рабочем столе), и оно тоже улетит влево максимум через 2 секунды. Перемещаться будут любые запущенные программы.
Мне самому так понравился пример, что я целых полчаса играл с окнами. Они так интересно исчезают, что я не мог оторваться от этого глупого занятия. Но больше всего мне понравилось тренировать себя в скорости снятия приложения. Для этого я установил задержку в 5 секунд, потом — 4 и тренировал свои пальцы в быстром нажатии Ctrl+Alt+Del и поиске приложения, которое надо снять. Сложность в том, что окно с процессами тоже улетает, и если не успеть снять задачу, то придется повторять попытку снова.
Таким вот способом реализовано большинство программ, переключающих экраны. Во всяком случае, других методов мне не известно, и готовых функций я тоже не нашел.
Примечание |
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter3\DesktopSwitch. |
Перемещение окна за любую область
С помощью программы, описанной в разд. 3.5, можно получить окно произвольной формы, но оно будет иметь один недостаток — его нельзя передвигать. Это потому, что у него нет обрамления и системного меню, с помощью которых и происходит перемещение окна по Рабочему столу. У этого окна есть только рабочая область, и это усложняет задачу.
Чтобы избавиться от этого недостатка, мы должны научить нашу программу двигать окно по щелчку мышки в любой его зоне. Есть два способа решения этой проблемы:
при нажатии кнопки в рабочей области можно обмануть систему и заставить ее поверить, что щелчок был произведен по заголовку окна. Тогда ОС сама будет двигать окно. Такое решение самое простое, и проблема решается одной строчкой кода, но в реальной работе это неудобно, поэтому я даже не буду его рассматривать;
самостоятельно перемещать окно. Программного кода будет намного больше, но зато он будет универсальным и гибким.
Для этого надо написать три обработчика событий:
Пользователь нажал кнопку мыши. Тогда необходимо сохранить текущую позицию курсора и запомнить в какой-нибудь переменной это событие. В нашем примере это будет глобальная переменная dragging типа bool. Помимо этого, нужно захватить мышку, чтобы все ее события посылались нашему окну, пока мы перемещаем его. Для этого служит функция SetCapture, которой надо передать в качестве параметра указатель на окно. Это необходимо для того, чтобы в случае выхода указателя за пределы рабочей области программа все равно получала сообщения о передвижении мышки.
Перемещение мышки. Если переменная dragging равна true, то пользователь нажал кнопку мыши и двигает окно. В этом случае надо подкорректировать положение окна в соответствии с новым положением курсора. Иначе это просто движение мышки поверх окна.
Пользователь отпустил кнопку мыши. В этот момент необходимо присвоить переменной dragging значение false и освободить курсор с помощью функции ReleaseCapture.
Воспользуйтесь примером из предыдущего раздела. Откройте его и найдите функцию WndProc, сгенерированную мастером при создании проекта. Добавьте в нее код из листинга 3.5, выделенный комментариями. В раздел глобальных переменных добавьте следующие две переменные:
bool dragging=false;
POINT MousePnt;
Листинг 3.5. Код перетаскивания мышки |
RECT wndrect; POINT point;
switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, ps); // TODO: Add any drawing code here... hdcBits=::CreateCompatibleDC(hdc); SelectObject(hdcBits, maskBitmap); BitBlt(hdc, 0, 0, 200, 200, hdcBits, 0, 0, SRCCOPY); DeleteDC(hdcBits); EndPaint(hWnd, ps); break; case WM_DESTROY: PostQuitMessage(0); break;
/////////////////////////////////////////// // Начало кода, который надо добавить ///////////////////////////////////////////
// Следующий код обрабатывает событие, // когда нажата левая кнопка мыши case WM_LBUTTONDOWN: GetCursorPos(MousePnt); dragging = true; SetCapture(hWnd); break; // Следующий код обрабатывает событие, // когда курсор мышки движется по экрану case WM_MOUSEMOVE: if (dragging) // Если нажата кнопка, то... { // Получить текущую позицию курсора GetCursorPos(point); // Получить текущие размеры окна GetWindowRect(hWnd, wndrect);
// Откорректировать положение окна wndrect.left = wndrect.left+(point.x - MousePnt.x); wndrect.top = wndrect.top +(point.y - MousePnt.y);
// Установить новые размеры окна SetWindowPos(hWnd, NULL, wndrect.left, wndrect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
// Запоминаем текущую позицию курсора MousePnt=point; } break; // Следующий код обрабатывает событие, // когда левая кнопка мыши отпущена case WM_LBUTTONUP: if (dragging) { dragging=false; ReleaseCapture(); } /////////////////////////////////////////// // Конец кода, который надо добавить /////////////////////////////////////////// default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
Все функции, используемые в примере, вам уже должны быть знакомы, но программа получилась большая, и я написал подробные комментарии, чтобы вам легче было в ней разобраться.
Примечание |
Исходный код примера , описанного в этом разделе , вы можете найти на компакт - диске в каталоге \Demo\Chapter3\MaskWindow2. |
Подсматриваем пароли
В большинстве программ вводимый пароль отображается звездочками. Это делается для того, чтобы никто из окружающих не увидел, что вы набираете при входе в приватную область своего компьютера. А что делать, если вы ввели пароль в программу и забыли?
Как увидеть пароль, спрятанный под звездочками? Для этого есть много разных специальных программ. Но вы же не думаете, что я буду вас отправлять к ним в своей книге? Конечно же, сейчас мы разберем, как самостоятельно написать подобную программу.
Программа будет состоять из двух частей. Первый файл — запускаемый — будет загружать другой файл — динамическую библиотеку — в память. Эта библиотека будет регистрироваться в системе в качестве обработчика системных сообщений, который будет ожидать, когда пользователь щелкнет в интересующем его окне правой кнопкой мышки. Как только такое событие произойдет, мы сразу должны будем получить текст этого окна и конвертировать его из звездочек в обычный текст. На первый взгляд все выглядит достаточно сложным, но реально вы сможете реализовать все за десять минут.
Программа расшифровки пароля
Теперь напишем программу, которая будет загружать библиотеку и запускать ловушку. Для этого создайте новый проект Win32 Project типа Windows Application. В нем надо только подкорректировать функцию _tWinMain, как в листинге 3.7.
Листинг 3.7. Загрузка DLL-библиотеки и запуск ловушки |
// Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_OPENPASSTEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);
// Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_OPENPASSTEST);
////////////////////////////////////// // Следующий код необходимо добавить LONG lResult; HINSTANCE hModule;
// Создаем новый указатель на функцию typedef void (RunStopHookProc)(bool, HINSTANCE);
RunStopHookProc* RunStopHook = 0;
// Load the DLL file (Чтение DLL-библиотеки) hModule = ::LoadLibrary("OpenPassDLL.dll");
// Получить адрес функции в библиотеке RunStopHook = (RunStopHookProc*)::GetProcAddress( (HMODULE) hModule, "RunStopHook");
// Выполнить функцию (*RunStopHook)(true, hInstance);
// Main message loop: while (GetMessage(msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } }
(*RunStopHook)(false, hInstance); FreeLibrary(hModule);
return (int) msg.wParam; }
Так как функция описана в динамической библиотеке, а использоваться будет в другой программе, то необходимо указать тип вызываемой функции. Если что-то указать неправильно, то вызов будет невозможным. Описание функции делается так:
typedef void (RunStopHookProc)(bool, HINSTANCE);
Таким образом, описывается тип функции RunStopHookProc, которая ничего не возвращает, но принимает в качестве параметров два значения типа bool и hInstance. В следующей строке объявляется переменная RunStopHook описанного типа, и ей присваивается значение 0.
Теперь необходимо загрузить динамическую библиотеку. Для этого есть функция LoadLibrary, которой нужно только передать имя файла или полный путь. В примере указано только имя файла, поэтому библиотека должна находиться в одном каталоге с запускаемым файлом, либо ее нужно разместить в каталоге, доступном для Windows.
Загрузив библиотеку, надо определить адрес, по которому расположена функция RunStopHook, чтобы ее можно было использовать. Для этого существует функция GetProcAddress, которой нужно передать указатель на искомую библиотеку и название функции. Результат сохраняется в переменной RunStopHook.
Вот теперь все готово, и можно запускать функцию-ловушку. Это делается не совсем обычным способом:
(*RunStopHook)(true, hInstance);
Дальше запускается цикл обработки сообщений, в котором ничего изменять не надо. Но по выходе из программы следует остановить ловушку и выгрузить динамическую библиотеку. Это делается следующим образом:
(*RunStopHook)(false, hInstance);
FreeLibrary(hModule);
Примечание |
Исходный код примера , описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter3\OpenPassTest. |
На 3.14 можно увидеть пример работы этой программы. Здесь изображено стандартное окно Windows 2000 для смены пароля. В первом поле вы видите пароль, который расшифрован нашей программой. Во втором поле, в котором нужно ввести подтверждение пароля, остались звездочки.
3.14. Пример работы программы OpenPassTest
Работа с чужыми окнами
Я регулярно получаю письма с вопросами типа: "Как уничтожить чужое окно или изменить что-то в нем?" В принципе, эта задача легко решается с помощью уже знакомой нам функции FindWindow. Но если необходимо изменить множество окон (или даже все), то нужно использовать другой метод поиска, который мы сейчас рассмотрим. Для начала напишем программу, которая будет искать все окна на Рабочем столе и изменять их заголовки.
3.1. Результат работы программы I SeeYou
На 3.1 показан вид нескольких окон после запуска программы, которую нам сейчас предстоит написать. Как видите, все заголовки изменились на "I See You".
Создайте в Visual C++ новый проект Win32 Project и в нем какой-нибудь пункт меню, при выборе которого будет запускаться программа, реализующая нашу задачу.
В функции WndProc добавьте следующий код обработки пункта меню:
case ID_MYCOMMANDS_ISEEYOU: while (TRUE) { EnumWindows(EnumWindowsWnd, 0); }
В приведенном коде ID_MYCOMMANDS_ISEEYOU — это идентификатор пункта меню. Цикл while будет выполняться бесконечно (TRUE никогда не станет равным FALSE). Внутри цикла вызывается функция EnumWindows. Это WinAPI -функция, которая используется для перечисления всех открытых окон.
В качестве первого параметра ей нужно передать адрес другой функции, которая будет вызываться каждый раз, когда найдено какое-нибудь запущенное окно. В качестве второго параметра указывается число, которое будет передаваться в функцию обратного вызова.
В качестве функции обратного вызова будет использоваться функция EnumWindowsWnd. Так что, каждый раз, когда EnumWindows найдет окно, будет выполняться код, написанный в EnumWindowsWnd. Этот код выглядит следующим образом:
BOOL CALLBACK EnumWindowsWnd( HWND hwnd, // handle to parent window // (Указатель на главное окно) LPARAM lParam // application-defined value // (значение, определенное приложением) ) { SendMessage(hwnd, WM_SETTEXT, 0, LPARAM(LPCTSTR("I See You"))); return TRUE; }
Количество параметров, их тип и тип возвращаемого значения должны быть именно такими:
идентификатор найденного окна типа HWND;
значение типа LPARAM, которое вы можете использовать в своих целях.
Если что-то изменить, то функция станет несовместимой с EnumWindows. В таких случаях, чтобы не ошибиться, я беру имя функции с параметрами прямо из файла помощи по WinAPI и вставляю в свой код. Лучше лишний раз проверить, чем потом долго искать опечатку или случайную ошибку. То же самое я советую делать и вам. В данном случае нужно открыть файл помощи в разделе Enumwindows и перейти по ссылке, указывающей на формат функции обратного вызова.
Итак, у нас есть идентификатор найденного окна. Такой параметр мы уже использовали много раз, когда прятали или перемещали окно, теперь научимся изменять его заголовок. Для этого используем уже знакомую функцию SendMessage, которая посылает сообщения Windows. Вот ее параметры:
идентификатор окна, которому надо отослать сообщение, — передан в качестве параметра функции-ловушки EnumWindowsWnd, и он равен идентификатору найденного окна;
тип сообщения — WM_SETTEXT, заставляет окно сменить текст заголовка;
параметр для данного сообщения должен быть 0;
новое имя окна.
Чтобы программа продолжила поиск следующего окна, ей надо вернуть значение TRUE.
Примечание |
Исходный код этого примера вы можете найти на компакт-диске в каталоге \Demo\Chapter3\ISeeYou. |
BOOL CALLBACK EnumWindowsWnd( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ) { SendMessage(hwnd, WM_SETTEXT, 0,LPARAM(LPCTSTR("I See You"))); EnumChildWindows(hwnd,EnumChildWnd,0); return TRUE; }
Здесь после отправки сообщения вызывается функция EnumChildWindows, которая определяет все окна, принадлежащие главному окну. У нее три параметра:
идентификатор окна, дочерние элементы которого нужно искать, — указываем окно, которое уже нашли;
адрес функции обратного вызова, которая будет запускаться каждый раз, когда найдено дочернее окно;
просто число, которое может быть передано в функцию обратного вызова.
Как вы можете заметить, работа функции EnumChildWnd похожа на EnumWindowsWnd, только если вторая ищет окна во всей системе, то первая — внутри указанного окна. Образец такого окна можно увидеть на 3.2.
В этой функции так же изменяется заголовок найденного окна, а чтобы поиск продолжился дальше, формируется выходной параметр (присваивается ему значение TRUE).
В принципе, программу можно считать законченной, но у нее есть один недостаток, о котором нельзя умолчать. Допустим, что программа нашла окно и начала перечисление в нем дочерних окон, а в этот момент окно закрывают. Программа пытается послать сообщение найденному окну об изменении текста, а его уже не существует, и происходит ошибка выполнения. Чтобы этого избежать, в самом начале функций обратного вызова нужно поставить проверку на правильность полученного идентификатора окна:
if (h==0) return TRUE;
З.2. Результат работы программы lSeeYou2
Вот теперь приложение можно считать завершенным и абсолютно рабочим.
Помните, что лишних проверок не бывает. Если хотите, чтобы ваш код был надежным, то нужно проверять все, что может вызвать проблемы. В данной книге я иногда буду пренебрегать этим правилом, чтобы не усложнять и не запутывать программу, но постараюсь указывать на те участки кода, которые нуждаются в пристальном внимании.
Примечание |
Исходный код этого примера вы можете найти на компакт-диске в каталоге \Demo\Chapter3\ISeeYou2. |
Этот побочный эффект может оказаться полезным для невидимых окон. А если код отображения главного окна программы просто удален, тогда обработчик сообщений окна останется "без работы", его тоже можно будет убрать.
Теперь добавим еще один простой, но очень интересный эффект. Будем перебирать все окна и сворачивать их. Тогда функция EnumWindowsWnd (вызывается, когда найдено очередное окно) будет выглядеть следующим образом:
BOOL CALLBACK EnumWindowsWnd( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ) { ShowWindow(hwnd, SW_MINIMIZE); return TRUE; }
Здесь в вызываемой функции ShowWindow в качестве второго параметра указывается флаг SW_MINIMIZE, который и заставляет найденное окно свернуться. Будьте осторожны при запуске программы. Функция FindWindow ищет все окна, в том числе и невидимые.
Примечание |
Исходный код этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter3\RandMinimize. |
Система
В этой главе будут рассматриваться разные системные утилиты. Сюда войдут примеры программ, способных следить за происходящим в системе. Это уже не просто программы-приколы, а работа с системой, хотя шуток в рассматриваемых задачах будет достаточно. Как я уже говорил, любой хакер — это профессионал, а значит, должен знать и уметь работать с внутренностями той ОС, в которой он находится.
При создании книги я подразумевал, что вы находитесь в Windows, программируете и работаете в ней. В данной главе я попробую научить вас лучше понимать эту систему. Я постараюсь не загружать вас теорией, а дать практический урок. Если вы уже читали мои труды, то знаете мой стиль. Я всегда говорю, что только практика ведет к познанию. Грош цена тем знаниям, которые не понимаешь, как применить на практике. Такие знания быстро забываются. Именно поэтому все главы этой книги наполнены практическими примерами, и эта — не исключение.
Я покажу несколько интересных примеров, и мы подробно разберем их. Таким образом, мы рассмотрим некоторые особенности работы с ОС Windows, и вы поймете, как применять эти особенности на практике. Надеюсь, что это вам поможет в работе.
В этой главе я постепенно буду усложнять примеры и покажу много интересного и полезного.
Управление ярлыками на Рабочем столе
На Рабочем столе ярлыки расположены аналогично строкам в элементе управления List View , поэтому ими очень легко управлять. Для этого нужно найти окно с классом ProgMan . Затем внутри этого окна необходимо получить указатель на элемент управления, содержащий ярлыки.
Все вышесказанное в виде кода выглядит следующим образом:
HWND DesktopHandle = FindWindow("ProgMan", 0);
DesktopHandle = GetWindow(DesktopHandle, GW_CHILD);
DesktopHandle = GetWindow(DesktopHandle, GW_CHILD);
Здесь ищется окно с заголовком ProgMan. Хотя вы такое окно не видите, оно существует еще со времен Windows третей версии (может и раньше) и называется Program Manager. Далее, с помощью функции GetWindow, определяется дочернее окно. После этого находим следующее дочернее окно. Вот теперь мы получили указатель на системный объект класса SysListView32. Этот элемент как раз и содержит все иконки Рабочего стола.
Мы можем управлять ярлыками, посылая сообщения с помощью функции SendMessage. Например, если выполнить следующую строку кода, то все ярлыки будут упорядочены по левому краю экрана:
SendMessage(DesktopHandle, LVM_ARRANGE, LVA_ALIGNLEFT, 0);
Рассмотрим каждый из параметров функции SendMessage :
DesktopHandle — окно, которому надо послать сообщение;
тип сообщения — LVM_ARRANGE, указывает на необходимость отсортировать иконки;
первый параметр для сообщения — LVA_ALIGNLEFT, упорядочивает иконки по левому краю;
второй параметр для сообщения — оставляем нулевым.
Если параметр LVA_ALIGNLEFT заменить на LVA_ALIGNTOP, то иконки будут выровнены по верхнему краю окна.
Следующая строка кода удаляет все элементы с Рабочего стола:
SendMessage(DesktopHandle, LVM_DELETEALLITEMS , 0, 0);
Код похож на тот, что мы уже использовали, только здесь посылается команда LVM_DELETEALLITEMS, которая заставляет удалить все элементы. Попробуйте выполнить эту команду, и весь Рабочий стол очистится. Только удаление происходит не окончательно, и после первой же перезагрузки компьютера все вернется на свои места. Но если в системе запустить невидимую программу, которая будет через определенные промежутки времени очищать ярлыки, то эффект будет впечатляющим.
А теперь самое интересное — перемещение ярлыков по экрану. Для этого можно использовать следующий код:
HWND DesktopHandle = FindWindow("ProgMan", 0); DesktopHandle = GetWindow(DesktopHandle, GW_CHILD); DesktopHandle = GetWindow(DesktopHandle, GW_CHILD); for (int i=0; i200; i++) SendMessage(DesktopHandle, LVM_SETITEMPOSITION, 0, MAKELPARAM(10, i));
Как и в предыдущем примере, ищется элемент управления, содержащий ярлыки. Потом запускается цикл от 0 до 200, в котором посылается сообщение функцией SendMessage со следующими параметрами:
окно, которое должно получить сообщение, — в данном случае это элемент управления с иконками;
сообщение, которое нужно послать, — LVM_SETITEMPOSITION (изменяет, позицию иконки);
индекс иконки, которую надо переместить;
новая позиция иконки. Этот параметр состоит из двух слов: х и у позиции элемента. Чтобы правильно разместить числа, мы воспользовались функцией MAKELPARAM.
Таким образом, можно как угодно шутить над Рабочим столом Windows. Единственный недостаток описанного примера проявляется в Windows XP, где иконки двигаются по экрану не плавно, а скачками. Вот такая уж специфика этой версии Windows. Зато в других вариантах — красота полнейшая, и шутка получается очень интересная.
Примечание |
Исходный код этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter3\ArrangeIcons. |