.Net platformos tarpinio kodo užslėpėjai – bakalaurinis darbas

1    Įvadas

.Net platforma pateikė daug naujovių. Ši platforma pasiūlė suderinamumą tarp atskirų programavimo kalbų, palengvino sąsajas su duomenų bazėm, taip pat pagerino programų saugumą. Naudojant šios platformos teikiamas galimybes buvo palengvintas programuotojų darbas ir t.t. Vienas iš pagrindinių šios platformos minusų yra jos tarpinio kodo paprastumas. Tai kelia nerimą ne vienai kompanijai, nes įdėtos investicijos kuriant naujus algoritmus ir procesus, gali būti lengviau pasisavintos kitų šalių.
Išeitį pasiūlo kodo užslėpėjų gamintojai, kurių įrankių dėka galima užslėpti programos logiką ir labiau sumaišyti tarpinį kodą taip apsunkinant asmenų, kurie bando išnagrinėti programos veikimą pagal jos tarpinį kodą, darbą.

2    Darbo tikslai

•    Ištirti tarpinio kodo trūkumus saugumo srityje.
•    Ištirti tarpinio kodo trūkumų priežastis.
•    Išanalizuoti kodo užslėpėjų teikiamą nauda.
•    Susipažinti su kodo užslėpėjų funkcijomis ir kaip jie tai realizuoja.
3    .Net platformos tarpinio kodo trūkumai saugumo srityje.

.Net platformai skirtos programos vykdomos virtualios mašinos pagalba, todėl tos programos yra sukompiliuojamos į tarpinį kodą. Tarpiniame kode yra saugomi ne tik programos logika, bet ir jos meta-duomenys, kurie apibūdina programos paketo modulius ir jų atvirai prieinamus bei privačius metodus.
3.1    Tarpinio kodo skaitomumas.

.Net platformos tarpinis kodas yra lengvai skaitomas ir suprantamas. Naudojant Microsoft įmonės sukurtą įrankį ildasm galima tarpinį kodą perversti į tarpinę kalbą msil (Microsoft intermediate language). Iš pirmo žvilgsnio atrodytų, jog tai neturėtų sukelti pakankamai daug problemų, nes ir seniau buvo panašių įrankių, kurių pagalba buvo galima perversti mašininį kodą į žemojo lygio asembler kalbą. Problematiška yra tai, jog .Net platformoje moduliai patys save apsirašo meta-duomenų pagalba ir visa informacija, kuri turėtų būti užslėpta [Fee03], yra lengvai prieinama kiekvienam. Panagrinėkime vieną pavyzdį, kuriuo bus demonstruojama tarpinio kodo skaitomumas. Iš pradžių parašykime prgramą (1 pavyzdys) kuri nieko realiai nedaro, bet jos metodai yra informatyvūs. Sukompiliavus šią programą įrankio ildasm pagalba perverčiame pirmosios programos tarpinį kodą į msil kalbą. Kaip matome šis įrankis mums pateikia visus programos modulyje esančiu metodus (2 pavyzdys).

1 pavyzdys: „Pirmoji bandomoji programa“.
using System;

namespace pirmas
{
    class Class1
    {
        private static void nuskaitytiDuomenis()
        {
            // nuskaitomi duomenys is failo
        }

        private static void sugrupuotiDuomenis()
        {
            // atliekamas duomenu grupavimas
        }

        private static void grupiuVidurkiai()
        {
            //atliekamas skaiciu vidurkis
        }
       
        private static void issaugotiDuomenis()
        {
            //issaugoti duomenis faile
        }

        public static void atliktiSkaiciavimus()
        {
            int[] vidurkiai;
            string pranesimas = “Klaidingi duomenys”;

            nuskaitytiDuomenis();
            sugrupuotiDuomenis();
            grupiuVidurkiai();
            issaugotiDuomenis();
        }

        [STAThread]
        static void Main(string[] args)
        {
            atliktiSkaiciavimus();
        }
    }
}

2 pavyzdys: „pradinė ildasm pateikiama informacija“.
 

3 pavyzdys: „atvirai prieinamas metodas ‚atliktiSkaičiavimus‘ “
 

Mums įdomus yra atvirai prieinamas metodas „atliktiSkaiciavimus“, todėl pasidomėję kokie veiksmai yra atliekami jo viduje (3 pavyzdys), pastebime ne tik iškviečiamus privačius metodus „nuskaitytiDuomenis“, „sugrupuotiDuomenis“, „grupiuVidurkiai“ ir „issaugotiDuomenis“, bet ir lokalių laukų pavadinimus bei jų tipus ir pradines reikšmes. Kadangi privačių metodų pavadinimai yra informatyvūs tai visai nesunku išsiaiškinti metodo „atliktiSkaiciavimus“ logiką.
Toks tarpinio kodo skaitomumas palengvina darbą tiems, kurie bando išsiaiškinti programos veikimą ir bandyti jį pritaikyti savo reikmėms. Žinoma nuo įsilaužimo į programą apsaugo .Net saugumo gijos, kurios kontroliuoja kas gali vykdyti programą ir kas gali kviesti jos modulius.
3.2    Intelektuali nuosavybė.

Nors nuo trečių asmenų įsilaužimo apsaugo .Net platformos saugumo gijos, bet vis tiek išlieka viena saugumo problema – privačios intelektualinės nuosavybės saugumo problema. Privati intelektualinė nuosavybė šiuo atveju yra sukurta programos logika. Kaip jau pastebėjome tarpinis kodas gali suteikti labai daug informacijos ir taip padėti trečios šalies asmenims pasisavinti tai kas yra sukurta. Tam, kad būtų lengviau suvokti iškylančią grėsmę panagrinėkime keletą pavyzdžių.
4 Pavyzdys. „Programos modulių pasisavinimas“.
 
4 pavyzdyje trečios šalies asmuo, nusipirkęs vieną programos P kopiją, ją išanalizuoja. Suradęs jam reikiamus programos modulius (kodo dalis,  klasės) juos gali nukopijuoti ir pritaikyti savo programos kūrimui. Viso to dėka šis asmuo, sutaupęs laiko naujų modulių kūrimui, parduoda savo programą Z kitiems žmonėms.

5 Pavyzdys. „Informacijos iškirpimas“.
 
5 pavyzdyje trečios šalies asmuo, nusipirkęs vieną filmo įrašą, kuriame yra ir kodo, atsakingo už pinigų nuskaitymą iš vartotojo už įrašo peržiūrėjimą.  Trečios šalies asmuo gali atskirti filmo įrašą nuo kodo, arba jei yra įmanoma pakeisti mokėjimo sumą pardavėjui.
Iškyla uždavinys – apsaugoti savo programos kodą nuo lengvo skaitomumo.

4    .Net platformos tarpinio kodo užslėpėjai.

Galimas nepatogus apsaugojimo variantas – programuojant metodų ir laukų vardus parinkti tokius, kad jie patys savaime teiktų kuo mažiau informacijos apie save. Toks variantas tiktų tik tuo atveju jei programa yra pakankamai mažos apimties ir visus laukus bei metodus galima lengvai atsiminti. Nes pabandykime įsivaizduoti kaip atrodytų klaidų ieškojimo procesas, jei metodai naudoja kitus metodus ir pagal jų pavadinimus sunku prisiminti jų paskirtį [Med03].
Tuo tikslu buvo sukurti įvairūs kodo užslėpėjai, kurių paskirtis programiškai paslėpti kiek įmanoma daugiau intelektualios informacijos. Naudojant užslėpėjus rašant programos kodą galima naudoti informatyvius metodus ir jų laukus, o pabaigoje juos pakeisti į sunkiai suprantamus ir klaidinančius jų pavadinimus ir tokiu būdu klaidinti trečių šalių asmenis, bandančius išsiaiškinti programos logiką.
4.1    Matematinis kodo užslėpėjų pagrindimas.

Pagrindinės tarpinio kodo užslėpimo pagrindimas remiasi teorijomis, kurios yra paremtos matematiniu pagrindu. Tarkime, kad kompiuterio architektūra paremta instrukcijų seka ir kontekstu – kintamųjų aibės, nusakančios galimas kompiuterio būsenas, vektorius. Taigi instrukcija galime apibrėžti kaip vektorinę funkciją [Wro02]:

 

Čia S – visų galimų kompiuterio būsenų aibė.
O programą galime apibrėžti kaip instrukcijų seką:

 

Nagrinėsime tik statines programas, kurių vykdymo laikas yra baigtinis. Pagal nutylėjimą instrukcijos yra vykdomos paeiliui nuo pirmos iki paskutinės, bet galimi atvejai kada tam tikros instrukcijos gali pakeisti vykdomų instrukcijų seką.
Programos gali naudoti didesne kompiuterio konteksto dalį, kurios rezultatas didelis kiekis įvedamos ir išvedamos informacijos. Bet dažniausiai vartotojui yra reikalinga tik nedidelė tos informacijos dalis. Vartotojui reikšmingos informacijos nustatymui apibrėžiamas konteksto vartojimo vektorius [Wro02].

1 Apibrėžimas. Konteksto c vartojimo vektorius v yra toks vektorius kurio reikšmių aibė yra {0,1}.  Vektoriai c ir v tos pačios dimensijos ir vektoriaus v reikšmės apibrėžia tokią prasmę:
•    1 – atitinkamo konteksto kintamojo reikšmė yra reikšminga vartotojui.
•    0 – konteksto kintamojo reikšmė nėra reikšminga vartotojui.

Vietoj to kad analizuot programas, kaip vieno galimo konteksto keitimą į kitą :

 

Naudingiau, būtų jas analizuoti kaip konteksto vartojimo transformaciją:

 

Nes mus labiau domina vartotojui reikšminga informacijos transformacija.

2 Apibrėžimas. Programa   yra ekvivalenti programai   įvedimo    ir išvedimo   vartojimo kontekstuose, jei:

 
arba galime visa tai pavaizduoti ir taip:
 

1 ir 2 apibrėžimai suteikia pagrindą kodo slėpimo transformacijai apibrėžti. Bet prieš tai dar reiktų apibrėžti programų apjungimą (padalinimą). Šioms operacijoms naudosime simbolį   , kuriuo vaizduosime programų apjungimą (padalinimą). Eilutė   reiškia, kad programa   bet kuriame taške yra padalinta į   ir   programas.

3 Apibrėžimas. Užlėpimo transformacija   yra programos   keitimas į programą  , taip kad    įvedimo    ir išvedimo   vartojimo kontekstuose:

  yra  slėpimo transformacija 

Suskaidant užslėptą programą į dvi dalis   ir   įgalina surišti užslėptas reversines operacijas su užslėpimo transformacija.

1 Teorema. Jei   yra užslėpimo transformacija, tai   gali būti:
•    Programos, ekvivalenčios programos   instrukcijoms
•    Reversinės operacijos
•    Instrukcijos, kurios pakeičia tik tą konteksto dalį, kurio programa   nenauadoja

2 Teorema. Kiekvienai programai  , bet kurioje vietoje padalintai į dvi programas  , kurios yra užslėptos dviem skirtingomis užslėpimo transformacijomis   ir  ,   ir   yra užslėpimo transformacija.

Remiantis šiomis dviem teoremomis, kurios yra įrodytos „Gregory Wroblewski, ‚General Method of Program Code Obfuscation‘, PhS Dissertation, Wroclaw University of Technology, 2002“ darbe, galima kurti kodo užslėpimo algoritmus [Wro02].

4.2    Pagrindiniai kodo užslėpėjų uždaviniai.

Remiantis 1 ir 2 teoremomis turint aibę užslėpimo transformacijų   ir programą   galime gauti ekvivalenčią programą  , kurios kodas būtų užslėptas. Užslepiant programos kodą yra sprendžiami tokie pagrindiniai uždaviniai [SCT00]:

•      turi neprarasti programos   funkcionalumo, t.y. transformacijos turi būti semantiškai saugios.
•      užslėptumas turi būti kuo labiau maksimizuotas, t.y. norint perprasti   programos kodo logiką reikia daugiau laiko nei perprasti  programos kodo logiką.
•      transformacijos tamprumas turi būti maksimizuotas, t.y. automatinių įrankių, kurie panaikintų transformaciją, kūrimas būtų labai sudėtingas, arba jų kūrimo laikas tarpas būtų kuo ilgesnis.
•      transformacijos slaptumas, t.y. transformacija turi būti tokia, kad ją būtų kuo sunkiau aptikti.
•    Programos   vykdymo laiko nuostoliai turi būti minimizuoti.

4.3    Leksinis transformavimas.

.Net platforma turi galimybę gauti tarpinio kodo meta-duomenis apie metodus ir jų laukus, todėl atsiranda galimybė surinkti visų naudojamų metodų, jų laukų, klasių pavadinimus ir remiantis 1 teorema juos pakeisti taip, kad pati programa neprarastų funkcionalumo.
Tarkime jei pritaikytumėme tokią užslėpimo transformaciją  , kurios dėka metodus ir jų laukus pakeistume į tokias simbolines struktūras kaip „A1“, „A2“, „A3“ ir t.t. tai tarpinį kodą pervertę į msil kalbą vietoj informatyvių metodų laukų matytume tik „A1“, „A2“ ir panašiai.
 
6 Pavyzdys: „Metodų ir jų laukų transformacija“.
 
 
 

Tokių būdų atliekant transformaciją   geriau būtų parinkti labiau painius pavadinimus kaip „AsInNnNlMm“, „AsImNnNlMm“, nes pavadinimus  kaip „A1“ ir panašiai nėra labai sunku įsiminti, o kodo užslėpėjų tikslas – kuo labiau klaidinti trečių šalių asmenis, kad jiems būtu sunkiau ne tik suprasti metodų paskirtį, bet ir atsekti jų veikimo eigą. Galima kai kuriuos metodus pakeisti ir į nepavaizduojamus simbolius, kaip #17, #20 [Fee03] ir pan. Tokiu atveju  ildasm įrankis suteiktų panašią informaciją kaip 7 pavyzdyje.
 
7 pavyzdys: “ildasm įrankio nepavaizduojamų simbolių vaizdavimas”

 
Kaip matome šiuo atvejų ildasm įrankis mažai kuo gali padėti tiems, kurie bandys aiškintis programos kodo logiką.
Tam kad programos kodo painiojimas būtų lankstesnis, galima sukurti atributą, kurio pagalba būtų galima pačiam programuotojui nustatyti kokiu pavadinimu pakeisti nurodytą metodą ar lauką. Po to slepiant kodą, kodo užslėpėjas parinktų pavadinimus tik tiems metodams ir laukams, kurie nenurodyti paties programuotojo (8 pavyzdys).
8 pavyzdys: “ildasm įrankio nepavaizduojamų simbolių vaizdavimas”
class Foo
{
    [ObfuscatorAttribute(changeTo=”kibiras”)]
    void bar(int data)
    {
        …
        }
 
    int test(int data)
    {
        …
    }
        [ObfuscatorAttribute(changeTo=”ddd”)] 
    int pData;
}
4.4    Valdymo transformacijos.

Atlikus leksinę transformaciją   , transformuojami tik klasių, metodų ir jų laukų pavadinimai, o kodo instrukcijų seka lieka nepakitusi. Nagrinėjant tokį tarpinį kodą „baltosios dėžės“ principu, galima gana tiksliai atkurti programos logiką. Todėl tokia transformacija nėra labai efektyvi.
Visai logiška būtų į programą   įterpti kokią nors kitą programą  , t.y.

 ,

arba suskaidyti programą į keletą dalių ir kurią nors programos dalį padalinus į dvi dalis ir jas sujungti jas neaiškaus predikato pagalba, t.y.

 

Tokios transformacijos turi būti atliekamos taip, kad nebūtų prarastas pradinės programos funkcionalumas.
Ką tik paminėtos užslėptumo transformacijos yra paremtos neaiškiaisiais predikatais [SCT00]:

4 Apibrėžimas. Neaiškusis predikatas yra toks, kurio rezultatas yra aiškus užslėpimo metu, bet sudėtinga išsiaiškinti analizuojant užslėptą kodą.

Parenkant neaiškiuosius predikatus reikia atsižvelgti į jų laiko sąnaudas ir jų slaptumą. Tarkim mes parenkame tokį neaiškųjį predikatą kurio laiko sąnaudos yra beveik nepastebimos, bet tuo pačių jo slaptumas yra neefektyvus, nes yra gana greitai aptinkamas, tokiu atveju tokį neaiškųjį predikatą labai lengvai galima apeiti. Kitu atveju jei slaptumas yra labai didelis ir jį sunku atpažinti, bet laiko sąnaudos yra didžiulės tai toks neaiškusis predikatas mums irgi nenaudingas. Kaip matome, kuriant neaiškiuosius predikatus papildomai sprendžiami 4.2 skyriuje paminėti uždaviniai:

•    Laiko sąnaudų minimizavimas.
•    Slaptumo maksimizavimas.

Susitarimui predikatus žymėsime   jei jis visada grąžina teigiamą reikšmę,   – jei visada grąžinama neigiama  reikšmė ir   – jei grąžinama reikšmė yra nežinoma. Panagrinėkime N pavyzdį.
9 Pavyzdys : „valdymo transformacijos remiantis neaiškiaisiais predikatais“.
 

9 Pavyzdyje yra pavaizduoti trys elementariausi būdai kaip neaiškiųjų predikatų pagalba galima atlikti užslėpimo transformaciją. Kaip matome blokas [AB] išskaidomas į du blokus A ir B.  9a  atveju A ir B sujungiami predikatu kurio rezultatas visada yra teigiamas, todėl programos vykdymas šioje vietoje bus toks pats. 9b atveju predikato rezultatas gali būti tiek teigiamas tiek neigiamas, todėl dar yra sukuriamas blokas B‘ kurio funkcionalumas yra toks pat kaip ir bloko B. Šiuo atveju kad ir koks bebūtų predikato   grąžinamas rezultatas programos funkcionalumas nepakis. 9c atveju  naudojamas neaiškusis predikatas, kuris visada grąžins mums teigiamą rezultatą. Pridedamas klaidingas blokas Bblog kuris niekada nebus vykdomas, o skirtas tik klaidinimo tikslais.

4.5    Duomenų transformacija.

Duomenų transformacija užslepia statines duomenų reikšmes, tokiu būdų dar labiau užslepiamas programos tarpinis kodas [SCT00]. Tarkime boolean tipo kintamąjį   išskaidome į du integer tipo kintamuosius   ir   (10 a) pavyzdys). Pateikus naują kintamojo   vaizdavimą, atitinkamai turi būti apibrėžtos ir naujos atitinkamos operacijos su jais (10 b) pavyzdys). 10c pavyzdyje pavaizduoti trys boolean kintamieji A, B, ir C, kurie išskaidomi integer tipo į kintamuosius a1 ir a2, b1 ir b2, c1 ir c2. Taigi po užslėpimo transformacijos, loginės operacijos gali ne taip aiškiai būti atitinkamos.

10 Pavyzdys: „Boolean tipo duomenų galima transformacija“.
 

Kaip matome 10c pavyzdyje (2‘) ir (3‘) atrodo skirtingos, nors iš tiesų jų abiejų reikšmė yra neigiama. Panašiai ir su (4‘) ir (5‘) operacijomis – nors atliekamos tos pačios operacijos, atrodo skirtingai.
3 pavyzdyje pavaizduota dar viena problema, su kuria galima dažnai susidurti – simbolinės string tipo eilutės. Tarpini kodą pervertus į msil kalbą string tipo eilutės išlieka nepakitusios. Tai gali padėti bandant išsiaiškinti praogramos logiką. Tokių atveju logiška būtų naudoti tokių string eilučių kodavimą. Elementarus kodavimo būdas pavaizduotas 11 pavyzdyje (10 skyrius – Priedai). Užkodavus simbolines eilutes reiktų sukurti metodą, kuris atkoduoja užkoduotas eilutes (12 pavyzdys) ir įterpti jį į programos modulį (13 pavyzdys (10 skyrius – Priedai)). Rezultatas gaunas vėl painus:

IL_0000:  ldstr bytearray(6E 00 76 00 7C 00 79 00 61 00 2A 00 79 00 76 00 76 00 6F 00 52, 00)

Šiuo atvejų reiktų nemažai laiko aiškinantis kodavimo algoritmą ir po to bandyti jas atkoduoti.
4.6    Kodo užslėpėjų trūkumai.

Nors kodo užslėpėjai paslepia didžiąją tarpinio kodo dalį, bet yra vietų kurių negalima paslėpti – sisteminės klasės, jų metodai ir sisteminiai įvykiai (13  pavyzdys (10 skyrius – Priedai)). Ši problema sukelia nemažai nepatogumų, nes kuriant programas juos tenka gan dažnai panaudoti. Todėl žinant tų sisteminių klasių metodų paskirtį galima tiksliau nuspėti ką programos kodas daro prieš ir po sisteminio metodo.

13 pavyzdys: „Sisteminės klasės ir jų  metodai “
 

5    .NET platformos CLI PE/COFF rinkmenos fizinė struktūra

Kad geriau būtų galima suprasti .Net platformai skirtų tarpinio kodo užslėpėjų veikimą, išnagrinėjus kodo slėpimo metodus ir būdus, reiktų panagrinėti ir pačią rinkmenos fizinę struktūrą. .Net platformai skirtų vykdomųjų rinkmenų (*.exe rinkmenos) ir bibliotekų (*.dll ) struktūra skiriasi nuo win32 platformai skirtų rinkmenų struktūros, nes .Net naudoją papildytą win32 platformos PE/COFF  rinkmenos formatą, kurį pavadino CLI  PE/COFF. Šis formatas pavadintas taip todėl, kad jo dėka sistema įgalinta atpažinti vykdomas rinkmenos atvaizdus/dalis, talpina savyje CLI kodą ir meta duomenis, skirtus pateikti informaciją apie duomenų esybes [Ecma02]. Šiame skyriuje bus bendrais bruožais panagrinėta fizinė .Net modulių struktūra.
5.1    CLI PE/COFF rinkmenos struktūra

Žemiau pateiktoje CLI PE/COFF rinkmenos struktūroje pavaizduota bendras jos atvaizdas.
 
15 pavyzdys: CLI PE/COFF rinkmenos bendra struktūra:
 

Visi rinkmenos atvaizdai vykdymo metu susideda iš tokių rinkmenos katalogų:
•    PE antraštės katalogas, kuriame yra nurodoma kaip turi būti inicijuojami įvairūs programos laukai.
•    CLI antraštės katalogas, kurioje yra nuorodos į specifinius  duomenis vykdymo metu.
•    CLI duomenų katalogas, kuriuose yra saugomi meta duomenys, vykdomasis IL kodas, virtualiu lentelių sutvarkymai.
•    Likusieji katalogai saugo faktinius duomenis aprašytus rinkmenos antraščių kataloguose. Į juos įeina tokie duomenys kaip  importuojami/eksportuojami duomenys, rinkmenoje esantys duomenys.
5.2    Meta duomenų struktūra

Meta duomenys domina todėl, kad juose yra visa informacija apie programoje panaudotų klasių, metodų ir kintamųjų vardai ir likusių nepaminėtų programos modulio esybių bei string tipo eilučių reikšmės. Žinant jos fizinę struktūra galima prie šios informacijos prieiti ir ją pakeisti, ką ir daro kodo užslėpėjai. Todėl reiktų ją panagrinėti ir detaliau su ja susipažinti.
Meta duomenų fizinis atvaizdas susideda iš jų antraštės ir jų duomenų. Antraštė yra skirta bendrai aprašyti meta duomenis, nes joje nėra jokios informacijos apie esybių vardus, jų tipus ir pan., bet joje yra nurodytas meta duomenų dydis fizinėje rinkmenoje bei   kurie toje antraštėje yra aprašyti. Žemiau pateiktoje lentelėje yra pavaizduota meta duomenų antraštės fizinė struktūra:
16 pavyzdys: Meta duomenų antraštės fizinė struktūra.
Adreso poslinkis    Lauko dydis baitais    Lauko pavadinimas    Aprašymas
0    4    Žymė    „Magiška“ žymė, kuri identifikuoja meta duomenų pradžią. Jos reikšmė yra 0x424A5342 arba “BSJB”.
4    2    Pagrindinė versija    Pagrindinė versija, reikšmė 1
6    2    Šalutinė versija    Šalutinė versija, reikšmė 0
8    4    Rezervuota    Rezervuota, reikšmė 0. Naudojama tik tada jei programoje panaudotos virtualios lentelės.
12    4    Ilgis    String tipo eilutės, apibrėžiančios .Net platformos versiją, ilgis. Reikšmė lygi m.
16    m    Versija    UTF-8 koduote užkoduota versiją nurodanti string tipo eilutė
16+m            tuščia frazė skirta pereiti prie sekančių 4 baitų
x    2    Vėlevėlės    Reikšmė lygi 0. Rezervuota vėlevėlėms, kurios identifikuoja virtualias lenteles
x+2    2    Duomenų srautų skaičius    Duomenų srautų skaičius, kuris nusako kiek bus naudojama duomenų srautų
x+4        duomenų srautų antraštės    Duomenų srautų antraščių masyvas

17 pavyzdys: Meta duomenų, duomenų srautų antraštės, fizinė struktūra.
Adreso poslinkis    Lauko dydis baitais    Lauko pavadinimas    Aprašymas
0    4    Adreso poslinkis    Nurodo duomenų srauto adreso poslinkį nuo meta duomenų pradžios adreso.
4    4    Dydis    duomenų srauto dydis, turi būti 4 kartotinis skaičius
8        Pavadinimas    Duomenų srauto pavadinimas, saugomas string tipo eilutėje ir jo pabaigą žymi \0 simbolis.

Galimi duomenų srautai meta duomenyse yra:
•    #Strings. Šiame duomenų sraute saugomi visi programos modulio esybių (klasių, metodų ir pan.) pavadinimai
•    #US.  Programuotojo sukurtos string tipo eilutės
•    #Blob. Visos programos modulyje esančios skaitinės reikšmės (kintamiesiems priskirtos reikšmės, metodams perduodamos parametrų reikšmės ir pan.)
•    #GUID. Saugo programos modulio 128 bitų ilgio GUID.
•    #~. Saugo visas meta duomenų lentelės, aprašančios modulyje panaudotas esybes.
Meta duomenys organizuojami lentelių pagalba, t.y. panaudoti esybių tipai kaip klasės, metodai ir pan. yra saugomi vienoje lentelėje, kurioje yra nuorodos į kiekvieno tipo esybės lenteles. Nurodytose lentelėse taip pat gali būti nuorodos į kitas lenteles ar reikšmes. Tokia forma yra saugomi visi meta duomenys. 18 pavyzdyje yra pavaizduotas paprastas meta duomenų organizavimo pavyzdys. Pirmoje lentelėje iš kairės yra saugomi duomenys apie visas panaudotas esybes modulyje. Iš šios lentelės seka nuorodos į informaciją, aprašančią tas esybes.  Pavyzdžiui Metodo esybės eilutė nurodo į metodų lentelę, kurios kiekviena eilutė be papildomos informacijos turi dar po tris nuorodas į  pavadinimo, signatūros informaciją bei parametrų lentelę.
18 pavyzdys: Meta duomenų organizavimas.
 
Tam, kad labiau susipažintumėme su meta duomenų organizavimu panagrinėkime 19 pavyzdyje pavaizduotos programos modulio fizine struktūrą.

19 pavyzdys: Nagrinėjamo modulio programos kodas.
using System;
namespace bakalauriniuiI
{
    class Class1
    {
        private static void Sveikintis(string vardas)
        {
            Console.Out.WriteLine(“Labas “+vardas);
        }
        [STAThread]
        static void Main(string[] args)
        {
            Sveikintis(“Vytautai”);
        }
    }
}

Sukompiliavę šį programos kodą į .Net platformos modulį atidarykime jį su žemo lygio kodo redaktoriumi. Suradę meta duomenų pradžią pagal 16 pavyzdyje pateiktą „magišką“ meta duomenų katalogo fiziniame modulyje žymę.
20 Pavyzdys: „magiškoji“ meta duomenų pradžios žymė.
 

Remdamiesi 16 Pavyzdyje pateikta meta duomenų struktūra fiziniame modulyje, nuskaitome naudojamus duomenų srautus:
21 Pavyzdys: Nagrinėjamame modulyje naudojami duomenų srautai:
Adreso poslinkis
(10-tainėje sistemoje)    Duomenų srauto dydis    Duomenų srauto Pavadinimas
108    468    #~
576    520    #Strings
1096    36    #US
1132    16    #GUID
1148    60    #Blob

Turint Šią informaciją mes greitai galime pasiekti mus dominantį duomenų srautą. Tarkime mus domina #Strings ir #US duomenų srautai. Apskaičiavę jų poslinkius nuo modulio pradžios, surandame jų vietą modulyje ir gauname informaciją, pateikta 22 ir 23 pavyzdžiuose.
22 Pavyzdys: #Strings duomenų srautas.
 

23 Pavyzdys: #US duomenų srautas.
 

Iš pirmo žvilgsnio gali pasirodyti, jog taip pasiekiamus meta duomenis pakeisti yra nesudėtingas uždavinys, bet panagrinėjus tarpinio kodo specifikaciją šį teiginį galima greitai paneigti. Iš tiesų tai labai sudėtingas ir didelio kruopštumo reikalaujantis uždavinys. Taip yra todėl, kad meta duomenys kaip buvo minėta anksčiau yra organizuojami lentelių ir nuorodų į lenteles bei kitus duomenis pagalba. Todėl, pakeitus duomenis vienoje vietoje pasikeičia kitų duomenų adresai, ko pasekoje reikia pakeisti visas nuorodas į juos. Be to dar reikia atsižvelgti ir į tai kad yra duomenų priklausomų nuo kitų duomenų, t.y. jų reikšmės yra apsprendžiamos remiantis kitais duomenimis, kas dar labiau pasunkiną šią užduotį.
6    Esamų įrankių analizė.

Ankstesniuose skyriuose buvo nagrinėjami teoriniai kodo užslėpėjų modeliai bei jų darbo metodiką.  Remiantis teorinėmis žiniomis buvo preliminariai įvertinti ir pačių kodo užslėpėjų teikiama nauda ir galimas šalutinis poveikis. Susipažinus su meta duomenų ir tarpinio kodo fizine struktūra .NET PE rinkmenose preliminariai buvo įvertintas kodo užslėpėjų darbo procesas ir su kokiais sunkumais yra susiduriama.
Šioje dalyje bus nagrinėjama realių įrankių teikiama nauda ir jų šalutinis poveikis. Šiam tikslui bus pasitelkta keletas įrankių skirtų tarpinio kodo atkūrimui į programos kodą (šiuos įrankius toliau vadinsime dekompiliatoriais) bei keletas įrankių, skirtų tarpinio kodo užslėpimui. Pasirinkti tokie įrankiai:
Dekompiliatoriai:
•    9Rays „Spices.Net Decomppiler“
•    Lutz Roeder‘s „.Net Reflector“

Kodo užslėpėjai:
•    Oak Vale Networks „XenoCode 2004“
•    Desaware „QND Obfuscator“

Kadangi kiti tarpinio kodo užslėpėjai arba veikė nestabiliai ir nepateikdavo rezultato, arba dėl bandomojo laikotarpio buvo ribojama jų veikla, todėl jie buvo atmesti iš analizei naudojamų sąrašo.
6.1    Programos kodo atgaminimas

.Net tarpinio kodo paprastumas sukėlė daugybę diskusijų dėl programų saugumo. Todėl šiame skyriuje pagrindinis dėmesys bus skiriamas programos kodo atgaminimo iš tarpinio .Net kodo tikslumą . Iš pradžių reikia parašyti paprastą programos kodą, kuris paimtų kuo daugiau programos savybių, todėl šį kartą bus panaudoti ne tik klasės ir metodai, bet ir klasių paveldimumas, loginės programos struktūros, ciklai, tipų keitimas (Casting) ir pan.
 24 pavyzdyje (10 skyrius – Priedai) pateikta pavyzdinė programa, su kuria atliksime programos kodo atgaminimo analizę. Iš pradžių ši programos kodą sukompiliuojame į .Net modulį. Sukompiliuotą modulį atsidarome su dekompiliatoriumi ir pradedame atkūrimo procesą. Iš pradžių gauname informaciją apie visas modulio esybes, nes meta duomenų pagalba yra atkuriami esybių pavadinimai. Pasirinkę klasės „Pagrindinis“ metodą „Main“ jį dekompiliuojame ir gauname informaciją, pateiktą 25 pavyzdyje (10 skyrius – Priedai). Programos kodas yra pakitęs, bet tarp originalaus kodo ir dekompiliuoto kodo didelių skirtumų nepastebėta. Be to šiame dekompiliuotame kode lengvai galima perprasti visą jo logiką. Be to, analizuojant toliau, randame esybių kurios faktiškai išlieka nepakitusios kaip 26 ir 27 pavyzdžiuose:
26 pavyzdys: Dekompiliuota klases „Asmuo“ savybė „asmensKodas“.
public int asmensKodas
        {
            get { return this.asmKodas;  }
            set { this.asmKodas = value; }
        }
27 pavyzdys: Dekompiliuotas klasės „Darbuotojas“ metodas „skaiciuotiAtlyginima“.
public double skaiciuotiAtlyginima(int dienu_sk)
        {
            int num3;
double num1 = ((double)dienu_sk);
            double num2 = 0f;

            if (dienu_sk<=31) {
                for (num3 = 0; num3< dienu_sk; num3++ )
                {
                    num2 += 20.25f;
                }
            } else {
                num2 = 627,75f;
            }
            return num2;
        }
Palyginę 27 pavyzdyje patiktą kodo fragmentą su originaliu kodo fragmentu pastebime jog nėra atkurta metodo viduje esančių kintamųjų pavadinimai. Neesminiai skirtumai ir tarp paties kodo struktūros. Taip analizuodami kitas modulio esybes nepastebime didesnių skirtumų ir tarp kitų esybių. Labiausiai išsiskyrusi esybė yra klasės „Darbuotojas“ konstruktorius, kuris pavaizduotas 28 pavyzdyje. Šiame metode skirtumas yra tarp išvedamo rezultato sudarymo – dekompiliuotame metode išvedama simbolinė eilutė sudedama iš masyvo elementų, o ne iš atskirų simbolinių eilučių kaip tai realizuota originaliame kode. Bet ir šiuo atveju programos logikos tai nekeičia ir įgalina nesunkiai ją perprasti.

28 pavyzdys: Dekompiliuotas klasės „Darbuotojas“ konstruktorius.
        public Darbuotojas(string vardas, int amzius)
        {
            this.vardas = vardas;
            this.amzius = amzius;
            object[] objArray1 = new object[4];
            objArray1[0] = “Naujas asmuo sukurtas:\nVardas: “;
            objArray1[1] = vardas;
            objArray1[2] = “\namzius: “;
            objArray1[3] = amzius;
            Console.WriteLine(string.Concat(objArray1));
         }

Išanalizavus 24 pavyzdį (10 skyrius – Priedai) ir dar kelis papildomus pavyzdžius nesunku įsitikinti, jog .Net modulių dekompiliatoriaus pagalba galima išgauti ekvivalentų programos kodą su visa informaciją apie dekompiliuoto modulio esybes.
6.2    Tarpinio kodo užslėpėjai.

Išanalizavus 6.1 skyriuje aprašytas dekompiliatorių galimybes įsitikinome, jog atgaminti sukompiliuotą programos kodą į tarpinį kodą, nėra labai sudėtingas uždavinys. Todėl šiame skyriuje pasinaudodami pasirinktais kodo užslėpėjų įrankiais atliksime kodo užslėpėjų teikiamos naudos analizę. Šiai analizei bus naudojami visi analizei pasirinkti kodo užslėpimo įrankiai, kurių pagalba bus vertinamas kodo užslėpimas pagal pasirinktus kriterijus.
Tarkime, kad turime programą, kuri naudoja sudėtingą matematinių skaičiavimų algoritmą. Kadangi programos licenzija yra pigi, palyginus su naudojamo algoritmo kaina, todėl mūsų pagrindinis uždavinys – paslėpti naudojamo algoritmo logiką. Efektyviam kodo užlėpimo įvertinimui pasirenkame tokius kriterijus:
•    Informatyvūs pavadinimai turi būti pakeisti į neinformatyvius šioms esybėms:
o    Klasėms.
o    Klasių metodams.
o    Metodų parametrams.
o    Metodų laukams.
•    Kodo transformavimui turi būti panaudota:
o    Papildomo kodo įterpimas.
o    Papildomų predikatų įterpimas.
o    Kodo struktūros pakeitimas.
•    Turi būti transformuotos simbolinės string tipo eilutės.
•    Programos išvedamas rezultatas turi būti nepakitęs.
6.2.1    Programos logikos užslėpimas

Naudojantis turimais įrankiais ir sudarytais kriterijais bus analizuojami įvairūs atvejai, kodo fragmentai ir jų transformacijos.
6.2.1.1    Programoje panaudotų esybių pavadinimų užslėpimas

Programoje naudojamų esybių pavadinimų užslėpimui įvertinti panaudosime paprastą programos kodą, kuriame yra tik vertinimo kriterijuose nurodytos esybės. 29 pavyzdyje pateiktas esybių užslėpimo analizei naudojamas kodas.
29 pavyzdys: kodas programos kodo esybių užslėpimui analizuoti.
class Pavyzdys
{
    private static int grupavimas(string stulpelio_pavadinimas)
    {
        int ilgis;
        ilgis = Console.ReadLine().Length;       
        return stulpelio_pavadinimas.Length – ilgis;
    }
    static void Main(string[] args)
    {
        Console.WriteLine(grupavimas(“penki”));
    }
}
Pasinaudoję turimais įrankiais gavome tokius rezultatus:
30 pavyzdys: įrankio „XenoCode“ užslėpimas
internal class d969e9e8085b9c5c
{
    // Methods
    private d969e9e8085b9c5c(){ /*  */ }
    public static int 5ac308287836cd9c(string 384a2646beca5704)
    {
        int num1 = Console.ReadLine().Length;
        return (384a2646beca5704.Length – num1);
    }
    private static void c447809891322395(string[] ce8d8c7e3c2c2426) {/* */}
}
30 pavyzdys: įrankio „QND Obfuscator“ užslėpimas
internal class $
{
    // Methods
    private static int $(string $)
    {
        int num1 = Console.ReadLine().Length;
        return ($.Length – num1);
    }
    private static void $(string[] $) {/* */}
    public $(){/* */}
}
Nesunku įsitikinti, jog „QND Obfuscator“ įrankis šiuo atveju pateikia labiausiai klaidinantį rezultatą, nes visos panaudotos esybės pervadintos vienu vardu „$“, todėl praktiškai neįmanoma atsekti kokios esybės kokiems tikslams yra panaudotos.
6.2.1.2    Kodo transformavimas

Paties kodo transformavimo analizei naudosime 27 pavyzdyje pateiktą „Darbuotojas“ klasės metodą „SkaiciuotiAtlyginima“.
„XenoCode“ įrankiu transformuotas metodas pateiktas 32 pavyzdyje (10 skyrius – Priedai). Šiuo atveju yra pakeisti ne tik esybių pavadinimai, bet ir pats tarpinis kodas. Tarpinis kodas transformuotas pakeičiant blokų struktūra, jų išdėstymo tvarką ir įterpti papildomi loginiai predikatai bei goto sakiniai, dėl kurių buvo daugybė diskusijų ir priimta jų atsisakyti dėl sunkaus tokio stiliaus kodo analizavimo. Viso to pasekoje kodas tampa neaiškus ir sunkiai analizuojamas.
„QND Obfuscator“ įrankio pagalba transformuotas analizuojamas metodas pateiktas 33 pavyzdyje. Kaip matome šis kodo užslėpėjas tarpinio kodo struktūros nepakeitė ir yra gan lengvai skaitoma, nors suprasti programos logika vis dėlto yra sunku, nes visi esybių pavadinimai yra vienodi, todėl sunku atsekti kuri esybė yra naudojama.
6.2.1.3    Simbolinių eilučių transformavimas

Simbolinių string tipo eilučių transformavimo analizei žingsnyje buvo pasirinktas klasės „Darbuotojas“ metodas „Ispejimas“. Šiame žemiau pateiktame pavyzdyje yra simbolinių eilučių transformacija naudojant „XenoCode“ įrankį. Šios transformacijos dėka metodo turinys tampa visiškai neaiškus ir galima jį supainioti su netikro kodo bloko įterpimu.
34 pavyzdys: Transformuotas klasės „Darbuotojas“ metodas „Ispejimas“ su „XenoCode“ įrankiu
public static void 1286e0319911caea(74def51de52408a9 5872f7320414eaac)
{
5872f7320414eaac.Invoke(string.Intern(d4485e5ae14ac128.cc381ffa3ede662f(“\u4982\u50b2\u57ae\u5e99\u65a7\u6c99\u7398\u7a8d\u8192\u8853\u8f43\u968a\u9d7e\ua48f\uab37\ub282\ub986\uc07a\uc774\uce7b”, 1411402049)));
}
35 pavyzdyje pateikta kodo transformacija naudojant „QND Obfuscator“ įrankį. Šis įrankis nedaro jokios simbolinių eilučių transformacijos, todėl nesunku atsekti kurioje kodo dalyje yra formuojamos ir išvedamos simbolinės eilutės.
35 pavyzdys: Transformuotas klasės „Darbuotojas“ metodas „Ispejimas“ su „QND Obfuscator“ įrankiu
public static void $($ $)
{
    $.Invoke(“Atsargiai- jau nulis”);
}
6.2.1.4    Transformuoto tarpinio kodo išvedamas rezultatas

Tikrinant transformuoto 24 pavyzdyje (10 skyrius – Priedai) pateikto kodo išvedamus rezultatus, visi kodo užslėpėjai atitiko nustatytą kriterijų ir išvedamas rezultatas išliko toks pats. Po to buvo bandoma su kitomis programomis ir rezultatas taip pat buvo nepakitęs, todėl galima teigti, jog naudoti kodo užslėpėjai neturi jokios įtakos išvedamiems rezultatams.
6.2.1.5    Kodo užslėpėjų naudos apibendrinimas

Atlikus kodo užslėpėjų analizę galime daryti išvadas naudojantis nustatytais kriterijais. Žemiau pateiktoje lentelėje yra išdėstyti visi kriterijai ir naudoti kodo užslėpėjai.
1 lentelė: trumpa naudotų kodo užslėpėjų suvestinė.
    „XenoCode“    „QND Obfuscator“
Klasių pavadinimai    pakeičia    pakeičia
Metodų pavadinimai    pakeičia    pakeičia
Metodų parametrai    pakeičia    pakeičia
Metodų laukai    pakeičia    pakeičia
papildomas kodas    įterpia    neįterpia
papildomi predikatai    įterpia    neįterpia
kodo struktūra    pakeičia    nepakeičia
simbolinės string eilutės    užkoduojamos    neužkoduojamos
išvedamas rezultatas    nepakinta    nepakinta

Kaip matome visus kriterijus atitiko „XenoCode“ kodo užslėpėjo įrankis, o „QND Obfuscator“ įrankis pakeičia tik esybių pavadinimus. Bet kadangi „QND Obfuscator“ labiau klaidina pakeisdamas esybių pavadinimus, buvo atliktas dar vienas bandymas – užslėpti tarpinį kodą, naudojant šiuos abu įrankius. Iš pradžių 24 pavyzdyje (10 skyrius – Priedai) esantis kodas buvo užlėptas su „XenoCode“ įrankiu, po to užslėptas kodas, buvo dar kartą užslėptas naudojant „QND obfuscator“. Rezultatas buvo toks kokio ir buvo tikimasi – kodas užslėptas efektyviai ir norint jį analizuoti nebeužtenka naudoti vien dekompiliatorius, nes jų pagalba praktiškai negaunama jokios naudingos informacijos.
6.2.2    Originalaus ir užslėpto kodo efektyvumas.

Šiame skyriuje bus analizuojamas tarpinio kodo užslėpėjų šalutinis poveikis – efektyvumo pokytis, t.y. kiek padidėja užslėpto tarpinio kodo vykdymo laikas. Šiam tikslui bus analizuojamos kelios programos su skirtingo pobūdžio veiksmais. Testavimas ir analizė bus vykdoma su ta pačia technine įranga ir tais pačiais operacinės sistemos procesais, kad šalutinis poveikis analizės rezultatams būtų kuo mažesnis.
Iš pradžių paanalizuosime, kokią įtaką daro efektyvumui užslėptas tarpinis kodas, kuris atlieka rekursyvius matematinius skaičiavimus. Tam tikslui pasirinkome fibonačio eilutės n-tojo nario skaičiavimą, kurio algoritmas programos kodas pateiktas 36 pavyzdyje (10 skyrius – Priedai). Šios programos vykdymo laiko palyginimas kai jos tarpinis kodas yra originalus ir kai jis yra transformuotas, yra pateiktas 2 lentelėje:
2 lentelė: fibonačio eilutės n-tojo nario skaičiavimų palyginimas.
originalaus tarpinio kodo vykdymo laikas (sek.)    užslėpto tarpinio kodo vykdymo laikas (sek.)    laiko skirtumas (sek.)    laiko skirtumas (procentais)
11,055    11,647    0,592    5
11,036    11, 466    0,430    4
11,096    11,507    0,411    4
11,216    11,537    0,321    3
11,076    11,547    0,471    4
Vidurkis:    4

Remdamiesi šios lentelės duomenimis galime daryti išvadą, jog vykdymo laiko skirtumas nėra didelis (4%).
Antram bandymui pasirinktas iteracijų efektyvumo palyginimas. 37 pavyzdyje (10 skyrius – Priedai) pateiktas programos kodas, kuris atlieka po 3 skirtingus ciklus, kurių kiekvienas įvykdė po 50000000 iteracijų. Šios analizės rezultatai pateikti 3 lentelėje:
3 lentelė: iteracijų vykdymo palyginimas.
originalaus tarpinio kodo vykdymo laikas (sek.)    užslėpto tarpinio kodo vykdymo laikas (sek.)    laiko skirtumas (sek.)    laiko skirtumas (procentais)
13,469    13,326    -0,143    -1
13,570    13,326    -0,244    -2
13,473    13,567    0,094    0,7
13,539    13,306    -0,233    -2
13,489    13,306    -0,183    -1
Vidurkis:    -1,3

Išanalizavę 3 lentelės duomenis gauname rezultatą priešingą prieš tai analizuoto vykdymų skirtumo rezultatui. Šiuo atveju transformuoto kodo vykdymo laikas sumažėjo. Dar kartą išanalizavęs programos kodą ir kodo užslėpėjų metodus iškėliau hipotezę:

Hipotezė: .Net platformos tarpiniame kode esančio ciklo vykdymo laikas priklauso nuo ciklo tipo ir nuo to ar kintamasis, kurio pagalba yra nustatoma ciklo pabaiga yra inkrementuojamas ar dekrementuojamas.
Šiai hipotezei patvirtinti arba paneigimui iš pradžių pakeičiame programos kodą taip, kad būtų skaičiuojamas kiekvieno ciklo vykdymo laikas. Šios programos kodo netransformuojame, nes transformuojant galima pakeisti ciklų struktūrą ir jų tipus nepakeičiant grąžinamo rezultato. Pakeistos programos ciklų po 500000000 iteracijų vykdymo laikų vidurkiai pateikti 4 lentelėje:

4 lentelė: 37 pavyzdžio ciklų vykdymo laiko vidurkiai.
for ciklo vykdymo laiko vidurkis (sek.)    while ciklo vykdymo laiko vidurkis (sek.)    do{..} while ciklo vykdymo laiko vidurkis (sek.)
5,083    5,348    3,645

Remiantis 4 lentelės rezultatais pasitvirtina hipotezės pirma dalis, kuri sako, kad ciklų vykdymo laikas priklauso nuo ciklo tipo. Tęsiant tyrimą pakeičiame programą taip, kad visi kintamieji, nustatantys ciklų pabaigą, būtų inkrementuojami. Dar kartą pakeistos programos ciklų vykdymo laikų vidurkiai pateikti 5 lentelėje:
5 lentelė: 37 pavyzdžio ciklų, kuriuose kintamieji, nustatantys ciklo pabaigą, yra inkrementuojami,  vykdymo laiko vidurkiai.
for ciklo vykdymo laiko vidurkis (sek.)    while ciklo vykdymo laiko vidurkis (sek.)    do{..} while ciklo vykdymo laiko vidurkis (sek.)
4,887    3,235    3,295

4 lentelėje pateikti rezultatai patvirtina hipotezės antrąją dalį, kuri teigia, kad ciklų vykdymo laikas priklauso nuo to ar kintamieji, kurie nustato ciklo pabaigą, yra inkrementuojami ar dekrementuojami.
Remiantis 4 ir 5 lentelių pateiktais duomenimis patvirtiname iškeltą hipoteze ir sudarome taisyklę:

Taisyklė: .Net platformos tarpiniame kode esančio ciklo vykdymo laikas priklauso:
•    nuo ciklo tipo.
•    nuo kintamojo, kuris nustato ciklo pabaigą, keitimo – inkrementavimas ar dekrementavimas.

Ši taisyklė paaiškina 3 lentelėje pateiktus rezultatus. Transformuojant tarpinį kodą galima pakeisti ciklų struktūrą ir tipus, nes svarbu kad būtų įvykdytas programos blokas nustatytą iteracijų skaičių ir neturi jokios reikšmės kokiu būdu tie ciklai bus įvykdyti – svarbu, kad gražinamas rezultatas būtu nepakitęs. Taigi 37 pavyzdyje (10 skyrius – Priedai) pateiktos programos tarpinis kodas buvo pakeistas taip, kad transformuoti ciklai buvo optimalesni.
Sekančiame žingsnyje pasirinkta operacijų su simbolinėmis eilutėmis vykdymo laiko lyginimas. Programos, kurio pagalba buvo analizuojamas simbolinių string eilučių vykdymo laiko skirtumas, kodas yra pateiktas 38 pavyzdyje (10 skyrius – Priedai). Šios analizės rezultatai pateikti 6 lentelėje:
6 lentelė: operacijų su simbolinėmis eilutėmis vykdymo laiko palyginimas:
originalaus tarpinio kodo vykdymo laikas (sek.)    užslėpto tarpinio kodo vykdymo laikas (sek.)    laiko skirtumas (sek.)    laiko skirtumas (procentais)
11,086    11,176    90    0,8
11,076    11,136    60    0,5
11,497    11,267    -230    -2
11,035    11,577    542    5
11,096    11,226    130    1
Vidurkis:    1

Remiantis 6 lentelės duomenimis įsitikiname, kad operacijų su simbolinėmis eilutėmis vykdymo efektyvumas lieka praktiškai nepakitęs (1%).
Atlikus šią analizę pastebimas tarpinio kodo vykdymo sulėtėjimas, bet kadangi šis sulėtėjimas yra gan mažas – apytiksliai iki 4 %, todėl didelės žalos nepadaroma.
 
7    Išvados.

Kodo užslėpėjai yra puikus įrankis norint apsaugoti programos kodo logiką nuo asmenų, kurių tikslas visą tai išsiaiškinti. Atlikus kodo užslėpėjų teikiamos naudos analizę paaiškėjo, jog galima efektyviai užslėpti programos kodą. Kadangi programuojant sistemą stengiamasi rašyti programos kodą kuo aiškesnį ir suprantamesnį, kad būtų išvengta kuo mažiau klaidų. Panaudojus kodo užlsėpėją tarpinis kodas tampa ne toks aiškus ir tai apsunkiną jo išsiaiškinimą.

Kai kuriais atvejais kodo užslėpėjai suteikia efektyviausią apsaugą. Toks pavyzdys būtų kai viename modulyje yra užkoduotas filmas ar muzika ir programos kodas, kuriuo vykdomas filmo ar muzikos vykdymas ir dar tarkim finansinės transakcijos (reikia sumokėti numatytą sumą už filmo žiūrėjimą).

Panaudojus kodo užslėpėją programai dažniausiai tenka didesnės vykdymo laiko sąnaudos. Atlikus kodo užslėpėjų analizę paaiškėjo jog dėl duomenų transformacijų ir įterpto papildomo kodo reikalingos papildomos operacijos toms transformacijoms grąžinti tikrąją reikšmę. Bet galimas ir toks atvejis, kad dėl pakitusių programoje vykdomų ciklų struktūros, sutrumpės ir vykdymo laikas.

Kuriant kodo užslėpėjus reiktų panaudoti kuo daugiau transformacijų tipų. Kadangi kodo užslėpimo transformacijos išlaiko programos funkcionalumą, tai reiktų panaudoti kuo daugiau jų tipų, kad kodas būtų labiau klaidinantis.Be to atlikus analizę buvo įsitikinta, jog ne visi siūlomi kodo užlėpimo įrankiai naudoja visas galimas kodo transformacijas arba jų nauda nėra tokia efektyvi kaip kitų įrankių.

 
8    Reziume

I have studied and analyzed theoretical models of intermediate language code obfuscation. After that I analyzed and compared some tools for reverse engineering and obfuscation. I feel that .net obfuscators will help to protect the intellectual property of future software-authors like us. In addition, it also successfully fulfils the purpose of making reverse engineering more difficult. In my opinion it is better to use more than one tool, because each of them has at least one function who works better than other tool’s equivalent function, and if we mix their functionality we will achieve better intermediate code security from reverse engineers.
 
9    Literatūros sąrašas

[SCT00]    Christian S. Collberg, Clark Thomborson. Tamper-Proofing, and Obfuscation – tools for software protection.
http://citeseer.nj.nec.com/458447.html 267 KB,
2000 m.

[Wro02]    Gregory Wrobkewski. General Method of Program Code Obfuscation.  http://citeseer.nj.nec.com/wroblewski02general.html. 228 KB,
2002 m.

[Med03]    Ivan Medvedev. Obfuscation and obfuscators.
http://blogs.gotdotnet.com/ivanmed/permalink.aspx/a20b0b8c-b62e-46ba-8432-d4944a66321d  14 KB,
2003.08.25.

[Fee03]    Greg Fee. Hijacking .Net – Security ramifications of reflection.
http://blogs.gotdotnet.com/gregfee/permalink.aspx/6e213ebb-e7a7-415f-a2b6-cc59f53e1a2b 18 KB,
2003.07.01.

[Ecma02]   ECMA – 335 „Common Language Infrastructure“ 2nd edition (December 2002)
           http://www.ecma-international.org/publications/files/ecma-st/ECMA-335.pdf

 
10    Priedai.

11 Pavyzdys: „string tipo simbolinių eilučių kodatorius“.
public static string Encrypt(string inputStr)
{
    int len = inputStr.Length;
    int sum = 10;

    char[] encryptedChars = new char[len];
    for (int i=0; i<len; i++)
    {
        encryptedChars[i] = (char)((int)inputStr[i] + sum);
    }

    char[] newNameStr = new char[len];
    for (int i=0; i<len; i++)
    { // reverse the name
        newNameStr[i] = encryptedChars[len-1-i];
    }

    for (int i=0; i<len; i++)
    { // reverse the name
        encryptedChars[i] = newNameStr[i];
    }

    return String.Intern(new String(encryptedChars));
}
12 Pavyzdys: „metodas užkoduotoms eilutėms atkoduoti“.
public static string Decrypt(string inputStr)
{
    int len = inputStr.Length;
    char[] decryptedChars = new char[len];
    for (int i=0; i<len; i++)
    {
        decryptedChars[i] = (char)((int)inputStr[i] – 10);
    }

    char[] newNameStr = new char[len];
    for (int i=0; i<len; i++)
    { // reverse the name
        newNameStr[i] = decryptedChars[len-1-i];
    }

    for (int i=0; i<len; i++)
    { // reverse the name
        decryptedChars[i] = newNameStr[i];
    }

    return String.Intern(new String(decryptedChars));
}

13 Pavyzdys: „tarpinis kodas po užslėpimo“.

.method public hidebysig static string  Decrypt(string inputStr) cil managed
{
  // Code size       119 (0x77)
  .maxstack  5
  .locals init (int32 V_0,
           char[] V_1,
           int32 V_2,
           char[] V_3,
           int32 V_4,
           int32 V_5,
           string V_6)
  IL_0000:  ldarg.0
  IL_0001:  callvirt   instance int32 [mscorlib]System.String::get_Length()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  conv.ovf.u4
  IL_0009:  newarr     [mscorlib]System.Char
  IL_000e:  stloc.1
  IL_000f:  ldc.i4.0
  IL_0010:  stloc.2
  IL_0011:  br.s       IL_0025
  IL_0013:  ldloc.1
  IL_0014:  ldloc.2
  IL_0015:  ldarg.0
  IL_0016:  ldloc.2
  IL_0017:  callvirt   instance char [mscorlib]System.String::get_Chars(int32)
  IL_001c:  ldc.i4.s   10
  IL_001e:  sub
  IL_001f:  conv.u2
  IL_0020:  stelem.i2
  IL_0021:  ldloc.2
  IL_0022:  ldc.i4.1
  IL_0023:  add
  IL_0024:  stloc.2
  IL_0025:  ldloc.2
  IL_0026:  ldloc.0
  IL_0027:  blt.s      IL_0013
  IL_0029:  ldloc.0
  IL_002a:  conv.ovf.u4
  IL_002b:  newarr     [mscorlib]System.Char
  IL_0030:  stloc.3
  IL_0031:  ldc.i4.0
  IL_0032:  stloc.s    V_4
  IL_0034:  br.s       IL_0048
  IL_0036:  ldloc.3
  IL_0037:  ldloc.s    V_4
  IL_0039:  ldloc.1
  IL_003a:  ldloc.0
  IL_003b:  ldc.i4.1
  IL_003c:  sub
  IL_003d:  ldloc.s    V_4
  IL_003f:  sub
  IL_0040:  ldelem.u2
  IL_0041:  stelem.i2
  IL_0042:  ldloc.s    V_4
  IL_0044:  ldc.i4.1
  IL_0045:  add
  IL_0046:  stloc.s    V_4
  IL_0048:  ldloc.s    V_4
  IL_004a:  ldloc.0
  IL_004b:  blt.s      IL_0036
  IL_004d:  ldc.i4.0
  IL_004e:  stloc.s    V_5
  IL_0050:  br.s       IL_0060
  IL_0052:  ldloc.1
  IL_0053:  ldloc.s    V_5
  IL_0055:  ldloc.3
  IL_0056:  ldloc.s    V_5
  IL_0058:  ldelem.u2
  IL_0059:  stelem.i2
  IL_005a:  ldloc.s    V_5
  IL_005c:  ldc.i4.1
  IL_005d:  add
  IL_005e:  stloc.s    V_5
  IL_0060:  ldloc.s    V_5
  IL_0062:  ldloc.0
  IL_0063:  blt.s      IL_0052
  IL_0065:  ldloc.1
  IL_0066:  newobj     instance void [mscorlib]System.String::.ctor(char[])
  IL_006b:  call       string [mscorlib]System.String::Intern(string)
  IL_0070:  stloc.s    V_6
  IL_0072:  br.s       IL_0074
  IL_0074:  ldloc.s    V_6
  IL_0076:  ret
} // end of method ‘Global Functions’::Decrypt

.method public hidebysig static  void Main() cil managed
{
    .entrypoint  .maxstack 1 
    IL_0000:  ldstr bytearray(6E 00 76 00 7C 00 79 00 61 00 2A 00 79 00 76 00 76 00 6F 00 52 00) // “Hello World”
    IL_0005:  call string Decrypt(string)
    IL_000a:  call  void [mscorlib]System.Console::WriteLine(string)    
    IL_000f:  ret
}

 
24 pavyzdys: programos kodas atkūrimo analizei.
using System;

namespace misrus
{
    interface IAsmuo { }

    class Asmuo : IAsmuo
    {
        protected int asmKodas;
        protected string vardas;
        protected int amzius;
        public int asmensKodas
        {
            set { asmKodas = value; }
            get { return asmKodas;  }
        }
    }

    class Darbuotojas : Asmuo
    {
        protected string _pareigos;
        public string Pareigos
        {
            set { _pareigos = value;}
            get { return _pareigos == null ?
 “Bedarbis” : _pareigos; }
        }
        public Darbuotojas(string vardas, int amzius){
            this.vardas = vardas;
            this.amzius = amzius;
            Console.WriteLine(“Naujas asmuo sukurtas:\n” +
                            “Vardas: ” + vardas + “\n” +
                            “amzius: ” + amzius);
        }
        public double skaiciuotiAtlyginima(int dienu_sk)
        {
            double dienos = (double)dienu_sk;
            double atlyginimas = 0;

            if (dienu_sk<=31) {
                for (int i = 0; i< dienu_sk; i++ )
                {
                    atlyginimas += 20.25;
                }
            } else {
                atlyginimas = 31*20.25;
            }

            return atlyginimas;
        }
        public static Darbuotojas operator+ (Darbuotojas d1,
     Darbuotojas d2)
        {
            return new Darbuotojas(d1.vardas + d2.vardas,
d1.amzius + d2.amzius);
        }
        public string toString()
        {
            return this.vardas + ” ” + this.amzius;
        }

        public delegate void IspejimasCallBack(string pranesimas);
        public static void Ispejimas(IspejimasCallBack ispejimas)
        {
           
            ispejimas(“Atsargiai- jau nulis”);
        }
    }

    class Pagrindas
    {
        private static void atsisveikinti()
        {
            Console.WriteLine(“Viso geriausio”);
        }
        public static void IspetiCallback(string pranesimas)
        {
            Console.WriteLine(pranesimas);
        }
        static void Main(string[] args)
        {
            int i = 3;
            Darbuotojas[] darbuotojas = new Darbuotojas[4];
            Darbuotojas hibridas = null;

            try
            {
                while(i > -1) {
                    if (i == 0) {
    Darbuotojas.IspejimasCallBack manoIspejimas =
            new Darbuotojas.IspejimasCallBack(IspetiCallback);
    Darbuotojas.Ispejimas(manoIspejimas);
                    }

    darbuotojas[i] = new Darbuotojas(“vardas”+i.ToString(),(i+1)*10);
                    i–;
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(“Iskilo klaida: “+e.Message);
            }

            Console.WriteLine(darbuotojas[2].skaiciuotiAtlyginima(21).ToString());
            hibridas = darbuotojas[0]+darbuotojas[2];
           
            atsisveikinti();
        }
    }
}
25 pavyzdys: dekompiliuotas analizuojamos programos „Main“ metodas.

private static void Main(string[] args)
{
IspejimasCallBack back1;
Exception exception1;
int num1 = 3;
Darrbuotojas[] darbuotojasArray1 = new Darbuotojas[4];
Darbuotojas darbuotojas1 = null;
try
{
while ((num1 > -1))
{
if (num1 == 0)
{
back1 = new IspejimasCallBack(Pagrindas.IspetiCallback);
Darbuotojas.Ispejimas(back1);
}
darbuotojasArray1[num1] = new Darbuotojas(string.Concat(“vardas”, num1.ToString()), ((num1 + 1) * 10));
num1 -= 1;
        }
}
catch (Exception exception2)
{
exception1 = exception2;
Console.WriteLine(string.Concat(“Iskilo klaida: “, exception1.Message));
}
double num2 = darbuotojasArray1[2].skaiciuotiAtlyginima(21);
Console.WriteLine(num2.ToString());
darbuotojas1 = Darbuotojas.op_Addition(darbuotojasArray1[0], darbuotojasArray1[2]);
Pagrindas.atsisveikinti();
}

 
32 pavyzdys: Transformuotas kodas „XenoCode“ įrankio pagalba
public double 960894cda756b35c(int c4c0b0313768e587)
{
    double num2;
    int num3;
    double num4;
    double num1 = ((double) c4c0b0313768e587);
    goto Label_001D;

        Label_0005:
        num3 += 1;

        Label_0009:
            if (num3 >= c4c0b0313768e587)
            {
                goto Label_0019;
                do
                {
                    num2 = 627.75f;
 
                Label_0019:
                        num4 = num2;
                    goto Label_003E;

                Label_001D:
                    num2 = 0f;
 
                }
                while ((c4c0b0313768e587 > 31));
                num3 = 0;
                goto Label_0009;
 
            }
        num2 += 20.25f;
        goto Label_0005;
 
            Label_003E:
            return num4;
 
}

33 pavyzdys: Transformuotas “QND Obfuscator” tarpinis kodas
public double $(int $)
{
    int num3;
    double num1 = ((double) $);
    double num2 = 0f;
   
    if ($ <= 31)
    {
        for (num3 = 0; (num3 < $); num3 += 1)
        {
            num2 += 20.25f;
        }
 
    }
    else
    {
        num2 = 627.75f;
    }
    return num2;
}

35 pavyzdys: Transformuotas „Main“ metodas.
private static void c447809891322395(string[] ce8d8c7e3c2c2426)
{
  int i;
  misrus.d2072d59a7f5fff1[] arr;
  misrus.d2072d59a7f5fff1 d2072d59a7f5fff1;
  misrus.d2072d59a7f5fff1.74def51de52408a9 74def51de52408a9;
  System.Exception exception;
  System.Double dbl;
  i = 3;
  goto ILO_0023;
ILO_0005:
  dbl = 4;
  System.Console.WriteLine(dbl.ToString());
  goto ILO_00b9;
ILO_0023:
  arr = new misrus.d2072d59a7f5fff1[misrus.d2072d59a7f5fff1.op_Addition(arr[0], arr[2])];
  d2072d59a7f5fff1 = null;
  try
  {
    goto ILO_007b;
    goto ILO_004a;
    // Exception on 0035: Stack empty.,
// StackTrace:   at System.Collections.Stack.Pop()
//   at .
//.()
//   at .
.(_
 )
//   at .
.(ArrayList , ArrayList )
  }
  catch (System.Exception exception1)
  {
    exception = exception1;
    System.Console.WriteLine(string.Concat(string.Intern(misrus.d4485e5ae14ac128.cc381ffa3ede662f(“Óą°ŒįŒį§¼āƒ¼āŸ¼āŗŖć—²ć³°ä¸¢ä«§å‡Ÿå£™å¾Æęš’”, 559023260)), exception.Message));
    goto ILO_00aa;
  }
ILO_00aa:
  goto ILO_0005;
ILO_00b9:
  d2072d59a7f5fff1 = arr[2].960894cda756b35c(21);
  misrus.b2b82339fe9db79b.2b660887128cd4b3();
  return;
}
36 pavyzdys: fibonačio eilutės n-tojo nario skaičiavimo algoritmo programos kodas.
using System;

class Fibo
{
    private static int fib (int x)
    {
        if (x > 1)
            return (fib(x – 1) + fib(x – 2));
        else return (x);
    }

    public static int Main( String[] args)
    {
        int number = 0, value;
        System.DateTime pradzia;
        System.DateTime pabaiga;
        System.TimeSpan ms;

        try
        {
            number = Convert.ToInt32(args[0]); 
        }
        catch (Exception e)
        {
            Console.WriteLine (“Ivesties klaida”);
            return 1;
        }

        pradzia = System.DateTime.Now;

        value = fib(number);
        Console.WriteLine (“fibonacci({0}) = {1}”, number, value);

        pabaiga = System.DateTime.Now;
        ms = pabaiga.Subtract(pradzia);

        Console.WriteLine(ms);
        return 0;
    }
}

37 pavyzdys: ciklams analizuoti naudotas programos kodas.
using System;

class Class1
{
    static int Main(string[] args)
    {
        int number = 0;
        int i;
        int sk=0;
        System.DateTime pradzia;
        System.DateTime pabaiga;
        System.TimeSpan ms;

        try
        {
            number = Convert.ToInt32(args[0]); 
        }
        catch (Exception e)
        {
            Console.WriteLine (“Ivesties klaida”);
            return 1;
        }
        pradzia = System.DateTime.Now;
       
        for (i=0; i<number; i++) { sk++; }
        while (i>0) { i–; }
        do{ i++; } while(i<number);

       
        pabaiga = System.DateTime.Now;
        ms = pabaiga.Subtract(pradzia);

        Console.WriteLine(ms);
        return 0;
    }
}

38 pavyzdys: Operacijų su simbolinėmis eilutėmis analizuoti naudotas programos kodas.
using System;

class Class1
{
    static int Main(string[] args)
    {
        int number = 0;
        int i = 0;
        string eilute = “Operaciju su simbolinemis eilutemis analizavimas”;
        string uodega = “uodega”;

        System.DateTime pradzia;
        System.DateTime pabaiga;
        System.TimeSpan ms;

        try
        {
            number = Convert.ToInt32(args[0]); 
        }
        catch (Exception e)
        {
            Console.WriteLine (“Ivesties klaida”);
            return 1;
        }

        pradzia = System.DateTime.Now;
               
        do{
            eilute.Insert(eilute.Length-1,uodega);
            eilute.Remove(2,6);
            i++;
        } while(i<number);
       
        pabaiga = System.DateTime.Now;
        ms = pabaiga.Subtract(pradzia);

        Console.WriteLine(ms);
        return 0;

    }
}