Программирование на C++ глазами хакера

Летающий Пуск


Вспоминаю, как я первый раз увидел Windows 95. Мне так понравилась кнопка Пуск, что я полюбил ее до глубины Выключить компьютер. Вскоре в нашем институте обновили парк машин, и на них тоже поставили Windows 95. Мне так захотелось подшутить над своими однокурсниками, что я решил написать программку, которая подбрасывала бы кнопку Пуск.

Сказано — сделано, написал (на Delphi ) и запустил на всех машинах. С каждым взлетом кнопки Пуск ламеры испуганно взлетали вместе с ней. А через некоторое время я увидел и в Интернете подобный прикол.

Сейчас я повторю свой старый подвиг и покажу вам, как самому написать такую программу с использованием Visual C++. Так что усаживайтесь поудобнее, наша кнопка Пуск взлетает на высоту 100 пикселов!

В этом примере мы пойдем на небольшую хитрость и будем подбрасывать не саму кнопку Пуск, а окно, в котором будет нарисовано изображение кнопки. Чуть позже мы рассмотрим пример реального доступа к системной кнопке, а пока ограничимся таким трюком, потому что это даже интересней.

Но прежде чем начать, нужно подготовить картинку с изображением кнопки Пуск. Для этого вы можете нарисовать ее своими руками в любом графическом редакторе. Ну, а если вы IBM-совместимый человек, то можете нажать клавишу PrintScrn (или PrintScreen), чтобы запомнить изображение экрана в буфере обмена, а потом выполнить вставку содержимого буфера в любом графическом редакторе. Далее простыми манипуляциями вырезать изображение кнопки и сохранить его в отдельном файле.

У меня получилась картинка размером 50x20, и вы найдете ее на компакт-диске в каталоге Demo/Chapter2/Start Button/Start.bmp. Можете воспользоваться этим файлом.

Создайте новый проект в Visual C++ типа Win32 Project с именем Start Button и добавьте в него нашу картинку. Для этого откройте ресурсы, дважды щелкнув по файлу Start Button.rc. Перед вами откроется окно с деревом ресурсов ( 2.1).

Рис. 2.1. Окно просмотра ресурсов

Щелкните в этом окне правой кнопкой мыши и в появившемся выпадающем меню выберите пункт Add resource . Вы должны увидеть окно добавления ресурсов с возможностью выбора типа создаваемого ресурса ( 2.2). В этом окне выберите пункт Bitmap и нажмите кнопку New.




Рис. 2.2. Окно выбора типа создаваемого ресурса



В этом разделе будет создан новый ресурс для хранения изображения. Под окном просмотра ресурсов вы можете видеть окно свойств изображения ( 2.3). Здесь нужно первым делом изменить свойство Colors (Количество цветов), установив значение Color или True Color. Ширину и высоту (свойства Width и Height) нужно указать в соответствии с вашей картинкой.

Откройте изображение кнопки Пуск в любом графическом редакторе (например, Paint) и скопируйте его в буфер обмена (чаще всего для этого нужно выделить изображение и выбрать меню Edit/Copy). Вернитесь в редактор Visual C++ и выполните команду Edit/Paste. Вы должны увидеть нечто похожее на 2.4.

Теперь переходим непосредственно к программированию. На этом примере мы рассмотрим некоторые приемы, которые будем использовать в дальнейшем достаточно часто.

Откройте файл Start Button.cpp. Для этого найдите его в окне Solution Explorer в разделе Source Files и дважды щелкните на строке с именем. В самом начале файла найдите раздел глобальных переменных, который начинается с комментария Global Variables: После этого комментария добавим две переменные:

// Global Variables:

HWND hWnd;

HBITMAP startBitmap;



Рис. 2.З. Окно свойств изображения



2.4. Изображение кнопки Пуск в редакторе ресурсов

Первая переменная — hWnd — имеет тип HWND, который используется для хранения указателей на окна. В ней мы и сохраним указатель на созданное в примере окно, чтобы в любой момент можно было получить к нему доступ. Вторая переменная — startBitmap — имеет тип HBITMAP, который используется для хранения картинок, и мы поместим сюда наше изображение кнопки Пуск.

Теперь переходим в функцию _tWinMain. В ней, после загрузки из ресурсов текста окна и имени класса окна, добавим следующую строчку кода:

startBitmap = (HBITMAP)::LoadImage(hInstance, MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);

Здесь мы переменной startBitmap присваиваем загруженную из ресурсов картинку. Для этого вызывается функция LoadImage, которой (в скобках) нужно передать следующие параметры:



экземпляр приложения — переменная hInstance, которую мы получили в качестве первого параметра нашей функции _tWinMain, именно она содержит необходимое значение экземпляра;

имя ресурса — наша картинка сохранена под именем IDB_BITMAP1;

тип изображения — в нашем случае растровая картинка — IMAGE_BITMAP;

размеры (следующие два параметра) — мы указали значение 0, чтобы использовать текущие размеры картинки;

флаги — здесь указано LR_DEFAULTCOLOR, что означает использование цветов по умолчанию.

Больше в этой функции мы пока ничего изменять не будем. Чуть позже мы еще сюда вернемся и добавим пару строчек, а сейчас переходим в функцию Initlnstance. Она будет выглядеть как в листинге 2.1.

Листинг 2.1. Обновленная функция InitInstance
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // Store instance handle in our global variable

hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

if (!hWnd) { return FALSE; } // Следующие строки добавлены нами int Style; Style = GetWindowLong(hWnd, GWL_STYLE); Style=Style || WS_CAPTION; Style=Style || WS_SYSMENU; SetWindowLong(hWnd, GWL_STYLE, Style);

return TRUE; }

Код, добавленный нами, начинается с объявления переменной Style, которая будет иметь тип int (целое число). В следующей строке этой переменной присваивается результат выполнения функции GetWindowLong. Она возвращает настройки окна и в скобках нужно передать два значения:

окно, параметры которого необходимо узнать — мы указываем только что созданное нами окно;

тип параметров — нас будет интересовать стиль окна, поэтому указана константа GWL_STYLE.

Зачем нам нужен стиль? Просто окно по умолчанию имеет заголовок, кнопки максимизации и минимизации, а нам все это не нужно. Для этого из полученного стиля в следующих двух строках удаляется заголовок окна и системное меню, которое содержит кнопки.

Теперь выполняем функцию SetWindowLong, которая записывает значения обратно в настройки окна. Если сейчас запустить программу, то вы увидите только клиентскую часть — серый квадрат без заголовка, кнопок и обрамления.



Переходим в функцию WndProc, где у нас обрабатываются все события. Нас будет интересовать рисование, поэтому добавим следующий обработчик события:

case WM_PAINT: hdc = BeginPaint(hWnd, ps); // TODO: Add any drawing code here... Rectangle(hdc, 1,1,10,10); hdcBits=::CreateCompatibleDC(hdc); SelectObject(hdcBits,startBitmap); BitBlt(hdc, 0, 0, 50, 20, hdcBits, 0, 0, SRCCOPY); DeleteDC(hdcBits); EndPaint(hWnd, ps); break;

Полный вариант функции WndProc вы можете увидеть в листинге 2.2.

Листинг 2.2. Функция WndProc с обработчиком для рисования
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; HDC hdcBits;

switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { 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... Rectangle(hdc, 1,1,10,10); hdcBits=::CreateCompatibleDC(hdc); SelectObject(hdcBits,startBitmap); BitBlt(hdc, 0, 0, 50, 20, hdcBits, 0, 0, SRCCOPY); DeleteDC(hdcBits); EndPaint(hWnd, ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }

Чтобы начать рисование, надо знать, где мы будем это делать. У каждого окна есть контекст, в котором можно рисовать средствами Windows. Чтобы получить его для текущего окна, нужно вызвать функцию BeginPaint. Эта функция как раз и вернет нам указатель на контекст окна, указанного в качестве первого параметра.

Но чтобы отобразить изображение нашей кнопки Пуск, надо еще подготовить картинку. В WinAPI нет готовой функции для рисования растрового изображения, но есть возможность выбрать изображение в контекст и возможность копирования между контекстами. Для этого сначала надо создать контекст рисования, совместимый с тем, что использует окно, чтобы можно было без проблем производить копирование. Воспользуемся функцией CreateCompatibleDC, которой нужно передать контекст окна, а она нам вернет новый контекст, совместимый с указанным.



Следующим шагом мы должны выбрать в новый контекст нашу картинку. Для этого можно вызвать функцию SelectObject, у которой два параметра:

контекст, в который нужно выбрать объект, — указываем созданный нами контекст, на основе оконного;

объект, который надо выбрать, — указываем картинку.

Вот теперь можно производить копирование с помощью функции BitBlt. У нее в скобках нужно указать следующие параметры:

контекст рисования, в который надо копировать (приемник), — указываем контекст окна;

следующие четыре параметра являются левой верхней координатой, шириной и высотой прямоугольника, в который надо скопировать изображение (целые числа). В данном случае левая и верхняя позиции будут равны нулю, чтобы картинка располагалась в левом верхнем углу окна. Ширина и высота равны размеру картинки (50x20);

источник копирования — указываем контекст hdcBits, в котором находится наша картинка;

следующие два параметра задают левую верхнюю координату прямоугольника в контексте-источнике (именно от этой точки будет взято изображение для копирования) — указываем нули, т. к. нас интересует вся кнопка;

последний параметр указывает на тип копирования — используем флаг SRC_COPY, т. к. будем создавать копию источника в приемнике.

После рисования нам уже не нужен контекст, который мы создали для картинки, и хорошим тоном было бы удалить его. Для этого вызываем функцию DeleteDC и в качестве параметра указываем наш контекст рисования.

Завершаем рисование вызовом метода EndPaint. Таким образом мы ставим точку в начатое функцией BeginPaint рисование.

Теперь в нашем окне в левом верхнем углу будет рисоваться изображение кнопки Пуск. Остается сделать самую малость — уменьшить размер окна до размеров изображения, чтобы пользователь видел только картинку, и заставить окно двигаться. Для этого мы должны написать функцию DrawStartButton (листинг 2.3), Желательно, до функци _tWinMain.

Листинг 2.3. Функция , заставляющая окно двигаться
void DrawStartButton() { int i; HANDLE h; int toppos=GetSystemMetrics(SM_CYSCREEN)-23;



//Отображаем окно ShowWindow(hWnd, SW_SHOW); //Установить верхнюю позицию окна в левый нижний угол экрана. SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20, SWP_SHOWWINDOW); UpdateWindow(hWnd); //Создаем пустой указатель h, который будем использовать для задержки. h=CreateEvent(0, true, false, "et");

// Сейчас будем поднимать кнопку // От 1 до 50 выполнять действия для изменения положения окна for (i=0; i0; i--) { toppos=toppos+4; SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20, SWP_SHOWWINDOW); WaitForSingleObject(h,15);//Задержка в 5 миллисекунд } }

Чтобы правильно расположить окно с нашей кнопкой на экране компьютера, мы должны знать его разрешение. Для этого выполняется следующая строка кода:

int toppos = GetSystemMetrics(SM_CYSCREEN)-23;

Здесь вызывается функция GetSystemMetrics, которая возвращает значение определенного системного параметра. В скобках указывается параметр, который нас интересует (в данном случае SM_CYSCREEN, высота экрана). Из результата вычитаем число 23 (высота картинки + еще 3 пиксела) и сохраняем результат в переменной toppos.

Таким образом, мы вычислили верхнюю позицию окна с изображением кнопки, и можем его туда переместить. Еще необходимо, чтобы наше окно всегда было поверх остальных. Обе эти операции можно сделать, вызвав только одну функцию SetWindowPos. У нее 7 параметров:

окно, которое надо переместить, — указываем наше окошко;

место размещения (после какого окна нужно расположить указанное) — устанавливаем флаг HWND_TOPMOST (поверх всех);

следующие четыре параметра определяют прямоугольник, в котором должно располагаться окно. Левую позицию задаем равной 4. Верхнюю — равной переменной toppos. Ширина и высота окна должны определяться размерами картинки. Возможно, что после запуска программы вам придется подкорректировать левую верхнюю позицию в зависимости от подготовленной вами картинки;

последний параметр задает режим отображения окна — устанавливаем флаг SWP_SHOWWINDOW (просто отобразить).

После этого прорисовываем окно в новой позиции с помощью вызова функции UpdateWindow(hWnd). В скобках указано окно, которое надо отобразить.



Последний штрих — создание пустого события с помощью вызова функции СreateEvent. Это событие мы будем использовать чуть позже, и нас устраивает, что оно пустое.

Теперь наше окно расположено в нужном месте, и можно приступить к его анимации (движению по экрану). Для этого запускаем цикл от 0 до 50, внутри которого выполняются следующие действия:

for (i=0; i

Сначала уменьшается значение переменной toppos на четыре пиксела. Таким образом, окно будет ползти вверх по экрану. Потом перемещаем это окно в новую позицию.

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

Итак, наш цикл двигает кнопку вверх по экрану. После этого мы должны вернуть кнопку на место, для чего запускается еще один цикл, в котором тем же способом кнопка движется в обратном направлении.

Теперь у нас все готово, и мы должны вернуться в функцию _tWinMain и написать там вызов функции DrawStartButton. Я рекомендую сделать этот вызов перед циклом обработки сообщений и внутри него:

DrawStartButton();

// Main message loop: while (GetMessage(msg, NULL, 0, 0)) { DrawStartButton(); if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } }

Теперь при старте программы наша лже-кнопка будет взлетать и возвращаться на место поверх настоящей кнопки. Если вы попытаетесь навести на настоящую кнопку мышку, то она пройдет поверх окна лже-кнопки, и наша программа получит сообщение от мышки, а значит, выполнится цикл обработки событий, в котором функция DrawStartButton снова подбросит нашу кнопку на некоторую высоту.



Эффект получается красивым, и обязательно посмотрите на результат ( 2.5). Ничего разрушительного в этом нет, но вашим друзьям понравится.



2.5. Пример работы программы

Примечание
Исходный код и запускаемый файл этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter2\Start Button. Чтобы запустить программу, выберите меню Debug/Start.
Очень много шуток могут основываться на эффекте "подставки", когда мы рисуем или двигаем не сам объект, а совершенно другое окно с изображением нужного объекта. Таким образом можно даже просто вывести на экран диалоговое окно с сообщением "Форматировать диск С:", и пользователь может не на шутку испугаться. Ну, а если вывести десять сообщений с надписью "Virus Alert!!!", то даже профессионал может поверить.

Вспоминаются времена 90-х годов, когда даже вирусы были веселыми. Все их действия заключались в том, что они выводили какие-то веселые сообщения, играли музыку через PC Speaker или выводили на экран какую-то ASCII-графику. При этом самое страшное, что они делали, — копировались без спроса между компьютерами. Конечно же, даже эти вирусы нельзя считать хорошими, но они, по крайней мере, были с изюминкой. Нынешние вирусы не несут в себе вообще ничего пристойного и интересного.


Содержание раздела