14
POCZĄTKI
HAKIN9 7-8/2008
W
irtualny pulpit jest mechanizmem
dość powszechnie stosowanym w
wielu dystrybucjach systemu Linux.
Używając go mamy wrażenie, jakbyśmy
dysponowali kilkoma pulpitami. Dzięki temu
możemy podzielić aplikacje na grupy i w razie
potrzeby przełączać się między nimi. W ten
sposób łatwo możemy zarządzać oknami.
Zasada działania wirtualnych pulpitów jest dość
prosta.
Poszukujemy wszystkich widocznych
okien na pulpicie, zapamiętując ich położenie
oraz wymiary, a następnie ukrywamy je. W
ten sposób powstaje nowy wirtualny pulpit,
na którym możemy swobodnie pracować.
Gdy zechcemy powrócić do poprzedniego
pulpitu, chowamy bieżące okna, zapamiętując
ich wymiary i położenie, a przywracamy
widoczność okien wcześniej ukrytych. Na takiej
zasadzie przełączamy się między wirtualnymi
pulpitami.
Cała trudność polega na znalezieniu
sposobu zarządzania oknami pulpitu jako
jednym obiektem. Z pomocą przychodzi nam
WinApi.
Artykuł ten ma na celu zilustrowanie
mechanizmu wirtualnych pulpitów jako
metody polepszającej komfort pracy z
systemem Windows, a także pokazanie, jak
łatwo użyteczną technikę można wykorzystać
w sposób szkodliwy. Projekt zrealizujemy
pod platformą .NET. Do napisania kodu
MACIEJ PAKULSKI
Z ARTYKUŁU
DOWIESZ SIĘ
jak działają wirtualne pulpity,
jak zarządzać oknami w
Windows.
CO POWINIENEŚ
WIEDZIEĆ
znać podstawy projektowania
zorientowanego obiektowo.
wykorzystamy darmowe środowisko Visual C#
2008 Expres Edition.
Klasa
obsługująca wirtualny pulpit
Tworzymy nowy projekt i nadajemy mu
odpowiednią nazwę. Dodajemy klasę, która
będzie reprezentować wirtualny pulpit. Zaczniemy
od importu niezbędnych funkcji WinApi. Ilustruje
to kod z Listingu 1.
Aby móc skorzystać z
DllImport
,
musimy najpierw dodać przestrzeń nazw
System.Runtime.InteropServices
. Działanie
poszczególnych zaimportowanych funkcji jest
następujące:
•
GetDesktopWindow
– funkcję
wykorzystujemy do uzyskania uchwytu do
pulpitu,
•
GetTopWindow
– funkcja zwraca uchwyt do
okna będącego najwyżej w porządku osi z
(ang. z order). Jako parametr przekazujemy
uchwyt do okna będącego oknem
nadrzędnym (ang. parent window) w stosunku
do poszukiwanego okna,
•
GetWindow
– funkcja zwraca uchwyt
do okna, które jest w specjalnej relacji
z wybranym oknem. Pierwszy parametr
określa relację między oknami, drugi
parametr to uchwyt do okna, z którym
poszukiwane okno jest w relacji określonej
pierwszym parametrem,
Stopień trudności
Wirtualny
pulpit
Windows
Współczesne systemy operacyjne umożliwiają uruchomienie
wielu programów jednocześnie, co może powodować problem
organizacji okien na pulpicie. Rozwiązaniem tego problemu może
być użycie wirtualnych pulpitów, choć mechanizm ten może także
tworzyć podstawę do działania złośliwego oprogramowania.
15
WIRTUALNY PULPIT WINDOWS
HAKIN9
7-8/2008
•
IsWindowVisible
– funkcja
sprawdza, czy okno jest widoczne
(czy ma ustawiony styl okna WS_
VISIBLE),
•
FindWindowEx
– funkcja zwraca
uchwyt do okna. Pierwszy parametr
to uchwyt do okna będącego
oknem nadrzędnym w stosunku do
poszukiwanego okna, drugi parametr
określa uchwyt okna, od którego
rozpocznie się przeszukiwanie w
porządku osi z, trzeci parametr określa
nazwę klasy okna, a czwarty jest
nazwą okna,
•
BeginDeferWindowPos
– przydziela
pamięć dla struktury opisującej
położenie okien, których liczba jest
przekazywana jako parametr. Funkcja
zwraca uchwyt do struktury,
•
DeferWindowPos
– funkcja zmienia
atrybuty okna (takie, jak położenie,
rozmiar, widoczność), jako parametry
przyjmuje ona kolejno:
• uchwyt do struktury
opisującej okna uzyskiwany
przy pomocy wywołania
BeginDeferWindowPos
,
• uchwyt do okna, którego atrybuty
chcemy zmienić,
• uchwyt do okna, które poprzedza
konkretne okno w porządku osi z.
Możliwe jest także zastosowanie
stałej, która będzie określać
położenie okna względem innych,
• współrzędna x lewego górnego
rogu okna,
• współrzędna y lewego górnego
rogu okna,
• szerokość okna w pikselach,
• wysokość okna w pikselach,
• flagi.
Wywołanie
DeferWindowPos
powoduje
zaktualizowanie struktury będącej
pierwszym parametrem funkcji, dzięki
czemu zawiera ona informacje o oknie,
którego uchwyt przekazujemy jako drugi
parametr.
Funkcja zwraca uchwyt do
zaktualizowanej struktury.
•
EndDeferWindowPos
– wywołanie
funkcji powoduje jednoczesną zmianę
atrybutów okien, określonych przez
strukturę, której uchwyt przekazujemy
jako parametr.
Kolejnym krokiem będzie zdefiniowanie
stałych, których użyjemy przy wywołaniu
funkcji WinApi. Dodajemy do naszego
programu kod z Listingu 2. Stałe są
wykorzystywane w następujący sposób:
•
GW _ HWNDNEXT
– wywołując funkcję
GetWindow
z tym parametrem
uzyskujemy uchwyt do okna, które leży
poniżej wybranego okna, w porządku
osi z,
•
SWP _ NOZORDER
– stała
wykorzystywana jako ósmy parametr
funkcji
DeferWindowPos
, określa
brak zmiany kolejności okien
(parametr
hWndInsertAfter
jest
ignorowany),
•
SWP _ HIDEWINDOW
– ósmy parametr
funkcji
DeferWindowPos
, określa
ukrycie okna,
•
SWP _ SHOWWINDOW
– ósmy parametr
funkcji
DeferWindowPos
, określa
pokazanie okna,
•
SWP _ NOACTIVE
– ósmy parametr
funkcji
DeferWindowPos
, określa brak
aktywacji okna,
•
SWP _ NOSIZE
– ósmy parametr funkcji
DeferWindowPos
, określa brak zmiany
rozmiaru okna (parametry cx i cy są
ignorowane),
•
SWP _ NOMOVE
– ósmy parametr
funkcji
DeferWindowPos
, brak zmiany
położenia okna (parametry x i y są
ignorowane).
Rysunek 1.
Wirtualne pulpity Windows
Listing 1.
Funkcje WinApi
[
DllImport
(
"user32.dll"
)]
public
static
extern
IntPtr
GetDesktopWindow
();
[
DllImport
(
"user32.dll"
)]
public
static
extern
IntPtr
GetTopWindow
(
IntPtr
hwnd
);
[
DllImport
(
"user32.dll"
)]
public
static
extern
IntPtr
GetWindow
(
IntPtr
hwnd
,
int
cmd
);
[
DllImport
(
"user32.dll"
)]
public
static
extern
bool
IsWindowVisible
(
IntPtr
hwnd
);
[
DllImport
(
"user32"
)]
public
static
extern
IntPtr
FindWindowEx
(
IntPtr
parent
,
IntPtr
childAfter
,
string
szClass
,
string
szWindow
);
[
DllImport
(
"user32"
)]
public
static
extern
IntPtr
BeginDeferWindowPos
(
int
nNumWindows
);
[
DllImport
(
"user32"
)]
public
static
extern
IntPtr
DeferWindowPos
(
IntPtr
hWinPosInfo
,
IntPtr
hwnd
,
int
hWndInsertAfter
,
int
x
,
int
y
,
int
cx
,
int
cy
,
int
wFlags
);
[
DllImport
(
"user32"
)]
public
static
extern
int
EndDeferWindowPos
(
IntPtr
hWinPosInfo
);
16
POCZATKI
HAKIN9 7-8/2008
WIRTUALNY PULPIT WINDOWS
17
HAKIN9
7-8/2008
Następną czynnością jest zadeklarowanie
pól naszej klasy. Pora na kod
zaprezentowany na Listingu 3.
Pola
hwndTab
użyjemy do
przechowania uchwytów okien,
które tworzą nasz wirtualny pulpit.
Zadeklarowana tablica ma stały wmiar,
tak więc jeżeli liczba okien przekroczy
1024, nie będziemy mogli ich obsłużyć.
Aby temu zapobiec, możemy np.
wykorzystać kontener
List
. Jednakże
prawdopodobieństwo, że będziemy
obsługiwali więcej niż 1024 okna jest
małe, tak więc skorzystamy z tablicy. Pole
counter
będzie licznikiem okien, które
obsługujemy.
Jesteśmy teraz gotowi do
zaimplemetowania metod, które będą
odowiedzialne za obsługę wirtualnego
pulpitu. Dodajemy kod z Listingu 4.
Zadaniem metody
Hide
jest
schowanie okien bieżącego pulpitu. Na
początku uzyskujemy uchwyt do pulpitu,
wywołując
GetDesktopWindow
. Następie
wywołujemy funkcję
GetTopWindow
,
dzięki czemu będziemy znać uchwyt okna
będącego najwyżej w porządku osi z.
Używając funkcji
FindWindowEx
,
uzyskujemy uchwyty odpowiedno do
paska zadań (ang. task bar), a także
pulpitu (pulpit oznacza w tym momencie
ikony takie, jak Mój Komputer itp.).
Następnie przy wykorzystaniu pętli
do
.. while
przeszukujemy okna pulpitu.
Kryteria poszukiwania zawężamy do
okien widocznych i nie będących oknami
paska zadań oraz pulpitu.
Dodatkowo, jeżeli aplikacja
obsługująca wirtualne pulpity jest
widoczna, powinniśmy ją także
pominąć podczas operacji zmiany
atrybutu widoczności okien (parametr
appHandle
).
Dysponując tablicą uchwytów do
widocznych okien pulpitu, możemy
zmienić ich wybrane atrybuty. Dzięki
wywołaniu funkcji
BeginDeferWindowPos
tworzymy strukturę opisującą okna.
Używając pętli
for
zmieniamy atrybuty
wybranych okien – ponieważ chcemy
schować okna, ustawiamy flagę
SWP _
HIDEWINDOW
. Zmian dodokonujemy
wyołując funkcję
EndDeferWindowPos
.
Uzyskujemy w ten sposób nowy, wirtualny
pulpit.
Metody
Show
używamy, jeżeli
utworzyliśmy wcześniej wirtualny pulpit,
na który właśnie chcemy się przełączyć.
Tworzy ona strukturę opisującą okna, a
następnie zmienia kolejno artybuty okien
wirtualnego pulpitu, jaki zamierzamy
uaktywnić.
Listing 2.
Stałe klasy
private
int
GW_HWNDNEXT
=
2
;
private
int
SWP_NOZORDER
=
4
;
private
int
SWP_HIDEWINDOW
=
128
;
private
int
SWP_SHOWWINDOW
=
64
;
private
int
SWP_NOACTIVATE
=
16
;
private
int
SWP_NOSIZE
=
1
;
private
int
SWP_NOMOVE
=
2
;
Listing 3.
Pola klasy
private
IntPtr
[]
hwndTab
=
new
IntPtr
[
1024
];
private
int
counter
=
0
;
Listing 4.
Metody klasy
public
void
Hide
(
IntPtr
appHandle
)
{
IntPtr
deskWin
=
GetDesktopWindow
();
IntPtr
win
=
GetTopWindow
(
deskWin
);
if
(
win
==
IntPtr
.
Zero
)
return
;
IntPtr
hTask
=
FindWindowEx
(
IntPtr
.
Zero
,
IntPtr
.
Zero
,
"Shell_TrayWnd"
,
null
);
IntPtr
des
=
FindWindowEx
(
IntPtr
.
Zero
,
IntPtr
.
Zero
,
"Progman"
,
null
);
counter
=
0
;
do
{
if
(
IsWindowVisible
(
win
)
&&
win
!=
hTask
&&
win
!=
deskWin
&&
win
!=
des
&&
win
!=
appHandle
)
{
hwndTab
[
counter
++]
=
win
;
}
}
while
((
win
=
GetWindow
(
win
,
GW_HWNDNEXT
))
!=
IntPtr
.
Zero
);
IntPtr
s
=
BeginDeferWindowPos
(
counter
);
for
(
int
i
=
0
;
i
<
counter
;
i
++)
{
s
=
DeferWindowPos
(
s
,
hwndTab
[
i
]
,
0
,
0
,
0
,
0
,
0
,
SWP_HIDEWINDOW
|
SWP_NOMOVE
|
SWP_NOSIZE
|
SWP_NOZORDER
|
SWP_
NOACTIVATE
);
}
EndDeferWindowPos
(
s
);
}
public
void
Show
()
{
IntPtr
s
=
BeginDeferWindowPos
(
counter
);
for
(
int
i
=
0
;
i
<
counter
;
i
++)
{
s
=
DeferWindowPos
(
s
,
hwndTab
[
i
]
,
0
,
0
,
0
,
0
,
0
,
SWP_SHOWWINDOW
|
SWP_NOMOVE
|
SWP_NOSIZE
|
SWP_NOZORDER
);
}
EndDeferWindowPos
(
s
);
}
16
POCZATKI
HAKIN9 7-8/2008
WIRTUALNY PULPIT WINDOWS
17
HAKIN9
7-8/2008
Ponieważ chcemy teraz, aby wybrane
okna były ponownie widoczne, tym razem
włączamy flagę
SWP _ SHOWWINDOW
.
Zmian dokonujemy przy pomocy
wywołania funkcji
EndDeferWindowPos
.
Wirtualny pulpit
jako zagrożenie
Stworzona przez nas klasa jest
implementacją wirtualnego pulpitu w
systemie Windows.
Jednakże możemy ją zastosować
także w innym celu. Zastosowanie tylko
funkcji chowającej, działającej w pętli
(Listing 5), uniemożliwi nam pracę z
jakąkolwiek alikacją – po prostu nie
będziemy widzieli okien.
Dodatkowo zauważmy, że
przeszukując okna na pulpicie, na liście
wyjątków nie uwzględniamy menadżera
zadań – jego też nie będziemy w stanie
wywołać.
Możemy się także pokusić o napisanie
aplikacji, która będzie imitować awarię
systemu poprzez brak odpowiedzi na
komendy użytkownika. Zasada działania
jest dość prosta. Robimy zrzut ekranu
i zapisujemy go do pliku, następnie
ukrywamy wszystkie okna – włącznie z
paskiem zadań oraz ikonami na pulpicie,
a na koniec zmieniamy tapetę na obraz
przedstawiający zrzut ekranu.
Ponieważ obraz ekranu był zapisany
przed schowaniem wszystkich okien,
są one na nim widoczne. W ten sposób
wygląd pulpitu użytkownika nie uległ
zmianie, lecz jego funkcjonalność jest
zablokowana.
Implementacja powyższego
mechanizmu wymaga drobnych zmian
klasy wirtualnego pulpitu. Na początek
będziemy potrzebować funkcji, za
pomocą której zmienimy tapetę pulpitu.
W tym celu wykorzystamy funkcję
SystemParametersInfo
. W sekcji
importu funkcji
WinApi
dodajemy kod
zawarty na Listingu 6.
Zadaniem funkcji
SystemParametersInfo
jest zmiana
bądź odczytanie wybranych ustawień
systemowych.
Przyjmuje ona następujące
parametry:
•
uAction
– kod czynności, którą
chcemy wykonać,
•
uParam
– znaczenie tego parametru
jest zależne od pierwszego
parametru,
•
lpvParam
– znaczenie tego parametru
jest także zależne od pierwszego
parametru,
•
uWinIni
– określa czynności, jakie
zostaną wykonane po dokonaniu zmian
ustawień systemowych.
Kolejnym krokiem jest zdefiniowanie
stałych, których użyjemy, wywołując
funkcję
SystemParametersInfo
. W
sekcji definicji stałych klasy dopisujemy
kod z Listingu 7.
Wywołanie funkcji
SystemParametersInfo
ze stałą
SPI _
SETDESKWALLPAPER
powoduje zmianę
tapety pulpitu.
Ścieżkę do pliku zawierającego
obraz nowej tapety podajemy jako trzeci
parametr funkcji. W systemach Windows
Server 2003 oraz Windows XP/2000 plik z
obrazem nie może być w formacie JPEG.
Przy definicji czwartego parametru funkcji
używamy stałych
SPIF _ UPDATEINIFILE
i
SPIF _ SENDWININICHANGE
. Oznaczają
one odpowiednio aktualizację profilu
użytkownika oraz wygenerowanie
komunikatu
WM _ SETTINGCHANGE
.
Przejdźmy do napisania metody,
która będzie zamieniać tapetę pulpitu na
wcześniej wykonany zrzut ekranu. Kod
metody jest przedstawiony na Listingu 8.
Na początku dodajemy
przestrzenie nazw
System.Drawing
,
System.Windows.Forms
, a także
System.Drawing.Imaging
.
Metoda rozpoczyna działanie od
stworzenia obiektu klasy
Bitmap
,
reprezentującego zrzut ekranu
Przeciążony konstruktor tej klasy pobiera
Listing 5.
Jak nie należy używać wirtualnych pulpitów – prosty przykład
while
(
true
)
{
(
new
VirtualPulpit
())
.
Hide
(
this
.
Handle
);
Thread
.
Sleep
(
100
);
}
Listing 6.
Funkcja SystemParametersInfo
[
DllImport
(
"user32.dll"
)]
public
static
extern
int
SystemParametersInfo
(
int
uAction
,
int
uParam
,
string
lpvParam
,
int
fuWinIni
);
Listing 7.
Stałe wykorzystywane przy zmianie tapety pulpitu
private
int
SPI_SETDESKWALLPAPER
=
20
;
private
int
SPIF_UPDATEINIFILE
=
1
;
private
int
SPIF_SENDWININICHANGE
=
2
;
Listing 8.
Metoda zamieniająca tapetę pulpitu na zrzut ekranu
private
void
ChangeWallPaperToScreenShot
(
string
bmpPath
)
{
Bitmap
bitmap
=
new
Bitmap
(
Screen
.
PrimaryScreen
.
Bounds
.
Width
,
Screen
.
PrimaryScreen
.
Bounds
.
Height
,
PixelFormat
.
Format32bppArgb
);
Graphics
screenshot
=
Graphics
.
FromImage
(
bitmap
);
screenshot
.
CopyFromScreen
(
Screen
.
PrimaryScreen
.
Bounds
.
X
,
Screen
.
PrimaryScreen
.
Bounds
.
Y
,
0
,
0
,
Screen
.
PrimaryScreen
.
Bounds
.
Size
,
CopyPixelOperation
.
SourceCopy
);
bitmap
.
Save
(
bmpPath
,
ImageFormat
.
Bmp
);
SystemParametersInfo
(
SPI_SETDESKWALLPAPER
,
0
,
bmpPath
,
SPIF_UPDATEINIFILE
|
SPIF_SENDWININICHANGE
);
}
18
POCZATKI
HAKIN9 7-8/2008
dwa parametry: szerokość i wysokość
obrazu.
Ponieważ zapisujemy cały obraz
na monitorze, wykorzystujemy klasę
Screen
w celu odczytania wysokości i
szerokości ekranu monitora. Zrzut ekranu
tworzony jest poprzez wywołanie metody
CopyFromScreen
klasy
Graphics
. Kopiuje
ona piksele z określonego fragmentu
ekranu do instancji klasy
Graphics
.
Metoda ta przyjmuje następujące
parametry:
• współrzędną x punktu początkowego,
• współrzędną y punktu początkowego,
• współrzędną x punktu końcowego,
• współrzędną y punktu końcowego,
• rozmiar obszaru do przekopiowania,
• obiekt klasy
CopyPixelOperation
,
odpowiadający za sposób
przedstawiania kolorów.
Obraz zapisujemy w wybranym miejscu,
używając formatu BMP. Na koniec
zamieniamy tapetę pulpitu na wcześniej
utworzony zrzut ekranu.
Ostatnią czynnością jest – pokazana
na Listingu 9 – drobna modyfikacja
metody
Hide
.
Istotną zmianą jest wywołanie na
początku metody
ChangeWallPaperToS
creenShot
. Dodatkowo usunęliśmy kod,
który dodaje okna aplikacji, paska zadań
oraz pulpitu (rozumianego jako ikony)
do listy wyjątków podczas wyszukiwania
widocznych okien.
Uruchamiając aplikację, która
wywołuje metodę
Hide
, zauważymy, że
nasz pulpit nie odpowiada na wydawane
przez nas komendy. Istnieje jednak
możliwość otworzenia pewnych okien,
np. poprzez specjalne kombinacje
klawiszy. Aby temu zapobiec, możemy
wykorzystać wcześniejszą wersję metody
Hide
(Listing 4) w sposób przedstawiony
na Listingu 5.
Podsumowanie
Celem artykułu było zaprezentowanie
techniki wirtualnych pulpitów.
Wykorzystując ten mechanizm jesteśmy
w stanie w sposób swobodny zarządzać
oknami uruchomionych apikacji, dzięki
czemu praca z systemem Windows staje
się wygodniejsza.
Jednakże omówioną technikę można
wykorzystać do pisania róznego rodzaju
robaków, które zamiast pomagać
użytkownikowi – powodują szkody w jego
systemie.
Będąc w stanie kontrolować pozycję,
rozmiar oraz widoczność okien, jesteśmy
w stanie podmienić wybrane okna. Pisząc
program, wyglądający identycznie jak
np. menadżer poczty e-mail, możemy
bardzo szybko zamienić oryginalne okno
aplikacji na specjalnie przygotowaną
przez nas kopię. Użytkownik,
nieświadomy zagrożenia, wprowadza
swój login i hasło, które zamiast trafić
do menadżera poczty, są przez nas
przechwytywane. Jeżeli dodatkowo,
przywrócimy widoczność oryginalnego
okna aplikacji, wygenerujemy komunikat
o drobnym błędzie z prośbą powtórnego
wpisania loginu i hasła, to istnieje
duże prawdopodobieństwo, że dane
użytkownika zostaną wykradzione w
sposób niezauważalny.
Maciej Pakulski
Absolwent studiów inżynierskich oraz aktywny członek
koła naukowego .NET Wydziału Fizyki, Astronomii i
Informatyki Stosowanej Uniwersytetu Mikołaja Kopernika
w Toruniu. Obecnie na studiach magisterskich.
Programowaniem zajmuje się od 2004. Potrafi
programować biegle w językach C/C++, Java, VHDL.
Programowaniem w języku C# i platformą .NET zajmuje
się od 2006 roku. Jest autorem szeregu publikacji z
zakresu programowania oraz bezpieczeństwa IT.
Kontakt z autorem: mac_pak@interia.pl
W Sieci
• http://www.microsoft.com/express/download – witryna Microsoft, z której można pobrać
środowisko Visual C# 2008 Express Edition,
• http://www.codeproject.com – zbiór bardzo wielu przykładów aplikacji dla platformy
.NET i nie tylko. Naprawdę godny polecenia,
• http://www.codeguru.pl – polska strona dla programistów .NET,
• http://msdn2.microsoft.com – dokumentacja MSDN. Znajdziesz tu opisy wszystkich
klas, własności i metod, jakie zawiera platforma .NET wraz z przykładowymi
programami.
Listing 9.
Zmodyfikowana metoda Hide
public
void
Hide
()
{
ChangeWallPaperToScreenShot
(
@
"C:
\w
allPaper"
);
IntPtr
deskWin
=
GetDesktopWindow
();
IntPtr
win
=
GetTopWindow
(
deskWin
);
if
(
win
==
IntPtr
.
Zero
)
return
;
counter
=
0
;
do
{
if
(
IsWindowVisible
(
win
)
&&
win
!=
deskWin
)
{
hwndTab
[
counter
++]
=
win
;
}
}
while
((
win
=
GetWindow
(
win
,
GW_HWNDNEXT
))
!=
IntPtr
.
Zero
);
IntPtr
s
=
BeginDeferWindowPos
(
counter
);
for
(
int
i
=
0
;
i
<
counter
;
i
++)
{
s
=
DeferWindowPos
(
s
,
hwndTab
[
i
]
,
0
,
0
,
0
,
0
,
0
,
SWP_HIDEWINDOW
|
SWP_NOMOVE
|
SWP_NOSIZE
|
SWP_NOZORDER
|
SWP_
NOACTIVATE
);
}
EndDeferWindowPos
(
s
);
}