You are here: TUTWiki>Tietoturva/Tutkielmat>JarvenpaaT?>2006-24
Jussi Kaijalainen:

Ohjelmien takaisinmallintamisen keinot

Johdanto

Tämän tutkielman tarkoitus on käsitellä ohjelmien takaisinmallintamisen (Software Reverse Engineering) menetelmiä. Termi ohjelmien takaisinmallintaminen juontaa alkunsa paljon laajemmasta käsitteestä takaisinmallintaminen (Reverse Engineering), joka voidaan määritellä seuraavasti: kohdesysteemin analysointiprosessi, jolla pyritään tunnistamaan systeemin komponentit ja niiden yhteistoiminta sekä luomaan systeemistä esitys toisessa muodossa tai korkeammalla abstraktion tasolla [5]. Nyt kuitenkin keskitytään nimenomaan tietokoneohjelmien takaisinmallintamisen keinoihin, joilla pyritään ottamaan selkoa valmiin ohjelman tai jonkin sen osan toiminnasta. Takaisinmallintamista on myös se, kun lähdekoodista jalostetaan dokumentaatiota, vaikkakin tavallisimmin oletetaan ettei lähdekoodi ole saatavilla [3].

Tutkielmassa pyritään esittämään kattava yleiskuva ohjelmien takaisinmallintamisesta ja siihen käytettävistä keinoista. Alkuun kerrotaan hieman ohjelmien takaisinmallintamisen lähtökohdista eli vastataan kysymyksiin miksi sitä tehdään ja onko se laillista. Tämän jälkeen käydään lävitse ohjelman takaisinmallintamisen prosessi ja siinä käytettävät menetelmät yleisellä tasolla, jonka jälkeen tutustutaan menetelmiin hieman tarkemmin ja esitellään niihin tarvittavia ohjelmia. Lopuksi pohditaan hieman ohjelmien takaisinmallintamisen tulevaisuudennäkymiä ja tutkielman kehitysideoita.

Ohjelmien takaisinmallintamisen lähtökohdat

Ennen varsinaisten keinojen esittelyä on hyvä miettiä kysymystä: miksi ohjelmia halutaan takaisinmallintaa? Ohjelmien takaisinmallintamista hyödynnetään moneen erilaiseen tarkoitukseen lähtien suorastaan laittomasta kopiosuojausten purkamisesta aina tietoturvallisuutta edistävään viruskoodin toimintaperiaatteiden selvittämiseen. Yleisesti voidaankin tehdä kahtiajako tietoturva- ja ohjelmistokehityslähtökohtaisen ohjelmien takaisinmallintamisen välille [1]. Käytännön esimerkki ohjelmistokehityslähtökohtaisesta takaisinmallinnuksesta on, kun ohjelmistoalan ammattilaiset joutuvat selvittämään omien ohjelmiensa tai muualta hankittujen ohjelmakirjastojen rajapintoja ja toimintoja puutteellisen tai kokonaan puuttuvan dokumentaation takia.

Kun tutkitaan toisten kirjoittamia ohjelmia ja niiden toimintaperiaatteita, nousevat esille myös kysymykset toiminnan laillisuudesta. Takaisinmallinnusprojektin laillisuuteen vaikuttavat monet seikat kuten tekijänoikeuslaki ja patentteja sekä liikesalaisuuksia koskevat lait, jotka tietysti saattavat olla erilaiset eri maissa. Voikin olla vaikeaa tehdä eroa laillisten ja laittomien takaisinmallinnusprojektien välille. Riskialtista projektia aloitettaessa olisikin turvallisinta hankkia lakimiehen apua. Yleisesti ottaen voidaan ajatella oltavan laillisella alueella, kun projekti tähtää yhteensopivuuteen. Laittomalla alueella ollaan vastaavasti, kun projektissa pyritään kopioimaan toisten koodia tai ohjelman toimintatapoja omaan käyttöön. Raja ei kuitenkaan ole kovin selkeä. Mielenkiintoinen oikeustapaus on esimerkiksi Yhdysvalloissa Blizzard vs. BNETD -tapaus, jossa pelkkä yhteensopivuuteen tähtäävä takaisinmallinnus tuomittiin laittomaksi.

Tekijänoikeuslakeja sekä liikesalaisuuksia voidaan kuitenkin yrittää kiertää esimerkiksi Clean room design -menetelmällä, jossa käytetään kahta eri ohjelmoijaryhmää. Toinen ryhmä suorittaa takaisinmallintamisen ja tuottaa erittäin tarkan dokumentaation ohjelmasta. Tämän jälkeen toinen ryhmä luo uuden ohjelman dokumentaation pohjalta, jolloin ohjelman tekemiseen ei suoranaisesti käytetty apuna kilpailijan ohjelmaa. Tämä menettely ei kuitenkaan kierrä patentteja. [6]

Ohjelmien takaisinmallintamisen prosessi

Kun todetaan tarve jonkin ohjelman tai ohjelmakomponentin takaisinmallintamiseen, on otettava huomioon muutamia seikkoja, jotta osataan valita oikea lähestymistapa ja työkalut. Pitää ottaa huomioon mm. ohjelman alusta tai siis käyttöjärjestelmä, jolle ohjelma on tehty sekä millä ohjelmointikielellä ohjelma on toteutettu. On myös tiedettävä, millaista tietoa ohjelmasta halutaan selvittää, jotta osataan varautua oikealla määrällä resursseja. Projektin koolla on suuri ero riippuen siitä, halutaanko vain pääpiirteittäin ymmärrys ohjelman toiminnasta vai täydellinen dokumentaatio. Tietysti myös ohjelman koko ja monimutkaisuus vaikuttavat takaisinmallinnukseen kuluvaan aikaan. Kun nämä asiat ovat selvillä, voidaan valita oikea lähestymistapa ja käydä varsinaisen ohjelmabinäärin kimppuun.

Aluksi ohjelmabinääri pyritään muuttamaan konekielestä tai tavukoodista joksikin muuksi, ymmärrettävämmäksi ylemmän tason kieleksi. Tämän jälkeen voidaan alkaa analysoida saatua koodia. Yleisesti ottaen on kaksi erilaista tapaa lähteä toteuttamaan varsinaista koodin analysointia: käymättömän koodin analysointi (offline analysis) ja ajonaikainen koodin analyysi (live analysis) [1]. Ohjelman koodin analysointi ja siinä samalla tapahtuva ymmärryksen hankkiminen ohjelman toiminnasta onkin ohjelman takaisinmallintamisen ydin. Takaisinmallinnusprojektin tavoitteista riippuen tästä ymmärryksestä voidaan jalostaa esimerkiksi dokumentaatiota tai sitä voidaan käyttää vaikkapa ohjelman kopiosuojauksen murtamiseen.

Käymättömän koodin analyysi

Käymättömän koodin analysointi on takaisinmallintamisen lähestymistapa, jossa ohjelma muunnetaan takaisinkääntimellä (decompiler) tai yleisemmin purkajalla (disassembler) ihmiselle mieluisampaan muotoon. Tämän jälkeen purkajan tai takaisinkääntimen antamaa tulostetta tai sen osaa tutkitaan manuaalisesti.

Tässä lähestymistavassa näkee hyvin kokonaisuuden ja pystyy keskittymään juuri siihen kohtaan, mikä kiinnostaa. Haittana on se, että koodia on ymmärrettävä paremmin, jotta sieltä löytää halutut kohdat, koska koodilohkon toimintaa ei näe suoraan. Monesti onkin kätevää, etsiä ajonaikaisella koodin analyysillä kiinnostava kohta ohjelmasta ja sitten tutkia sen kohdan koodia tarkemmin.

Ajonaikainen koodin analyysi

Ajonaikaisessa koodin analyysissä ensimmäinen vaihe on sama kuin käymättömän koodin analyysissä, eli ohjelma muunnetaan aluksi ihmisen ymmärtämään muotoon. Mutta kuten jo nimestäkin voidaan päätellä, ei koodia pelkästään lueta, vaan sitä ajetaan eräänlaisen tutkintaohjelman (debugger) sisällä, jolloin nähdään miten koodi käyttäytyy ajon aikana. Debuggerilla voidaan ajaa ohjelmaa esimerkiksi käsky kerrallaan, jolloin ihminen pystyy seuraamaan tarkasti, mitä ohjelman sisällä tapahtuu.

Tämä lähestymistapa on yleensä ottaen helpompi aloittelevalle takaisinmallintajalle, koska se tarjoaa paljon enemmän informaatiota ohjelman toiminnasta: nähdään yksittäisten muuttujien arvot ja miten ne muuttuvat ohjelman käydessä sekä nähdään ohjelman tekemät luku- ja kirjoitusoperaatiot muistiin. Tämän menetelmän avulla saadaan melko helposti jonkinlainen yleiskuva ohjelman toiminnasta ja löydetään mielenkiintoiset kohdat koodista. Ongelmana debuggereilla on, että ne väistämättä muuttavat tutkittavan ohjelman sisäisiä ajoituksia, minkä vuoksi ajoaikaisten ongelmien selvittäminen monimutkaisissa ympäristöissä on hyvin vaikeaa [2].

Takaisinmallintamista vaikeuttavia tekijöitä

Takaisinmallintajan työtä vaikeuttavat seikat voidaan lajitella syntyperänsä mukaan kahteen eri ryhmään: ohjelman käännösprosessin aiheuttamat ja tarkoituksella aiheutetut. Ohjelmaa korkean tason kielestä konekieleksi käännettäessä häviää informaatiota, koodin rakenne muuttuu ja sen ymmärtäminen vaikeutuu. Tarkoituksella tehtyä sellaista koodin muokkaamista, joka vaikeuttaa sen ymmärtämistä, kutsutaan koodin hämäännyttämiseksi (code obfuscation). Ohjelma voidaan myös kryptata ja sitten purkaa sitä mukaan, kun sitä ajetaan. Tämä tapa ei ole kovin tehokas, koska ohjelman on kuitenkin sisällytettävä kryptaamattomana moduuli, jolla kryptauksen purku suoritetaan, että sitä ylipäänsä voidaan ajaa.

Ohjelman käännösprosessi

Ohjelman käännösprosessissa kääntäjä muuntaa korkean tason ohjelmointikielisen lähdekoodin alemman tason ohjeiksi, joka voi olla joko tavanomaista alustariippuvaista konekieltä tai sitten erityistä alustariippumatonta tavukoodia riippuen käytetystä korkean tason ohjelmointikielestä. Alustariippumattomia kieliä ovat esimerkiksi Java ja Microsoft .NET -yhteensopivat kielet. Esimerkiksi suositut C ja Cpp ovat taas alustariippuvaisia.

Suurimmat ongelmat kääntäjän tuottamaa koodia tulkittaessa ovat nykyaikaisten kääntäjien suorittamat optimoinnit. Kääntäjät käyttävät monenlaisia tekniikoita pienentääkseen koodin kokoa ja parantaakseen suorituksen joutuisuutta. Koodin rakenne saattaa muuttua hyvinkin radikaalisti: esimerkiksi silmukat voidaan purkaa osittain tai jopa kokonaan. Tästä seuraa, että kääntäjän tuottama koodi on usein kaukana helposti ymmärrettävästä. [1]

Koodin hämäännyttäminen

Koodin hämäännyttäminen (code obfuscation) tarkoittaa ohjelmakoodille tehtävää muunnosta, jolla pyritään tekemään sen ymmärtäminen vaikeammaksi siten, että sen toiminnallisuus kuitenkin säilyy [4]. Syitä koodin hämäännyttämiseen on monia. Näistä yleisimpiä ovat halu suojata omaa koodia kopioinnilta ja parantaa ohjelman tietoturvaa "security by obscurity" -periaatteella. Tämä tulee vastaan varsinkin käytettäessä alustariippumattomia ohjelmointikieliä kuten Java tai .NET, joissa säilyy paljon korkean tason informaatiota. Koodin hämäännyttämistä käyttävät myös virusten kirjoittajat vaikeuttaakseen viruksen ymmärtämistä ja sen tunnistamista. Tähän tehtävään löytyy monia valmiita ohjelmia, ja esimerkiksi alempana esiteltävä JODE osaa takaisinkääntämisen lisäksi suorittaa erinäisiä hämäännyttämistoimenpiteitä.

Varsinainen koodin hämäännyttäminen voidaan jakaa neljään osa-alueeseen, jotka kaikki pyrkivät vaikeuttamaan koodin tulkintaa mutta kukin hieman eri tavalla. [4]

  1. Lähde- tai konekoodin rakenteellinen hämäännyttäminen pyrkii muokkaamaan lähdekoodia tai konekoodia sisältävää tiedostoa siten, että koodin toiminnallisuus säilyy, mutta sen luettavuus heikkenee.
  2. Tiedon hämäännyttäminen on menetelmä, jolla pyritään hämäännyttämään ohjelman sisältämää tietoa ja tietorakenteita. Tähän voidaan päästä esimerkiksi jakamalla yksi muuttuja kahdeksi tai koodaamalla merkkijonot (kuten MyDoom -viruksessa rot13-algoritmilla).
  3. Ohjelman kulun hämäännyttäminen on sellainen menetelmä, jossa ohjelman kulkua muutetaan vaikeaselkoisemmaksi siten, että kuitenkin sen logiikka säilyy. Tähän päästään lisäämällä koodiin turhia ehtolauseita ja muita mystisiä rakenteita.
  4. Ehkäisevä hämäännyttäminen pyrkii suojaamaan koodia erityisesti takaisinmallinnusta vastaan. Metatiedon muuttaminen hölynpölyksi ja funktioiden sekä muuttujien nimien muuttaminen mitäänsanomattomiksi ovat ehkäisevää hämäännyttämistä.

Anti-debugging

Ohjelmakoodissa on mahdollista erinäisillä tekniikoilla haitata tutkintaohjelmien (debbugerit, joista tarkemmin jäljempänä) käyttöä. Debuggeriohjelmilla on oma vaikutuksensa järjestelmän tilaan ja toimintaan: tutkittavasta ohjelmasta käsin on mahdollista havaita pieniä eroavaisuuksia esimerkiksi muistinkulutuksessa, käyttöjärjestelmä- ja prosessitiedoissa, suorituksen viiveissä. Siten voi olla mahdollista havaita ohjelmassa, milloin sitä ajetaan debuggerin kautta. Tätä kautta voidaan varautua takaisinmallinnusta vastaan, joskin samalla vaikeutetaan vianetsintää ongelmatilanteissa. [8]

Ohjelman muuntaminen luettavaan muotoon

Valmis, käännetty tietokoneohjelma on oikeastaan vain iso kasa konekielisiä käskyjä, joiden lukeminen on ihmiselle erittäin hankalaa, ellei mahdotonta. Siksi onkin olemassa lukuisia ohjelmia, jotka yrittävät muuntaa ohjelman konekieliset käskyt ihmisystävällisempään, luettavampaan muotoon. Tämä muunnos tehdään yleisimmin assembly-kieleksi, mutta joissain tapauksissa voidaan yrittää muuntaa ohjelma ylemmän asteen ohjelmointikieleksi. Se, voidaanko ohjelma saattaa takaisin ylemmän asteen kieleksi tai lähelle sitä, riippuu ohjelman tekemiseen käytetystä kielestä ja tarkemmin ottaen siitä, miten paljon ylemmän tason informaatiota kielen kääntäjä karsii.

Ohjelman purku assemblyksi

Assembly-kieltä tuottavia purkuohjelmia kutsutaan nimellä disassembler. Assembly on matalan tason ohjelmointikieli hyvin lähellä konekieltä, jonka lukeminen kuitenkin onnistuu jo ihmiseltä. Usein ensimmäinen konkreettinen askel ohjelmia takaisinmallinnettaessa on ohjelman purkaminen assemblyksi. Purkaja lukee ohjelmabinääristä konekieliset käskyt ja kirjoittaa niitä vastaavan assembly-kielisen dokumentin. Seuraavassa esitellään muutama purkuohjelma.

Purkutyökalut eli disassemblerit

  • IDA: IDA eli Interactive Disassembler on DataRescue-nimisen yrityksen valmistama maksullinen disassembleri, joka tukee useampaa eri prosessoriarkkitehtuuria. IDA on ominaisuuksiltaan erittäin monipuolinen ja siitä saa ainakin tällä hetkellä (14.12.2006) ladattua kokeiluversion ilmaiseksi.

  • objdump: Objdump on Linuxin GNU Binutils -binäärityökalukokoelmaan kuuluva ohjelma, joka kykenee näyttämään erinäisiä tietoja binääritiedostoista mukaan lukien assembly-listauksen [7].

Ohjelman takaisinkääntäminen

Ohjelman takaisinkääntäminen (decompilation) on prosessi, jossa pyritään tekemään kääntäjän tekemän työn vastakkainen operaatio. Tämän operaation onnistuminen on riippuvainen ohjelmatiedoston sisältämästä tiedon määrästä, joka puolestaan riippuu käytetystä ohjelmointikielestä eli siitä sisältääkö ohjelmatiedosto konekieltä vai tavukoodia. Javalla tai Microsoftin .NET-yhteensopivalla ohjelmointikielellä toteutettu ohjelmatiedosto sisältää paljon enemmän korkean tason informaatiota, kuin vaikkapa C- tai Cpp-kielellä luotu vastaava ohjelmatiedosto. Konekielisille ohjelmille tarkoitettuja takaisinkääntimiä on vähän, ne ovat rajoittuneita ja niiden antama ulosanti on melko kehnoa.

Takaisinkääntimet eli decompilerit

  • Boomerang: Boomerang on avoimen lähdekoodin ohjelma, joka pyrkii muuntamaan konekielistä ohjelmaa C-kieleksi. Boomerang on vapaasti ladattavissa.

  • JODE: JODE on Javalla kirjoitettu avoimen lähdekoodin ohjelma, joka toimii samalla sekä takaisinkääntäjänä että optimoijana Java-ohjelmille. JODE on vapaasti saatavilla.

  • Salamander: Salamander on kaupallinen takaisinkäännin Microsoft .NET -yhteensopiville kielille. Ohjelman seittisivut antavat takaisinkääntää kohtuullisen kokoisia .NET -ohjelmia.

Ohjelman toiminnan analysointi

Varsinainen ohjelman toimintojen analysointi tehdään yleensä sen jälkeen, kun ohjelmabinääri on muunnettu käytännöllisempään muotoon. Kuitenkin joissakin tapauksissa ohjelman toiminnan tutkiminen voidaan joutua toteuttamaan toisin, kun esimerkiksi varsinaiseen ohjelmatiedostoon ei päästä käsiksi tai siihen ei ole oikeuksia. Tällöin on tyydyttävä tutkimaan sen toimintaa ja sitä kautta yritettävä saada ohjelmasta selkoa. Nyt kuitenkin keskitytään sellaiseen analysointiin, jossa päästään käsiksi ohjelmatiedostoon.

Takaisinmallintajan lottovoitto on tietenkin, jos ohjelma on kirjoitettu Javalla tai jollain .NET-kielellä, jolloin päästään yleensä tutkimaan melko miellyttävää korkean tason lähdekoodia, jonka ymmärtämiseen ei yleensä tarvita suurempia taikatemppuja. Ohjelman analysointi tässä tapauksessa on takaisinkääntimen antaman lähdekoodin tarkkaa läpilukemista ja dokumentaation synnyttämistä, jossa kuitenkin voidaan käyttää apuna debuggeria. Lottovoitto voi mennä kuitenkin ohi suun, jos koodi on hämäännytetty tehokkaasti, jolloin korkean tason lähdekoodi voi olla pahimmassa tapauksessa lähes lukukelvotonta. Hämäännytetyn koodin ymmärtämiseen debuggeri on lähes välttämätön.

Jos ohjelmasta ei voida saada irti korkean tason koodia, on tyydyttävä ottamaan kaikki irti assembly-listauksesta. Tässä vaiheessa yleensä otetaan käyttöön ajonaikainen analysointi, eli käynnistetään oma lempidebuggeri ja aletaan ajaa koodia. Useissa debuggereissa tulee mukana purkaja, jolloin ei erikseen tarvita ohjelmaa binäärin purkamiseen assemblyksi. Jos kuitenkaan debuggeria ei ole saatavilla tai sitä ei haluta käyttää, voidaan tietysti assembly-listausta käydä analysoimaan omin avuin jollain tekstieditorilla tai vaikka ihan paperilla. Kun kyseessä on vähänkään suurempi ohjelma, voi halutun kohdan löytäminen ilman debuggeria muodostua isoksikin ongelmaksi. Jo pelkkä perinteinen hello world -ohjelma tuottaa yllättävän paljon konekieltä ja vastaavasti assemblyä, jonka tulkitseminen ja läpikäyminen ei tämän tutkielman puitteissa ole järkevää.

Debuggerit

Debuggeri on ohjelma, jota käytetään testaamaan ja debuggaamaan muita tietokoneohjelmia [2]. Debuggerit tarjoavat ominaisuuksia, joista on hyötyä niin ohjelmistokehittäjille kuin takaisinmallintajille. Tällaisia ominaisuuksia ovat muun muassa mahdollisuus ajaa ohjelmaa askel kerrallaan, hyppääminen suorituksen johonkin kohtaan ja ohjelman pysäyttäminen haluttuun kohtaan. Debuggerit antavat myös usein tietoa ohjelman muistin ja rekistereiden tilasta sekä saattavat tarjota mahdollisuuden muuttaa näissä sijaitsevia arvoja. Kun takaisinmallintaja pystyy kontrolloimaan ohjelman muuttujien arvoja ajon aikana, pystyy hän käytännössä ohjailemaan ohjelmaa ja tämä helpottaa paljon ohjelman salojen selvittelyä. Seuraavassa muutama yleisin debuggeri.

  • OllyDbg: Selkeä Windows-pohjainen debuggeri, joka on painottunut binäärikoodin analysointiin. Sisältää oman disassemblerin. OllyDbg on sharewarea, mutta sitä saa käyttää vapaasti.

  • GDB: GDB eli GNU Project Debugger on suosituimmilla Unix- ja Windows-versioilla toimiva debuggeri. Tuettuja ohjelmointikieliä ovat mm. Ada, C, Cpp ja Pascal.

  • SoftICE: SoftICE on Compuware:n tarjoama suosittu Windows-debuggeri, joka toimii käyttöjärjestelmän alla mahdollistaen myös itse käyttöjärjestelmän analysoinnin. SoftICE on maksullinen.

Ohjelmien takaisinmallintamisen tulevaisuudesta ja kehitysideoista

Ohjelmien takaisinmallintaminen on monitahoinen työkalu, jonka käytettävyys on helpottunut lähiaikoina varsinkin takaisinkääntimien kehittyessä. Sen kehitys onkin luonut tarpeen koodin hämäännyttämiselle, jota käytetään varsinkin helposti takaisinkäännettävien ohjelmien kohdalla. Koodin hämäännyttämisen tehokkuudesta voidaan taas olla montaa mieltä. On kuitenkin mielenkiintoista nähdä kummat vievät voiton, hämäännyttäjät vai takaisinkääntäjät, mutta tässä tutkielmassa sitä ei enempää käsitellä - ainakaan toistaiseksi.

Javan ja .NET-kielten tapauksissa takaisinkääntäminen onnistuu nykyään melko hyvin. Mielenkiintoista olisikin ollut itse testata kuinka hyvin konekieltä pystytään nykyään takaisinkääntämään. Tässä kuitenkin Boomerangin sivuilta esimerkki siitä, mitä Boomerang osaa tällä hetkellä.

Ohjelmien takaisimallintaminen tulevaisuudessa tulee olemaan luultavasti yhä haasteellisempaa ohjelmistojen kokojen ja monimutkaisuuden kasvaessa. Toisaalta kuitenkin takaisinmallintamisessa käytettävät ohjelmat ja menetelmät kehittynevät myös ajan myötä. Se, voidaanko konekielistä ohjelmaa koskaan esittää täysin ylemmän tason ohjelmointikielellä, jää vielä nähtäväksi.

Lähteet

-- HannuSimonen? - 05 Oct 2009
Print version |  PDF  | History: r8 < r7 < r6 < r5 | 
Topic revision: r8 - 17 Nov 2009 - 08:16:42 - HannuSimonen?
 

TUTWiki

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TUTWiki? Send feedback