DLL-kirjastoista ja niiden lataamisesta - jatkotarinan osa I: "Hyökkäys"

DLL-kirjastoihin ja niiden lataamiseen liittyvät hyökkäykset näyttävät olevan päivän kuuma aihe - ja kysymyksiä aiheen tiimoilta satelee myös asiakkailta. Tarkastellaanpa siis hieman tarkemmin, mistä tässä kaikessa oikeastaan on kysymys. Tällaiset hyökkäykset tunnetaan siis nimityksellä DLL-lataushyökkäys (DLL Preloading Attack). Nämä hyökkäykset eivät ole mikään uusi asia, eivätkö ne myöskään ole mitenkään rajoittuneita vain Windows-maailmaan - tämän tyyppinen hyökkäys koskee potentiaalisesti mitä tahansa käyttöjärjestelmää, jossa sovellus voi ajon aikana ladata suoritettavia kirjastoja. Kuten David LeBlanc toteaa helmikuulta 2008 olevassa blogimerkinnässään:

"A DLL preloading attack is something that can get you on a lot of different platforms. One of the first variants I heard about was in an ancient telnet daemon on certain versions of UNIX where you could specify environment variables, and one of the things you could specify was where to look for libraries. Obviously, if you could get the telnet daemon running as root to load your library, it was then your system."

Mitä tässä oikein on taustalla?
Ongelman ytimessä on siis tilanne, jossa vihamielinen hyökkääjä pääsee kontrolloimaan kirjastoa, jonka sovellus lataa ja suorittaa. Tällöin luonnollisesti sovelluksessa suoritetaan hyökkääjän haluamaa, ladatun DLL-kirjaston kautta sovellukselle syötettyä ohjelmakoodia. Ja ohjelmakoodi luonnollisesti suoritetaan niiden käyttöoikeuksien turvin, joilla sovellus normaalistikin pyörii. Tässäkin tilanteessa siis kirjautuminen sisään tavallisena käyttäjänä, ja pääkäyttäjän tunnuksen ja käyttöoikeuksien käyttäminen vain erikoistarpeisiin, pienentää riskiä.

Vaikkakin vastaavanlainen tilanne on siis mahdollinen myös muualla kuin Windowsissa, keskityn nyt kuitenkin Windowsiin ja nykytilanteeseen. Miten Windowsissa siis olisi mahdollista, että vihamielinen hyökkääjä pääsisi konttrolloimaan, minkä DLL-kirjaston sovellus lataa? Jos sovelluksessa ladataan DLL-kirjasto käyttämällä seuraavanlaista komentoa:

HMODULE handle = LoadLibrary("c:\\windows\\system32\\schannel.dll"); (LoadLibrary tai LoadLibraryEx on Windowsin funktiokutsu, jolla DLL-kirjasto ladataan sovellukseen)

Tässä tapauksessa schannel.dll-kirjastoa haetaan polusta c:\windows\system32, ja ladataan sovelluksen muistiavaruuteen, jos tiedosto sieltä löytyy. Ainoa tapa, jolla hyökkääjä pääsisi kontrolloimaan ladattavaa kirjastoa on, jos hän pääsisi korvaamaan c:\windows\system32-hakemistossa olevan schannel.dll-kirjaston omalla versiollaan - ja siinä tapauksessa toki hyökkääjällä olisi paljon muitakin mahdollisuuksia pahan tekoon, ja koneen käyttäjällä ja ylläpitäjällä paljon suurempiakin huolenaiheita kuin DLL-kirjastot. Tämä ei siis ole kovinkaan todennäköistä.

Jos sen sijaan sovelluksessa ladataan kirjasto ILMAN, että määritetään sen tarkka polku, esim. seuraavanlaisella komennolla: 

HMODULE handle = LoadLibrary("schannel.dll");

Tässä tapauksessa Windows lähtee sovelluksen puolesta hakemaan schannel.dll-nimistä DLL-tiedostoa.  Haku tapahtuu seuraavanlaisen hakujärjestyksen mukaan:

  1. Ensimmäisenä haetaan DLL-kirjastoa, tässä tapauksessa schannel.dll-nimistä kirjastoa hakemistosta, josta sovellus käynnistettiin.
  2. Sen jälkeen kirjastoa haetaan Windowsin system-hakemistosta (esim. c:\windows\system32).
  3. Sitten 16-bittisestä system-hakemistosta (esim. c:\windows\system).
  4. Ja sen jälkeen windows-hakemistosta (esim. c:\windows).
  5. Sitten nykyisestä työhakemistosta.
  6. Ja viimeisenä kaikista PATH-ympäristömuuttujassa määritetyistä hakemistoista.

Entäs sitten? Suurin osa noista on sellaisia, joihin hyökkäjä ei pääse käsiksi (tai jos pääseekin, niin hän saa tehtyä paljon muutakin tuhoa, kts. edellä). Joo, kyllä, mutta listan kohta 5 on se vaaranpaikka: Nykyinen työhakemisto. David LeBlanc jatkaa aiemmin mainitussa blogiartikkelissaan:

"A difference between UNIX-ish systems and systems based on DOS is that the current directory "." is not on the search path for UNIX-ish systems, and it is for DOS systems, which didn't have different users, so there was no need to worry about some of these things."

Esimerkki!
Miten hyökkääjä pääsee kontrolloimaan sovelluksen nykyistä työhakemistoa? Helppoa, hyökkääjä asettaa tiedoston TIEDOSTO.SOV, joka avataan jossakin tietyssä sovelluksessa SOVELLUS, verkossa olevaan kansioon Z:\KANSIO ja sen jälkeen houkuttelee käyttäjän avaamaan ko. tiedoston esimerkiksi kaksoisnapsauttamalla sitä. Tiedosto ladataan käyttäjän koneelle, Windows tutkii SOV-tiedostotunnisteen määrityksistä, että kyseisen tyyppinen tiedosto avataan sovelluksessa SOVELLUS, käynnistää ko. sovelluksen ja välittää sille tiedoston TIEDOSTO.SOV, jonka sovellus luonnollisesti avaa. Tämän jälkeen sovelluksen SOVELLUS nykyinen työhakemisto on Z:\KANSIO.

No hyvä, entäpä sitten? Kuvittelepa tilanne, jossa hyökkääjä tietää, että SOVELLUS lataa DLL-kirjaston KIRJASTO.DLL, ja tietää lisäksi, että ko. kirjaston lataavassa LoadLibrary-kutsussa ei ole määritetty kirjaston tarkkaa polkua (eikä se sijaitse lisäksi missään heljästä ensimmäisestä hakujärjestyksessä olevasta hakemistosta. Nyt hyökkääjä voi luoda oman versionsa DLL-kirjastosta, sijoittaa sinne haluamaansa ohjelmakoodia, nimetä sen nimellä KIRJASTO.DLL ja sijoittaa sen samaan kansioon tiedoston TIEDOSTO.SOV kanssa, ja sen jälkeen houkutella käyttäjä avaamaan TIEDOSTO.SOV-tiedoston. SOVELLUS käynnistetään, se lähtee lataamaan kirjastoa KIRJASTO.DLL, joka löytyy ensimmäisenä nykyisestä työhakemistosta, se ladataan ja suoritetaan - mutta oikean kirjaston sijasta suoritetaan hyökkääjän haluama ohjelmakoodi. Ja tämän jälkeen sananlaskun sanoja mukaillen Paavo on enosi...  

Edellä mainittu siis pikkuisen yksinkertaistettuna, mutta periaate tulnee selväksi. Tämän jälkeen tietysti hyökkääjä pitää lukijaa hyppysissään ja kaikkia kiinnostaa, miten suojatua. Ja siitä jatketaan hetkisen kuluttua jatkotarinan osassa II.