tanulas_kibervedelmi_tesztalkalmazasok - Fehér Krisztián honlapja

Fehér Krisztián weboldala
Fehér Krisztián weboldala
Tartalomhoz ugrás

tanulas_kibervedelmi_tesztalkalmazasok

Fehér Krisztián: Kibervédelmi tesztalkalmazások programozása

Tartalomjegyzék
1 Előszó
1.1 Mennyire valódiak a könyv példái?
1.2 A leírás célja és szerkezete
1.3 Követelmények
2 Billentyűzetnaplózás
2.1 A módszer bemutatása
2.2 Miért működik ez a módszer?
2.3 Kibervédelmi megfontolások
3 Egérnaplózás
3.1 Kibervédelmi megfontolások
4 Portszkennelés
4.1 Portszkennelés rendszerhívásokkal
4.2 Kibervédelmi megfontolások
5 WIFI szkennelés okostelefonon
5.1 Kibervédelmi megfontolások
6 Feltörhetetlen titkosítás
6.1 A módszer bemutatása
6.2 Egy példaprogram működése
6.3 Kibervédelmi megfontolások
7 Teljesítménymérés
7.1 Hogyan lehet terhelést mérni?
7.1.1 Memória terhelése
7.1.2 CPU terhelés kiszámítása
7.2 Kibervédelmi megfontolások
8 Fájlok biztonságos törlése
8.1 Kibervédelmi megfontolások

1 Előszó

Ez a leírás azoknak szól, akik saját maguk szeretnének kibervédelmi gyakorlószoftvereket készíteni, illetve érdekli őket a rendszerközeli programozás.
A leírás segítségével olyan programok készíthetőek, melyek segítségével jobban megérthető és elképzelhető valódi hackerprogramok működése, logikája, így begyakorolható az ezek ellen kifejthető védekezés is, hiszen a hatékony védekezés egyik alapfeltétele, hogy ismerjük, megismerjük, mi ellen kell védekeznünk.
Az olvasó hét témakörön keresztül ismerhet meg kibervédelmi szakterületeket.
A kibervédelem rendkívül aktuális és felkapott téma. Azok számára, akik technikai értelemben is szeretnék közelebbről megismerni egy-egy szakterület technikai hátterét, ez a leírás tökéletes választás.

1.1 Mennyire valódiak a leírás példái?

A leírásban bemutatott technikák és kódok igaziak és működőképesek, mindazonáltal nem valódi hackerprogramok, csupán működésükben reprodukálják azok viselkedését. Mind manuális kereséssel, mind automatizált védelmi módszerek (pl. víruskeresők) használatával felfedhető a működésük.
Ezek gyakorlásra, tájékozódásra használhatóak, nem rosszindulatú programok. A bemutatott módszerek egy része a támadók tevékenységét igyekszik szemléltetni madártávlatból, míg másik részük elsősorban védekező stratégiák kialakításához illik jobban.
Bármilyen, ettől eltérő célú használattól elhatárolódok és minden felelősséget elhárítok.

1.2 A leírás célja és szerkezete

A leírás elsődleges célja olyan ismeretek átadása, melyek segítségével gyakorlatban is megérthető és programírás szintjén is megismerhető egy-egy olyan technika, melyek segítségével imitálható hackerprogramok működése és ezáltal az ellenük történő védekezés hatékonysága növelhető.
Mi ennek a célja? Az okulás és a tapasztalatokból történő tanulás, a minél sikeresebb védekezés érdekében. Ezt máshogyan csak gyötrelmesen, vagy legalábbis hosszú idő után szerezheti meg az, aki ezeknek a témáknak a tanulmányozását tűzi ki célul.
Minden fejezet egy adott technika áttekintéséből, a vonatkozó programozási eszközök bemutatásából, majd gyakorlati demonstrációból áll. A fejezetek végén amolyan gondolatébresztőként kifejezetten kibervédelmi szempontok is helyet kapnak az adott technikával kapcsolatban. Ez a leírás teljes forráskódokat nem tartalmaz.
A téma jellegéből adódóan a bemutatott technikák csak egy szubjektív megközelítésként kezelendőek. Napjaink informatikai lehetőségei olyannyira szerteágazóak, hogy rengeteg más módszer is létezik ugyanannak a célnak az elérésére.
Egyetlen esetben sem lehet törekedni az adott szakterület túlzottan mély ismertetésére, csupán azokat a részleteket vannak kiemelve, melyek az adott témakör gyors megismeréséhez és feldolgozásához elengedhetetlenül szükségesek.

1.3 Követelmények

Az elsődleges célközönséget azon olvasók alkotják, akik érdeklődnek a kibervédelem technikai megvalósításai iránt és /  vagy tapasztalattal rendelkeznek a klasszikus alkalmazások fejlesztésében C / C++ (Windows), valamint JAVA (Android) programozási nyelven.
Ahol csak lehet, a sztenderd C nyelven alapuló, de alapvetően Windows-specifikus megoldások vannak kidolgozva, talán ez tekinthető szakmai értelemben a legszélesebb “közös nevezőnek”. Sőt, ahol csak lehet, ragaszkodunk a sztenderd C nyelv eszközeihez is. Ehhez Visual Studio esetében meg kell adnunk a projekt beállításai között a _CRT_SECURE_NO_WARNINGS direktívát.



2 Billentyűzetnaplózás

A billentyűnaplózás (angolul: key logging) elnevezéssel illetett tevékenységek a billentyűhasználat figyelése körül forognak.
Adatok bevitelére egyelőre a billentyűzet a legelterjedtebb és valóban, rengeteg mindent ki lehet deríteni csupán a leütött billentyűk elemzésével.

Néhány példa, a teljesség igénye nélkül:
• meglátogatott weboldalak
• felhasználónevek/jelszavak
• begépelt privát üzenetek
• webes keresési kulcsszavak.

A billentyűzet egyelőre megkerülhetetlen adatbeviteli eszköz és egy hacker szemszögéből meglehetősen ésszerűnek tűnik specializálódni rá, hiszen egyfajta mechanizmus kiismerésére kell csak fókuszálni.
Ennek ellenére a felhasználói adatbevitel rögzítésére számos módszer létezik. Egy elterjedt módszer DLL-ek segítségével az ún. “hooking”. Ennek keretében az operációs rendszer alacsonyszintű folyamataiba szoktak beregisztrálni és aktiválni egyedi kódokat, algoritmusokat, melyek elvégzik a dolgukat. Ez a technikásabb, “életszerűbb”, de bonyolultabb módszer.
Egy másik, ezen leírásban is bemutatott módszer segítségével az operációs rendszer alacsonyszintű lekérdezőfunkcióit, függvényeit használjuk. A Windows SDK ugyanis lehetőséget biztosít arra, hogy figyeljük a billentyűzet, a billentyűk állapotát. Semmilyen hackelés nem kell hozzá, a hivatalos SDK alapértelmezett eszközei is elegendőek.

2.1 A módszer bemutatása

Az egyik megközelítés a GetAsyncKeyState függvényt használja, mely a paraméterként megadott kódú billentyű aktuális állapotáról ad felvilágosítást (le van-e nyomva vagy sem):

SHORT GetAsyncKeyState( int vKey );

Az itt hivatkozott ún. virtuális billentyűkód azért hasznos, mert univerzális, eszközfüggetlen használatot tesz lehetővé és nem mellesleg nem kell karakterkódokat észben tartanunk, hanem szöveges kódnévvel is hivatkozni lehet egy-egy billentyűre.
Megjegyezzük, hogy az egérgombok állapotának lekérdezése szintén lehetséges ugyanezzel a függvénnyel. Ezt fogja kihasználni leírásom egy másik példaprogramja is a következő fejezetben.
A billentyűleütéseket egy egyszerű WHILE ciklussal is figyeltethetjük. Amennyiben történt leütést, akkor feldolgozhatjuk, például kiírathatjuk egy naplófájlba, melynek neve LOG_keyboard.txt és a program mappájában jön létre. Ha ezt képesek vagyunk programunkkal végrehajtatni, akkor lényegében egy valódi billentyűnaplózó program működését tudjuk imitálni.
Egy WHILE cikluson belül egy FOR ciklus végigszalad a legfontosabb billentyűk kódjain: mindegyikre ellenőrzi azok állapotát és ha lenyomott billentyűt érzékel, naplózza azt.


while (1)
{
 for (i = 8; i <= 190; i++)
 {
  if (GetAsyncKeyState(i) == -32767)
   Save(i, "LOG.txt");
 }
}


Megjegyezzük, hogy az antivírus programok képesek felismerni egy ilyen programot, ennek köze lehet a folyamatos GetAsyncKeyState függvényhívásnak is. Tehát mind a működési intenzitás, mind a meghívott függvények is árulkodó jelei egy billentyűnaplózó jelenlétének.
A legegyszerűbb, ha a fejlesztéskor egyszerű konzolalkalmazásban gondolkodunk, de Windows projektként is kezelhetjük a dolgot.
Felmerül a kérdés, hogy hogyan lehet elrejteni az alkalmazást annak futása közben, hiszen egy konzolablak könnyen beazonosítható és meglehetősen feltűnő is ilyenkor.
Alkalmazásablakok láthatóságát WIN32API-val a ShowWindow függvénnyel tudjuk befolyásolni:

BOOL ShowWindow(
 HWND hWnd,
 int  nCmdShow
);

Az első paraméter az adott ablak azonosítója, a másik a megjelenés módja. Ez utóbbi 0 esetén rejtett állapotot jelent, 1 esetén pedig láthatót. A két állapotra SW_HIDE és SW_SHOW értékekkel is hivatkozhatunk.
Ablakos alkalmazás esetén a ShowWindow metódust egyszerűen nem hívjuk meg. Konzolalkalmazások esetében azonban semmiféle ablaklétrehozást nem kell kezdeményeznünk alapesetben. Ilyenkor is lehetőség van azonban a konzolablak elrejtésére, mégpedig úgy, hogy először a FindWindowA függvénnyel lekérdezzük az alkalmazásablak azonosítóját.

HWND FindWindowA(
 LPCSTR lpClassName,
 LPCSTR lpWindowName
);

Az első paraméter az alkalmazásosztály neve. Ez konzolalkalmazás esetén "ConsoleWindowClass" lesz. A második paraméter az adott alkalmazás ablakának neve. Ha NULL értéket adunk meg, akkor minden potenciális ablak szóba jöhet. Példánkban abból indulunk ki, hogy a program indításakor más konzolalkalmazás nem fut.
Számunkra az a fontos, hogy így hozzájuthatunk parancssoros alkalmazásunk ablakazonosítójához, amivel azután a ShowWindow függvénnyel eltüntethetjük magát az ablakot.
Ezután a program már csak teszi a dolgát és a leütött billentyűkhöz tartozó karaktereket kiírja egy egyszerű szöveges fájlba. A működésének a kimenete ez a fájl lesz.

2.2 Miért működik ez a módszer?

A Windows egy ún. multitasking rendszer, alkalmazások kvázi párhuzamos futtatását képes levezényelni.
A GetAsyncKey függvény nem a hívó alkalmzáshoz érkező billentyűleütéseket figyeli, hanem „globálisan” szolgáltat információkat, tehát nem kell fókuszban lennie a programnak.
Ez a két feltétel garantálja a módszer működését. Mindazonáltal megjegyezzük, hogy a program elindulásakor egy pillanatra felvillanhat a konzolablak, ami szakavatott szem számára figyelmeztető jel lehet (észlelés esélye).

2.3 Kibervédelmi megfontolások

Rendben van, a módszer működik. De hogyan érik el az adattolvajok, hogy automatikusan elinduljon a célrendszeren egy ilyen program?
Ilyesmi nem témája ennek a leírásnak, de számos módszer létezik erre, preparált pendriveoktól kezdve az egyszerű rendszerindítási beállításokig, hogy az alkalmazás automatikusan elinduljon a Windows indulásakor.
Egy korszerű számítógépen nem fog feltűnni egy ilyen naplófájl jelenléte sem, már ami a fájlméretet illeti, mivel 1 gigabájtnyi szöveg akár egy kb. 500.000 oldalas könyvnek is megfelelhet. Másrészt pedig egy naplózás nem tart örökké, a szükséges információ pedig belátható időn belül jóval előbb kinyerhető a naplóból, mielőtt azt valaki normál számítógéphasználat mellett észrevenné.
Természetesen a „profi” programok biztosra mennek és minden nyomot eltüntetnek maguk után. Programunk ilyesmit nem végez el.
A program átnevezésével nagyon egyszerűen el lehet rejteni azt a futó porgramok között. Ha valamilyen olyan nevet adunk neki, ami nagyon hasonlít egy rendszerprogramhoz (például: explorer.exe), akkor azt még nehezebb lesz beazonosítani.
A kipróbálást megkönnyíthetjük, ha valamelyik speciálisabb billentyű (például ESC) leütésekor befejeztetjük a program futását.
Egy ilyen példaprogramot a fentiek tükrében remekül lehet használni például keylogger detektálás gyakorlására.
A naplófájl mindazonáltal az adott gépen marad, tehát fizikailag is hozzá kell férni a géphez, hogy felhasználható legyen.



3 Egérnaplózás

Biztonsági szempontból a másik érdekes terület az egérkurzor állapotának lekérdezése.
Az egérgombok állapotát a már ismert GetAsyncKeyState függvénnyel kérdezhetjük le, az egérpozíciót pedig a GetCursorPos függvénnyel:

BOOL GetCursorPos( LPPOINT lpPoint );

A paraméterként megadott pont struktúra egészen egyszerűen X és Y képernyőkoordináták tárolására alkalmas:

typedef struct tagPOINT {
 LONG x;
 LONG y;
} POINT, *PPOINT;

A lekérdezések ugyanúgy globálisan elvégezhetőek, mint a billentyűlekérdezések esetén, azaz nem szükséges az ablaknak fókuszban lennie, hogy a lekérdezés működjön.
Példaprogramunk 50 ezredmásodpercenként lekéri az egérkurzor koordinátáit és az egérgombok állapotát, majd ki is írja ezeket a LOG_mouse.txt naplófájlba a program mappájában. Az egérgombok állapotát viszont csak akkor naplózza, ha tényleg megnyomták őket. Egy további megszorítás is van itt: a GetAsyncKeyState nem fogja érzékelni azokat a bal egérgombos kattintásokat, melyek ablakok közötti fókuszváltáshoz kellenek.
A program gyakolási célból történő felhasználásának feltételei, az elemzések lefolyása megegyezik a billentyűnaplózáséval.

3.1 Kibervédelmi megfontolások

Az egérkurzor tulajdonságai sohasem önmagukban, hanem valamilyen kontextusban szolgáltatnak információt. Ez a kontextus a képernyő aktuális tartalma. Éppen ezért más módszerekkel, például billentyűzetnaplózással válik igazán hasznossá. Segítségével például kikövetkeztethető, hogy egy adott weboldal mely elemére kattintanak. Ezt az információt máshogyan csak nagyon-nagyon nehezen, vagy egyáltalán nem lehetne megszerezni. Az előző példához hasonlóan azonban itt is fizikai hozzáférés szükséges a naplófájl felhasználásához.



4 Portszkennelés

A portszkennelés, vagy portletapogatás az információgyűjtési módszerekhez tartozik. Segítségével a biztonsági szakemberek meg tudják állapítani, hogy egy adott számítógépen vannak-e nyitott portok. Ezt úgy lehet elképzelni, mintha valaki végigszaladna egy sor ajtó mellett és közben megnézné, hogy az ajtók nyitva vannak-e. Az ajtók az ún. portok, amelyek logikai kommunikációs csatornák. Ha egy ilyen csatorna nyitva van, akkor azon a számítógép fogad bemenő kéréseket. Egy támadónak ennyi lényegében elég is, ugyanis vannak olyan behatolási módszerek, melyek a nyitott portokon keresztül kivitelezhetőek.
Egy támadó szisztematikusan végigszkennelheti az összes, vagy a leggyakrabban nyitva hagyott portokat egy adott, hálózaton elérhető számítógépen. Ehhez legtöbbször valamilyen automatizált, kész programot használnak.

4.1 Portszkennelés rendszerhívásokkal

A szkennelés megkezdése előtt közvetlen kapcsolatot kell felépíteni a vizsgálandó számítógéppel. Ez technikailag az ún. socketekkel történik. Ehhez először is inicializálni kell a Winsock DLL-t a WSAStartup függvényhívással. Egy sockaddr_ adatstruktúrában meg kell adnunk a célgép IP címét, az ellenőrizni (szkennelni) kívánt port számát és a hozzáférést:


clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("87.229.65.16");
clientService.sin_port = htons(80);


A hozzáférési típusok lehetséges értékei:

AF_UNSPEC
Nem specifikált.

AF_INET
Ipv4 címzés.

AF_IPX
IPX/SPX címzés. NWLink kell hozzá. (Windows Vista és későbbiek nem támogatják.)

AF_APPLETALK
Appletalk címzés. (Windows Vista és későbbiek nem támogatják.)

AF_NETBIOS
NetBIOS címzés. Külön beállítást igényel 64 bites Windows rendszereken, de 32 biteseken alapértelmezetten elérhető. Ilyenkor a socket type paraméterét kötelezően SOCK_DGRAM értékre kell beállítani.

Ezt követően lehet próbálkozni először a socket objektum létrehozásával (socket függvényhívás). Ha ez nem sikerül, illik meghívni a WSACLeanup függvényt, ami befejezteti a Winsock DLL használatát. Hiba esetén a WSAGetLastError függvénnyel kérhetjük le a hibaüzenet szövegét.
Ha létrejön érvényes socket, akkor kísérelhetjük meg a kapcsolat létrehozását, a connect függvénnyel. Akár van hiba eközben, akár nem, a socketet a closesocket függvénnyel be kell zárni ezt követően. Amennyiben a kapcsolat sikeresen létrejött, akkor azt azt jelenti, hogy a megadott port nyitva van. (Bingó!)
Egy FOR ciklus segítségével egész sor portot végigszkennelhetünk egy lépésben. A programot könnyedén átírhatjuk úgy, hogy indítási paraméterként legyen megadható a célgép és a portszám is, így testreszabott manuális ellenőrzéseket is indíthatunk, például egy parancsfájlból.
Megjegyzendő, hogy az inet_addr függvény helyett a Microsoft az inet_pton függvény használatát javasolja.

4.2 Kibervédelmi megfontolások

A leírás portletapogató példaprogramja visszakövethető, tehát nem alkalmas „rejtett” szkennelésre. Ennek ellenére rutinszerű ellenőrzésekhez jól jöhet, mivel segítségével egy egyszerű eszközzel felfedhető egy hiányos rendszerkonfiguráció: például egy feleslegesen nyitva hagyott port.



Az alábbi képen egy példaprogram futásának egy tipikus mozzanat látható:





5 WIFI szkennelés okostelefonon

Az okostelefonok térnyerése több szempontból örömteli a biztonsági kérdések iránt érdeklődőknek. Az egyik ilyen szempont, hogy ezekbe az eszközökbe nem csak rengeteg szenzort, adó-vevőt stb. szuszakoltak bele, hanem ezek szabadon elérhetőek ingyenes programozási eszközökkel.
Nem fogok kitérni az androidos programozás semmilyen előzetes témájára, hanem azt feltételezzük, hogy az olvasó már rendelkezik ilyen ismeretekkel. Példaprogramunk az ingyenes Android Studio fejlesztőeszközzel lett kifejlesztve, mely az androidos alkalmazások hivatalos fejlesztőeszköze.
Itt egy JAVA nyelven megírt segédeszköz forráskódját fogjuk áttekinteni, ami az ún. „harci kocsikázáshoz” (angolul: war driving) használható. Ez a gyakorlatban azt jelenti, hogy androidos telefonunkon az alkalmazást futtatva, egy rövidebb séta keretében mobileszközünkkel összegyűjthetjük az elérhető WIFI hálózatok legfontosabb adatait, sőt, ezeket földrajzi koordinátákhoz is rendelhetjük, hiszen a legtöbb mobileszköz (ezalatt most a telefonokat és tableteket értjük) rendelkezik GPS alapú helymeghatározási képességgel is. Az eszközök mérete igen kicsi, ez lehetővé teszi a diszkrét „munkát”, sőt, ha zsebbe, vagy hátizsákba rakjuk az eszközt, senki emberfia meg nem mondaná rólunk, hogy éppen WIFI adatokat „szüretelünk”. A dolog teljesen legális, hiszen a hozzáférési pontoknak muszály ezeket az információkat az éterbe sugározniuk, mivel egyszerűen így működnek. Ezek tehát publikus adatok.
A technikát egyébként egészen banális célokra is fel lehet használni, például felmérhetjük vele, hogy az otthoni vezetékes hálózatunk határai meddig terjednek. Ez nem mellesleg általában elég tanulságos is szokott lenni.
Az alkalmazás felhasználói felületének legnagyobb részét egy lista alkotja, mely az elérhető WIFI hálózatok neveit tartalmazza. A képernyő felső részén három nyomógomb található, melyek segítségével beállíthatjuk, hogy pár másodpercenként automatikusan lekérdeződjenek-e a WIFI adatok, vagy inkább kézzel szeretnénk azokat egy adott pontról beolvasni.

Egy példaalkalmazás valahogy így fest mobileszközön, működés közben:
 


Az összegyűjtött adatokat célszerű egy CSV fájlba (WIFI_SCAN.TXT) lemeneti, hogy utólag kényelmesen fel lehessen dolgozni azokat. A programhoz tartozó Manifest fájlban ezért meg kell adnunk a WIFI, a háttértároló és a helymeghatározó eléréséhez szükséges engedélyeket.
A felhasználói felület elemeinek programozásával nem foglalkozunk. Ezeket mindenki egyénileg tanulmányozhatja a forráskódban. Amire viszont kitérünk, az az adatlekérdezések mikéntje.
A WIFI adatok lekérését egy ún. BroadCastReceiver osztályon keresztül tudjuk levezényelni, melyhez a WIFI lekérdezéssel kapcsolatos eseményt rendeljük. Az osztálypéldány neve  wifiStateChangedReceiver lesz, amit a registerReceiver hívással külön regisztrálnunk kell.
Ennek az osztálynak az egyes metódusait kell részletesen megírnunk, hogy működjön a dolog. A Wifimanager osztály osztálypéldánya végzi el a WIFI eszközzel kapcsolatos adatok lekérdezését, a startscan metódusával. A kapott adatokat a getScanResults metóduson keresztül érhetjük el.
Az adatokat egy Scanresult osztálypéldány tartalmazza. Az elérhető adatok listája, az adattípusokkal:

String   BSSID
String   SSID
String   capabilities
int   centerFreq0
int   centerFreq1
int   channelWidth
CHANNEL_WIDTH_20MHZ
CHANNEL_WIDTH_40MHZ
CHANNEL_WIDTH_80MHZ
CHANNEL_WIDTH_160MHZ/CHANNEL_WIDTH_80MHZ_PLUS_MHZ
int   frequency
int   level
CharSequence  operatorFriendlyName
long   timestamp
CharSequence  venueName

A bemutatott példaprogram a BSSID, SSID, capabilities és level tulajdonságokat naplózza, melyek a hálózat nevét, MAC címét, az alkalmazott titkosítást és a jelerősségét adják meg.
Az alábbiakban egy valós utcai mérés részlete látható, ahol a konkrét adatok természetesen anonimizálva vannak.

HUAWEI-ABC,44:da:11:aa:11:99,[WPA2-PSK-CCMP][WPS][ESS],-78,19.0,47.5
AndroidAP,11:66:aa:cd:cc:47,[WPA2-PSK-CCMP][ESS],-80,19.0,47.5
DDDmobility,ff:cc:ee:aa:55:14,[WPA2-PSK-CCMP][ESS],-75,19.0,47.5

Az automatikus szkennelés kapcsán meg kell említeni, hogy mivel egy időzítőt (Timer) használunk, ezért figyelnünk kell arra, hogy az időzítő kódja Runnable legyen, mert a GUI-t (konkrétan a listadobozt) is módosítjuk benne. Ellenkező esetben problémák adódhatnak futás közben.
Természetesen a helymeghatározást is be kell kapcsolnunk, ez utóbbi azonban csak ahhoz kell, hogy az elmenetett WIFI adatokhoz oda tudjuk írni az aktuális földrajzi koordinátákat is.

5.1 Kibervédelmi megfontolások

Egy WIFI felderítőprogram segítségével képet lehet kapni arról, hogy mit láthat egy rosszindulatú felhasználó a hálózatunkból. A mérési adatok segítségével jobban beállítható a WIFI kiterjedése, ellenőrizhető egyes beállítások érvényessége, de ezenkívül felfedhetőek hamisított hozzáférési pontok is, melyeket hálózati forgalom elterelésére használhatnak.
A program ezenkívül a mérés WGS84 földrajzi koordinátáit is elmenti, ez később felhasználható speciális WIFI térképek létrehozására is.



6 Feltörhetetlen titkosítás

Korunk egyik legnagyobb kihívása adataink védelme. Ennek egyik lehetséges módja a titkosítás.
A titkosítások elsődleges célja nem az adatlopások megelőzése, hanem annak megakadályozása, hogy a már ellopott adatokat a tolvajok értelmezni/felhasználni tudják.
Rengeteg furfangos módja létezik az adatok elváltoztatásának. A módszerek gyenge pontja mindig az, hogy az adattitkosítás milyen könnyen reprodukálható, hiszen a próbálgatás az egyik legelterjedtebb módszer a visszafejtési próbálkozások esetében.
Ebben a leírásban egy egyedi megoldás kerül bemutatásra, ami azért is jó lehet, mert az egyedi titkosításokat sokkal nehezebb, vagy lehetetlen feltörni sztenderd eszközökkel. A dolog további bája, hogy egy saját fejlesztésű titkosításba bármilyen furfangos módszert berakhatunk, amit csak el tudunk képzelni (és le tudunk programozni).
Példánk egy kulcsalapú titkosítás, mellyel egyedi fájlokat tudunk titkosítani. A titkosítás szimmetrikus, azaz ugyanaz a kulcs használható az adatok titkosítására és visszafejtésére is. Vegyük észre, hogy a módszernek maga a kulcs az egyetlen gyenge pontja: a kulcsot kell mindenáron biztos helyen őrizni az illetéktelen hozzáférések elől.

6.1 A módszer bemutatása

A titkosítás során maguk az adatok nem változnak meg, viszont a program bájtokra szedi szét őket. Előkészületként egy ún. zajgenerálás történik meg: egy véletlenszerű bájthalmaz generálódik, minden titkosítási menetben más és más. Ezt követően az adatbájtokat, tehát a szétszedett adatdarabkákat véletlenszerűen szétszórjuk ebben a digitális zajban, de úgy, hogy két bájt nem kerülhet közvetlenül egymás mellé. A kulcs egy bináris adatfájl (elnevezése: KEY.KEY), ami az adatdarabkák helyeit tartalmazza a zajban.
A titkosított adatfájl tökéletesen visszafejthetetlen a kulcs nélkül. Ennek csak egyik oka a zaj. A másik ok az eredeti adat tartalmában van, amit nevezhetünk szemantikai zárnak is. Nézzünk egy példát szöveges tartalom titkosítására! Vegyük az alábbi szöveget:

gyere el fél ötre

Titkosítva a szöveg így is kinézhet:

jdskfvüdéeéfáőtsnjkshgfwhkihkewocöüxétvcálvékvpökmíáádőwöwiuqrmkdslnctnxuiivfeiqwmnc

A visszafejtés során próbálkozni kell különböző kombinációkkal. A baj az, hogy a lehetséges megoldások száma szinte végtelen. Az alábbi értelmes szövegek bármelyike kirakható ugyanis a fenti titkosított karaktersorozatból:

üdv és szia

álmodtam egy szépet

gyere el fél ötre

nem szeretlek
...

Melyik lehet a jó megoldás? Kulcs nélkül lehetetlen megmondani. A visszafejtés során minden próbálkozás alkalmával egy kulcsot kell hamisítani. Ezt végtelenszer meg lehet próbálni, de a végeredményben soha nem lehetünk biztosak, hiába kapunk értelmes szöveget.
A titkosítás erőssége nagy, de ez még tovább növelhető a zaj arányának növelésével.
Ez a titkosítási módszer speciálisan számokra is alkalmazható. Milyen bankszámlaszám, vagy PIN kód lehet például elrejtve az alábbi számsorozatban?

621217457848738776672166378372147893782478674336746767467215435215638388473674523412451565763610273173172899ö4894350246898934823894

A választ csak az eredeti feloldókulccsal lehet helyesen megadni.
Megjegyzendő, hogy ez a módszer nagy valószínűséggel ellenáll kvantumszámítógépes feltöréseknek is, mivel a visszafejtés sikeressége nem függ az időtől: egy kvantumszámítógép hiába lenne képes az összes lehetséges variáció legenerálására 1-2 perc alatt, a keletkező gigászi adattömeg alapján sem lehetne megmondani, mi a helyes megoldás.

6.2 Egy példaprogram működése

A következőkben egy a módszert alkalmazó program működését vesszük górcső alá kódszinten.
A program elindulásakor (indulas függvény) először ellenőrzi, hogy létezik-e már kulcs. Ezután előkészíti magát a “zajt” és törli a tényleges adatok tárolására szánt memóriaterületeket. A zaj létrehozásakor nem szükséges kriptografikus erősségű véletlenszámgenerátorban gondolkodnunk, megteszi egy pszeudovéletlenszám generátor is. Programunkban egy olyan véletlenszámgenerátort alkalmazunk, mely véletlenszámok összege. A zaj a megengedett adatfájlmérethez képest kb. 80-szoros.
A kulcs létrehozását a kulcsletrehoz függvény végzi. A kulcsfájl neve alapértelmezetten KEY.KEY lesz. A kulcs a fentebb már ismertetett tartalommal bír. Ki kell emelni, hogy egy kulcs újrafelhasználható és ugyanaz a kulcs használható bármilyen, 1-50000 bájt méretű fájl titkosításához. A felhasználói felületen a kulcs létrehozását a ‘Create key’ nyomógombra kattintva indíthatjuk el.
Egy fájl titkosítását (csak már létező kulcsfájl esetén, ami a programmal azonos könyvtárban található) az adatkiiras, a visszafejtését az adatbeolvasas függvények végzik el.
A titkosítás során minden esetben egy 4.000.004 bájt hosszúságú bináris fájl keletkezik, mely a zajt tartalmazza a tényleges adatokkal és 4 bájtnyi hosszinformációt, mely azt adja meg, hogy a zajban bájtra pontosan mekkora méretű adat van elrejtve. Megjegyzendő, hogy ez szintén egy gyenge pont. Mindazonáltal az a titkosított adatok mennyiségének ismerete sem teszi lehetővé a sikeres visszafejtést.
A visszafejtés pofonegyszerű: a titkosított adatkavalkádból a kulcsban a hosszinformációnak megfelelő alkalommal lépkedve kiszemezgetjük az eltárolt bájtokat, szépen egymás után, majd pedig az immár újrafelépített adatokkal felülírjuk a titkosított tartalmú fájlt.
Fontos, hogy a fájl neve titkosított formájában is megmarad. Ez megkönnyíti a visszafejtést.

Néhány optimalizálási ötlet:
• a fájl nevét is el lehetne rejteni, az eredeti fájlnevet pedig szintén megváltoztathatnánk, így nem lehetne következtetni arra sem, hogy milyen típusú fájl volt az eredeti
• a fájlméret dinamikus kezelése szintén nem rossz ötlet.

A futó alkalmazás így néz ki. A nyomógombok elnevezését illetően megmaradtam az angol nyelvnél:



6.3 Kibervédelmi megfontolások

Egyes fájlok titkosítása elsősorban a védekezést szolgálja. Így akkor sem kell aggódnunk, ha esetleg illetéktelen kezekbe kerül.



7 Teljesítménymérés

Ugyan mi köze lehet a számítógép teljesítményének a monitorozása a hackeléshez?
Léteznek kémprogramok, vírusok stb., melyek aktívan igyekeznek elrejteni tevékenységüket. Ennek egyik eszköze az lehet, ha a számítógép üresjárati, vagy éppen ellenkezőleg: csúcsterheléses állapotaiban végzik tevékenységüket. Ilyenkor ugyanis jelentősen csökkenhet annak az esélye, hogy észreveszik őket, például a számítógép lelassulása miatt. A videóállományok feldolgozása, lejátszása például egy tipikusan teljesítményigényes dolog. Ha ilyesmit végez egy számítógép, akkor egy illegális program is elkezdhet működni, a számítógép használója ugyanis eleve abból indul ki, hogy a gépe éppen “nagyon elfoglalt”.
Ugyanígy, terhelés észlelése segíthet felismerni nem kívánatos tevékenységet egy számítógépen.

7.1 Hogyan lehet terhelést mérni?

Terhelés alatt itt a memória kihasználtságát és a CPU terhelését értjük.

7.1.1 Memória terhelése

A memória ellenőrzése az egyszerűbbik eset. Ehhez a GlobalMemoryStatusEx függvényt használhatjuk, ami egy adatstruktúrába tölti be az információkat:

BOOL GlobalMemoryStatusEx(
 LPMEMORYSTATUSEX lpBuffer
);

Az LPMEMORYSTATUSEX adatstruktúra igen sokrétű információval szolgál a rendszer memóriaterületeiről:

typedef struct _MEMORYSTATUSEX {
 DWORD     dwLength;
 DWORD     dwMemoryLoad;
 DWORDLONG ullTotalPhys;
 DWORDLONG ullAvailPhys;
 DWORDLONG ullTotalPageFile;
 DWORDLONG ullAvailPageFile;
 DWORDLONG ullTotalVirtual;
 DWORDLONG ullAvailVirtual;
 DWORDLONG ullAvailExtendedVirtual;
} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;

Számunkra elsősorban a dwMemoryLoad, ullTotalPhys és az ullAvailPhys értékek érdekesek.
A dwMemoryLoad százalékosan adja meg a használatban levő memória mennyiségét.
A másik két érték a teljes memóriaméretet és a rendelkezésre álló (szabad) memória méretét adják meg. Előbbiből utóbbit kivonva megkapjuk a lekérdezés pillanatában foglalt memória méretét, bájtra pontosan.

7.1.2 CPU terhelés kiszámítása

A CPU terhelésének mérése már egy kicsit trükkösebb és csak több lépcsőben lehetséges, de megoldható.
Ehhez először is a GetSystemTimes függvényre van szükség, ami egy-egy FILETIME struktúrában ad vissza időértékeket.

BOOL GetSystemTimes(
 PFILETIME lpIdleTime,
 PFILETIME lpKernelTime,
 PFILETIME lpUserTime
);

Az időértékek az 1601 január 1. óta eltelt időt adják meg 100 nanoszekundumos intervallumokban. A visszaadott érték 64 bites és egy alsó, valamint felső duplaszóból áll.

typedef struct _FILETIME {
 DWORD dwLowDateTime;
 DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;

Ezeknek az értékeknek az ismeretében számolhatjuk ki a CPU terhelési értékeit. Ehhez még szükség van egy segédfüggvényre (TimeToInt), ami az időértékeket INTEGER típusúra konvertálja.
A szamitas a terheles_kiszamitas függvény végzi, ami 0.0 és 1.0 értékek között adja meg a terhelés mértékét. A végeredményt a CPU_terheles függvény adja meg, hiba esetén -1.0 visszatérési értékkel. A számítás során a várakozással eltelt idő, valamint a kernel és user módban töltött időt használjuk fel.
Egy ilyen példaprogram a CPU terhelését százalékban, a RAM kihasználtságát megabájt értékekben adja meg, ezenkívül pedig készít egy CSV fájlt, mely mindkét mérési értéket százalékban adja meg, például így:

TIMESTAMP,TYPE,VALUE
2019/10/30 00:08:06,CPU,11
2019/10/30 00:08:06,RAM,31
2019/10/30 00:08:07,CPU,36
2019/10/30 00:08:07,RAM,32

A CSV fájlok alapján egy táblázatkezelő programmal nem mellesleg nagyon könnyen előállíthatóak grafikonok is, ami megkönnyíti a kiértékelést.
Akit érdekel néhány további érdekes CPU-specifikus információ lekérdezésének lehetősége, annak ajánlható a GetSystemInfo függvény, mely egy SYSTEM_INFO adatstruktúrába tölt be számos érdekes vonatkozó információt.

void GetSystemInfo(
 LPSYSTEM_INFO lpSystemInfo
);

typedef struct _SYSTEM_INFO {
 union {
   DWORD dwOemId;
   struct {
     WORD wProcessorArchitecture;
     WORD wReserved;
   } DUMMYSTRUCTNAME;
 } DUMMYUNIONNAME;
 DWORD     dwPageSize;
 LPVOID    lpMinimumApplicationAddress;
 LPVOID    lpMaximumApplicationAddress;
 DWORD_PTR dwActiveProcessorMask;
 DWORD     dwNumberOfProcessors;
 DWORD     dwProcessorType;
 DWORD     dwAllocationGranularity;
 WORD      wProcessorLevel;
 WORD      wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;

7.2 Kibervédelmi megfontolások

A rendszerterhelés detektálása fontos részét képezheti a védekezésnek. Automatizáltan pedig igen hatékony monitorozóeszközzé is válhat. Ne felejtsük el azonban, hogy ez csak egy eszköz a sok közül!
A teljesítményfigyelést összekapcsolhatjuk egy konkrét alkalmazás futásának figyelésével. Elképzelhető olyan szituáció is, ahol az operációs rendszer alapértelmezett monitorozóprogramjait támadók blokkolják. Ilyenkor is jól jöhet egy „független” segédprogram.



Egy példaprogram, futás közben:





8 Fájlok biztonságos törlése

A fájlok törlése kétélű fegyver.  Jogos az igényünk arra, hogy törölni kívánt adataink valóban visszaállíthatatlanul törlődjenek. Másrészről egy rosszindulatú támadás nagyon érzékeny károkat tud okozni azzal, hogy visszavonhatatlanul töröl egy-egy értékes tartalommal bíró adatfájlt.
Amikor az operációs rendszer töröl egy fájl, akkor valójában csak rögzíti az adathordozó vonatkozó nyilvántartásában, hogy a fájl ki van vonva a forgalomból és az általa korábban használható adatterületre szabad írni. Amikor véletlenül törölt adataink visszaállítására van szükségünk, ez még jól is jöhet, de egészen más a helyzet akkor, amikor ki kell adnunk egy adathordozót a kezünkből, amin adataink vannak (például garanciális szervízbe egy drága merevlemezt, ami nem indul el, de értékes és érzékeny adataink is vannak rajta). Senki nem szeretné, ha illetéktelenek akárcsak bele is néznének személyes adataikat tartalmazó fájlokba, még ha azok sztenderd módon törölve is lettek. Ennek a kockázata viszonylag alacsony, de létezik.
Amennyiben nincsen szükség egy korábban még fontos adatfájlunkra, a legbiztosabb módja a törlésének az, ha teljesen felülírjuk az adatokat. Ez lehet csupa 0 érték, de léteznek ajánlások különféle bitminták használatára, sőt, ezek többszöri kiírására is. A fájltörlés alatt ezen a helyen tehát felülírást, nem pedig fájleltávolítást értünk, de a művelet után már “hagyományosan” is nyugodt szívvel törölhetjük a fájlt.
Számunkra elég egy egyszeri felülírás is, csupa nulla értékkel. Példaprogramunk egyszerű lesz, még külön függvényeket sem írtunk hozzá. A program a saját könyvtárában található minden fájlt felülír 0 értékekkel. Nem kérdez semmit, egyszerűen csak megteszi. Ezt azért emeljük így ki, mert akár kellemetlen perceket is szerezhetünk magunknak azzal, ha csak egyszerűen lefuttatjuk egy olyan könyvtárban, ahol más értékes adatfájlunk is van. Ezen törlés visszavonására ugyanis nincsen lehetőség!
Példánk a Windows fájlrendszerkezelő függvényeit fogja használni arra, hogy megtalálja az összes fájlt egy adott könyvtárban. Ilyenkor a meghajtó gyökérkönyvtára (“.”), az előző könyvtár hivatkozása (“..”) és persze a programunk is fellelhető lesz a listában. De ne szaladjunk ennyire előre!
A program a _WIN32_FIND_DATA adatstruktúrára épít, ami rengeteg alapvető információt képes tárolni egy fájlról. Ezt az adatszerkezetet használják fel a specifikus Windows függvények is.

typedef struct _WIN32_FIND_DATA {
 DWORD    dwFileAttributes;
 FILETIME ftCreationTime;
 FILETIME ftLastAccessTime;
 FILETIME ftLastWriteTime;
 DWORD    nFileSizeHigh;
 DWORD    nFileSizeLow;
 DWORD    dwReserved0;
 DWORD    dwReserved1;
 TCHAR    cFileName[MAX_PATH];
 TCHAR    cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;

Egy egyszerű do .. while ciklussal fogunk végighaladni a fájlokon, egyszerre mindig csak egy fájlt megkeresve. Hogy éppen hol tartunk, azt egy HANDLE típusú változó segítségével tudjuk nyomon követni.
A legeslegelső fájl a FindFirstFile függvény tudja lekérdezni.

HANDLE FindFirstFileW(
 LPCWSTR            lpFileName,
 LPWIN32_FIND_DATAW lpFindFileData
);

Amennyiben fájlnévként a “*.*” paramétert adjuk meg, akkor végiglépkedhetünk az adott könyvtárban található összes fájlon. Először azonban meg kell találnunk a legelső fájlt. Ha semmilyen fájl nincsen a könyvtárban, a “.” és “..” hivatkozásokat mint fájlokat mindenképpen megkapjuk, tehát ez a minimum. Ezek után jön a többi, hagyományos értelemeben vett fájl is, ha van. Fontos megjegyezni, hogy ezek között maga a törlőprogram is sorra kerül. Ennek a törlését természetesen felesleges megpróbálni, ezért ezt külön le kell kezelni.
Az első fál után a következőt a FindNextFile függvénnyel tudjuk lekérdezni, ami 0 értéket ad vissza, ha már nem talál több fájl. (Ez a while ciklusból történő kilépés feltétele is.)

BOOL FindNextFileA(
 HANDLE             hFindFile,
 LPWIN32_FIND_DATAA lpFindFileData
);

Az adott fájlnevet a _WIN32_FIND_DATA adatszerkezet cFileName mezője tartalmazza. Ezt át kell másolnunk a fajlnev karaktersorozatba, amit már fájlnévként használhatunk a C nyelv sztenderd fájlkezelő függvényeihez.
Egy a program elején létrehozott, 0 értékekkel feltöltött puffer tartalmával írjuk felül az adott fájl tartalmát, nagyobb fájlméret esetén, szekvenciálisan, folyamatosan kiírva a tömb tartalmát.
Mégegyszer felhívjom a figyelmet egy ilyen program körültekintő használatára. Esetleg átírhatjuk úgy is, hogy csak futtatási paraméterként megadott fájlt töröljön.

8.1 Kibervédelmi megfontolások

A biztonságos fájltörlés alapvető adatvédelmi elvárás. Egy “alaposan törölt” fájl visszaállítása már nem lehetséges semmilyen módon, csak biztonsági másolatok felhasználásával. Ilyen módon még a titkosítatlan fájlok esetében is megakadályozható a tartalmuk visszaállítása, rekonstruálása.


KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
Vissza a tartalomhoz