JavaSvet - otvorena java zajednica

 
glavna stranica arr2javasvet  english version arr2java.net

Reverse Engineering: Bytecode manipulacija

Igor Spasić
18 Jan 2005
Sadržaj:

Predhodni članak je objasnio pojam obfuskacije Java koda, dao pregled tehnika koje se koriste za zaštitu Java programa i pokazao kako se jedna takva komercijalna zaštita može prevazići ako joj se pristupi na odgovarajući način.

Iako je pokazni primer iz prethodnog članka koristio relativno dobru zaštitu, korišćeni obfuskator nije modifikovao klase tako da se one posle dekompajliranja ne mogu ponovo kompajlirati. Tačnije, obfuskator je to ipak radio, ali na trivijalan način, tako da se posle par prostih izmena u dekompajliranom sorsu (zamena redosleda Java instrukcija) dobija sors koji se bez problema može menjati i potom ispravno kompajlirati. Kao što je rečeno, današnji obfuskatori posvećuju mnogo više pažnje baš ovom aspektu zaštite, trudeći se da dobijeni dekompajlirani sors učine što je moguće više nerazumljivim i nemogućim za ponovno kompajliranje.

Ovaj članak se bavi upravo takvim zaštitama, koje je jedino moguće prevazići na nivou bajtkoda. To je i krajnji nivo do koga se uopšte može ići sa zaštitom i sa reverse engineeringom koda u Javi, pa mu treba posvetiti više pažnje. Zato će ovaj put biti opisana dva pokazna primera. Inače, oba primera su uzeta baš za potrebe članka, tj. autor nije ranije prevazišao zaštite, već je to rađeno u toku pisanja članka, kako bi stvari bile što je više moguće fer i objektivne.

Alat

Serija članaka "Reverse Engineering":
1. Disasembliranje Java koda
2. Bytecode manipulacija

Ovo je možda zgodno mesto za predstavljanje potrebnog "alata", osim Jave. Total Commander se pokazao nezamenljivim zbog lake manipulacije s (jar) arhivama, inteligentnom komparacijom fajlova, odličnom pretragom i generalno lakim radom s fajl sistemom. Potreban je i dobar editor koji može da radi sa velikim fajlovima (na pr. Ultra Edit). I na kraju, nezaobilazan je i hex editor (na pr.: WinHex).

Od java programa tu je poznati jad dekompajler i njegov GUI DJ Java Decompiler (u daljem tekstu samo: DJ). Za rad sa bajtkodom koriste se biblioteke: KJC, BCEL i Jmangler, o kojima će kasnije biti više reči.

Napomena

Iz razloga da se ovaj članak ne shvati drugačije, sledi autorova napomena/izjava:

Članak je napisan jedino i isključivo za edukativne potrebe i u cilju istraživanja sigurnosti Java sistema. Autor se ograđuje od bilo kakve zloupotrebe i štete koja je slučajno ili namerno nastala na osnovu članka i/ili korišcenjem izloženih tehnika.

Isto tako, autor neće odgovarati na pitanja i zahteve koji su u suprotnosti sa gornjom izjavom.

PREDIGRA

Prvi primer je malo zagrevanje koje služi da pokaže postupke u slučaju naprednijeg obfuskatora koji menja bajtkod tako da ga je nemoguće ponovno kompajlirati.

Opis zaštite

Za prvi primer je izabrana backgammon igrica, i to jedna od najboljih (ako ne i baš najbolja) implementacija ove igre: BGBlic (napomena: ispravno ime igre ima u nazivu 'tz' umesto poslednjeg slova 'c'). BGBlicov obfuskator menja imena klasa, paketa, metoda i promenljivih, čineći kod teškim za praćenje. Nova, obfuskovana imena klasa i paketa su reči sastavljene od minimalnog broj slova. Za obfuskovana imena metoda i promenljivih se intezivno koriste ključne reči Java jezika (byte, new, itd.). Ovim se ne samo otežava dekompajliranje klase, već i potpuno i apsolutno onemogućava njeno ponovno kompajliranje.

Dalje, stringovi unutar klasa su ili kriptovani, ili izmešteni iz klase u kojoj se koriste, ili razbijeni na podstringove/karaktere koji se na mestu korišćenja sabiraju, tako da se prostim gledanjem sadržaja fajlova klasa ne može uočiti bilo šta pametno. Informacija o licenci se nalazi u dobro kriptovanom fajlu (DSA algoritam) koji je u istom folderu gde i program. Običan, neregistrovani korisnik ima ograničen pristup programu i njegovim naprednijim funkcijama, osim par puta na početku. To dodatno otežava stanje stvari, jer se ograničenja javljaju na više mesta i za više funkcija programa. U "Help/License..." dijalogu piše da postoji 3 tipa licenci, od kojih se profesionalna licenca čini najsveobuhvatnijom. "Help/About..." dijalog prikazuje trenutni tip licence (Evaluation). Ukupan broj klasa u sistemu nije velik: 672.

U programu, dakle, ne postoji probni period, već se ograničava njegova upotreba, što je drugačiji slučaj od prethodnog primera. Znači, ovaj put je cilj aktivirati licencu za profi korisnike.

Hvatanje

Najteži deo prevazilaženja zaštite je naći mesto gde se krije logika koja obezbeđuje zaštitu. "Hvatanje" je izmišljeni termin (autor očigledno nema baš bujnu maštu:) koji opisuje upravo to: pronalaženje kritičnog ("vrućeg") mesta aplikacije sa stanovišta zaštite - klase koje enkapsuliraju logiku kojom se ispituje i validira zaštita.

U ovom primeru, postojanje licencnog fajla prosto bode oči. Sledeći logiku iz prethodnog članka, prva stvar koja pada na pamet je trejsovanje rada aplikacije u slučaji kada licencni fajl postoji i kada on nedostaje. "Trejsovanje" je pojam koji se uopšteno odnosi na praćenje toka izvršavanja programa. U prvom slučaju, kada je licenca tu, aplikacija se startuje normalno. U drugom slučaju, kada nema licencnog fajla, aplikacija javlja grešku i automatski se gasi.

Međutim, ovde čak ne treba primeniti ni trejsovanje! Sam program generiše "log.txt" fajl u kome se u slučaju kada nema licencnog fajla pojavljuje sledeće:

ERROR [Protector] - initialize Protector
java.security.SignatureException: L_NIX
	at bgblic.hecke.l.byte(License.java:150)
	at bgblic.hecke.m.<clinit>(Protector.java:55)
	at bgblic.hecke.g.U(Installation.java:87)
	at bgblic.hecke.g.<clinit>(Installation.java:43)
	at Bgblic.<init>(Bgblic.java:656)
	at Bgblic.main(Bgblic.java:3075)
	...

ERROR [AboutBox] - License error!!!
java.security.SignatureException: L_NIX
	at bgblic.hecke.l.byte(License.java:150)
	at bgblic.ad.<init>(AboutBox.java:180)
	at Bgblic.<init>(Bgblic.java:709)
	at Bgblic.main(Bgblic.java:3075)
	...

Očigledno je bgblic.hecke.l.byte jako dobar kandidat za "vruće" mesto.

Alternativno hvatanje trejsovanjem

Da program nije generisao log fajl, trejsovanjem bi se došlo do iste pretpostavke. Evo zato prikaza i ovog načina hvatanja, koje će dati isto vruće mesto.

Upoređivanjem trejsova u jednom i u drugom slučaju, nalaze se mesta granjanja koja predstavljaju potencijalna "vruća" mesta. Evo prvog takvog mesta

1) Licenca je ok:
|:|>bgblic.hecke.l.byte()
...
|:|:>bgblic.hecke.l.(java.lang.String)
  2) Licenca ne postoji:
|:|>bgblic.hecke.l.byte()
...
|:|*bgblic.hecke.l.byte

U drugom slučaju, program ulazi u metodu bgblic.hecke.l.byte() (primetiti ime metode koje je obfuskovano) iz koje izlazi zbog exceptiona, što predstavlja razliku od prvog slučaja. Prva sledeća trejs razlika se dešava u istoj ovoj metodi iz koje se u drugom slučaju vrlo brzo izlazi isto zbog exceptiona što odmah dovodi do gašenja aplikacije.

Više nego očigledno je da je klasa bgblic.hecke.l "vruće" mesto. Ako se klasa dekompajlira, otkriva se da obiluje čitanjem/pisanjem u fajl (verovatno licencni), da koristi Cryptix paket (verovatno kriptovanje licence), a pojavljuje se SecurityException itd. Vreme je za analizu.

Analiza

Analiza koja sledi sigruno nije i jedini put kako doći do rešenja. Proces analize koda zahteva stalno formiranje novih pretpostavki u vezi zaštite sistema. Proverom pretpostavki se polako sužava krug izbor, sve dok se ne nađe tačno mesto koga treba menjati. Ovde ulogu igraju zdrav razum i rasuđivanje i ponešto iskustva u analizi sorseva.

Površnim pregledom bgblic.hecke.l.byte() i ostatka klase se ustanovljava da ona zaista radi sa čitanjem/pisanjem licencnog fajla i njegovom validacijom. Verovatno postoji način da se izmenom ove metode dođe do rešenja. Međutim, izgled metode je nagnao autora da potraži problem na drugom mestu. U vezi toga, uvek vredi pogledati okolne metode i klase, a pogotovo klase koje koriste i pozivaju "vruću" klasu (kao što je ovde to klasa m, na primer).

Evo u čemu je stvar. "Vruće" mesto je najčešće neko logičko granjanje ("pitalica") od kojeg zavisi da li program ide na jednu ili na drugu stranu. U velikom broju slučajeva to radi posao. Međutim, ne tako retko program ima i zaštitu u vidu "nivoa" pristupa, kao što je to i ovde slučaj. Logično je očekivati da negde u programu postoji mesto gde se ta informacija čuva. Zato, umesto da se menja pitalica, bolje je promeniti sam "izvor": mesto koje vraća informaciju! Umesto da se čuva informacija da je korisnik na probnom nivo, napravi se takva izmena da program "misli" da je korisnik na najvišem korisničkom nivou. Krajnji oblik ovog principa izmene "izvora" je pronalaženje cele logike zaštite i pravljenje tkzv. "key generatora" - nezavisnog programa koji bi generisao ispravne licence. Jasno je da je izmena izvora bolje rešenje. Trivijalan primer je kada program više puta u toku rada proverava licencu. Umesto da se sva ta mesta provere (pitalice) traže i menjaju, dosta je izmeniti samo izvor informacije tako da daje lažne. Na žalost, kako to obično biva, izmena izvora je teža i zahteva više truda.

Posle kraće analize, nalazi se da je podjednako interesantna klasa bgblix.hecke.m koja u toku svog izvršavanje poziva gore pomenutu. Dekompajliranje otkriva se zanimljiv deo koda:

public static boolean a(f f1) throws k {

...     switch(_fldgoto._mthchar()) {
       
case 2: // '\002'
       
case 17: // '\021'
           
if (integer.intValue() <= 2) return true;
           
break;
       
case 3: // '\003'
           
if(integer.intValue() <= 3) return true;
           
break;
       
case 63: // '?'
           
if(integer.intValue() <= 63) return true;
           
break;
        ...

Digresija: gornji sors je dobijen pomoću DJ-a. Promenljive _fldgoto i metod _mthchar() se ne zovu zaista tako, već goto i char(), respektivno. Ovo je upravo primer obfuskacije kada se originalna imena zamenjuju ključnim rečima Jave. DJ dodaje prefikse ("_mth" za metode i "_fld" za promenljive/atribute klase) kako bi dekompajliran fajl mogao bar da se čita iz java-enabled okruženja, iako time klasa prestaje da bude kompatibilna sa ostatkom programa.

Ovo liči na pitalicu, kojom se ustanovljava korisnički nivo, pogotovo kada se zna da u programu postoje 3 zvanična nivoa. Sors metode _mthchar() (tj. char(), obfuskovano) je u već nađenoj klasi bgblic.hecke.l, što navodi na pretpostavku da klasa l služi i za čuvanje, a ne samo manipulaciju podataka u vezi licence:

public byte _mthchar() {
   
try {
       
if (bgblic.hecke.h._mthif(_mthgoto())) {
           
_fldelse.error("IL_RE");
           
byte byte0 = 1;
           
return byte0;
       
}
       
if(_mthcase() < bgblic.hecke.m._mthnew()) {
           
_fldelse.error("L_REDE");
           
byte byte1 = 1;
           
return byte1;
       
} else {
           
byte byte2 = _fldnew;
           
return byte2;
       
}
    }
catch(k k1) {
       
...
   
}
}

Kritičan je treći slučaj, jer jedino on nema poziv metode error(). Tada se prosleđuje vrednost polja _fldnew (tj. new, obfuskovano).

PRETPOSTAVKA: ako _fldnew ima vrednost 3, onda je program u professional modu.

Digresija: možda se iz teksta ne vidi direktna veza i tok razmišljanja koji je doveo do navedenih pretpostavki i zaključaka. Na žalost, tu nema šta više reći, oni su deo analize koda i nešto malo iskustva, tako da nešto što se jednom čitaocu čini logičnim, drugom to možda nije. Jedino rešenje je probati samostalno analizirati klase, pošto način razmišljanja zavisi od celokupnog izgleda klase i metoda sistema.

Otključavanje

Jedna ideja je da se negde u kodu properti bgblic.hecke.l.new klase setuje na konstantnu vrednost 3. To zahteva dužu analizu, da bi se sprečilo da program u međuvremenu ponovo ne promeni vrednost promenljive. Lakše je alternativno rešenje, pogotovo pošto je ovaj properti klase private: svuda gde se vraća vrednost ove promenljive, vratiti konstantu 3.

Primenjena obfuskacija apsolutno onemogućava bilo kakvu izmenu klase. Sve što preostaje je direktna manipulacija bajtkoda. Već pominjana KJC biblioteka donosi 2 korisna alata. Jedan je disasembler, koji generiše tekstualni sors fajl koji predstavlja definiciju klase iskazanu isključivo bajtkod instrukcijama. KJC izuzetno radi posao i, po mišljenju autora, bolji je od ostalih programa slične namene (kojih inače nema puno). Jedna od dobrih stvari KJC-a je što je format bajtkod sorsnog fajla sličan uobičajenom Java sorsu. Drugi program je, naravno, asembler (tj. kompajler), koji od ovakvog bajtkod sorsa generiše klasu. Bajtkod asembler ne vodi računa o mnogim stvarima koje uobičajen java kompajler vodi računa, već samo validira klasu na nivou bajtkoda. To otvara mesta za do sad nezamislive greške tipa nevraćanja rezultata sa return iako se to očekuje itd. Ovakve greše se primećuju tek u run-time-u. Ipak, upravo ovakva fleksibilnost omogućuje totalnu kontrolu bajtkoda, a time i samog Java koda.

Rad sa bajtkod sorsom nije težak i nije tema ovog članka. Čak i ako se ne zna tačno kako bi se pomoću bajtkoda napisao neki Java izraz ili deo Java koda, uvek postoji mogućnost da se to na sličan način uradi prvo u Javi, pa da se onda iskoristi dobijeni bajtkod primera, koji se sa manjim izmenama može iskoristiti. Jedna od najlepših stvari je to što za asembliranje ne treba podešavati nikakav CLASSPATH niti je potrebno da postoje sve zavisne biblioteke, već je dovoljno raditi isključivo sa samom klasom, bez obzira šta sve ona referencira i koristi! To značajno pojednostavlja proces asembliranja i pravljenje izmena.

Sledi prikaz dve potrebne izmena u klasi bgblic.hecke.l:

Izmena #1 - dekompajliran obfuskovan Java kod, logička izmena
Pre izmene:
public byte _mthchar() {
...
	} else {
		byte byte2 = _fldnew;
		return byte2;
	}
  Posle izmene:
	} else {
		return 3;
	}
Izmena #1 - Java bajtkod, konkretna izmena
Pre izmene:
_L22:	@aload		0
        @getfield	byte bgblic.hecke.l.new
        @istore		1
_L26:	@ireturn
  Posle izmene:
_L22:	@const		3
_L26:	@ireturn
Izmena #2 - dekompajliran obfuskovan Java kod, logička izmena
Pre izmene:
public byte _mthlong() {
	return _fldnew;
}
  Posle izmene:
public byte _mthlong() {
	return 3;
}
Izmena #2 - Java bajtkod, konkretna izmena
Pre izmene:
@signature "()B"
public byte long() {
	@line	191
	@aload		0
	@getfield	byte bgblic.hecke.l.new
	@ireturn
}
  Posle izmene:
@signature "()B"
public byte long() {
	@line	191
	@const	3
	@ireturn
}

Rezultat

Pokazuje se da je pretpostavka bila dobra i da gornja izmena dovodi do toga da igrica prepozna default licencu kao da je profesionalna, čime je korisniku dostupan pun potencijal programa. Podaci u "About" dijalogu su neki defaultni. Ukupno vreme za prevazilaženje zaštite je do sat vremena.

HARDCORE

Za poslednji primer ove serije tekstova, uzet je pravi izazov za reverse engineering. Prethodni primeri, iako nisu trivijalni i laki, nisu bili ni preteški, pogotovo kada im se pristupa na opisan način. Zato je primer koji sledi "tvrd orah" i zahteva mnogo više vremena, truda i upornosti.

Opis zaštite

Reč je o poslednjoj verziji (4.0) programa U M L paradigma (očigledno je kako glasi ispravan naziv). Paradigma je baš velika: kada se instalira zauzme 155 MB. Samo je glavni JAR fajl velik 40 MB, a sadrži ukupno 23842 klasa. Klase su obfuskovane. Prvo, ne postoje razgranati paketi, već se više od 17200 klasa nalaze samo u jednom. Imena klasa su imena sačinjena od što je moguće manje slova. Isto važi i za imena metoda, gde je što je moguće više metoda nazvano istim imenom. Stringovi od značaja su svi razbacani ili kriptovani. Obfuskacija koristi neke metode koji sprečavaju ponovno kompajliranje klasa. Dalje, pošto program može da komunicira sa raznim IDE, sadrži i kod koji referencira Eclipse, JBuilder, IDEA itd. pakete i klase. Uz program se dobija licencni fajl, u, kako izgleda, nestandardnom formatu. Postojanje ovog fajla je obavezno. Default licenca je za evaluation korisnike i važi 30 dana. Postoje različita izdanja/verzije programa, gde je professionlni sa najviše mogućnosti (košta oko 900 $US).

Cilj je osposobiti profi verziju, bez bilo kakvog ograničenja.

Analiza #1

Preogroman broj klasa nerazumljivih i izuzetno sličnih imena su već sasvim dovoljna zaštita i razlog da se sve batali i ode raditi nešto drugo (pametnije:).

Prva analiza je gledanje kako program radi. Učitani licencni fajl se, na prvi pogled, ne može naći, pa nema šta dirati kada se jednom prijavi u program, kao u prethodnom primeru. Dalje, na više mesta postoji informacija da je reč o Evaluation verziji (About dijalog, caption prozora...). Osim promene vremena, na prvi pogled se ne vidi drugi način kako je moguće izazvati da program javi da licenca više nije validna. Jasno, sve ide ka tome da se uradi već dobro poznato AspectJ trejsovanje rada programa u dva slučaja: kada probni period nije istekao i kada je on istekao.

Već na samom početku, javlja se problem: AspectJ javalja ogroman broj grešaka! Ista stvar se dešava i sa AspectWerkz-om i ostalim aspektnim kompajlerima, jer nije problem do njih, već u programu. Kako je već rečeno, Paradigma se može upotrebiti i kao plugin za različita okruženja, zbog toga ima deo koda koji se referencira na pakete tih okruženja. Kada aspektni kompajler naiđe na metodu koja za argument ima tip koji se ne vidi na CLASSPATH-u, kompajler javlja grešku. To je ono što se dešava i ovde.

Banalno rešenje bi bilo naći i staviti sve Jar-ove koji su potrebni na CLASSPATH. To podrazumeva da korisnik ima instalirana sva podržana okruženja! Jako nepraktično.

Drugo rešenje je locirati i izdvojiti iz apsektnog kompajliranja sve klase koje pucaju prilikom kompajliranja. Iako je malo verovatno da se zaštita nalazi i u tim klasama, ta mogućnost postoji. Dalje, broj takvih klasa nije mali, pa je potrebno puno vremena samo ih naći među tih 23800 klasa. Ovo je moguće rešenje, ali je još daleko od praktičnog.

Ako alat ne radi posao, treba ga promenti. Aspektni kompajleri su ipak napravljeni za druge stvari.

Doktor Mangler...

Potreban je alat koji radi sličnu stvar kao do sada AspectJ: da omogući trejsovanje izvršavanja programa. Ovaj alat bi trebalo da prođe svaku klasu i da na početku i kraju svake metode ubaci odgovarajući poziv statičke metode neke Tracer klase koja radi ispis u fajl.

Za početak je potrebno ostvariti izmenu bajtkoda klase na fajl sistemu. Danas postoji više biblioteka za modifikaciju bajtkoda: ASM, BCEL, Jassist... Pažnju privlači JMangler koji interno koristi BCEL. Među primerima JMangler ima već jednostavnu implementaciju offline Transformer-a koji upravo radi trejsovanje. Time je veliki deo koda već odrađen, pa ostaje još samo ga prilagoditi.

Početni transformer iz primera uzima rekurzivno sve class fajlove iz nekog foldera i dodaje korisnikov kod na početku, na regularnom izlazu i na izlazu pri bacanju exceptiona. Originalni primer je promenjen tako da je kod koji se injectuje u metode u stvari poziv statičkih metoda neke Tracer klase, smeštene u rut Jar-a, tj. CLASSPATH-a. Dalje, u opis metode koja se poziva dodat je i spisak njenih argumenata. I poslednja stvar, dodato je hvatanje jako retkih exceptiona, koje BCEL zna da napravi zbog jednog svog baga.

Ovako napravljen transformer se može pozvati preko JManglera. Nakon par proba se pokazuje da radi odlično. Jedini nedostatak je to što ne obrađuje i jar fajlove, ali to je u redu s obzirom da je u transformer sklepan za 10 minuta.

Postupak injectovanja bio bio sledeći:

  1. otpakuju se sve klase iz jara u neki folder,
  2. JMangler pomoću napravljenog transformera injectuje pozive Tracer metoda,
  3. izmenje klase se vrate nazad u jar,
  4. u rut jar-a se doda klasa Tracer (ako već ne postoji).

Total Commander dosta pomaže u ovom postupku.

... i novi Tracer

TraceAspect koji je korišćen u primeru s AspectJ-om pati od nekih nedostataka koji u ovako velikom programu kao što je Paradigma izuzetno značajno utiču na potupak trejsovanja.

Prva je u pitanju brzina. Ako se trejs fajl svaki put otvara i zatavara prilikom upisa samo jednog trejsa, startovanje i rad velikog programa kao Paradigma traje skoro večno. Probe radi, na dobroj mašini startovanje programa sa ovakvim trejsovanjem je trajalo oko 30 minuta. Jako nepraktično!

Druga stvar su threadovi. Ako svi tredovi pišu u isti fajl, vrlo je verovatno da se to neće dogoditi svaki put u istom trenutku. To bi kao posledicu dalo različite trejs fajlove, a koji u suštini nisu različiti!

Zato je napravljen novi Tracer koji vodi računa o navedenim problemima. On koristi HashMap da čuva jednom na početku otvorene output streamove i trenutnu dubinu poziva za svaki aktivni thread posebno. Trejs se snima u poseban fajl za svaki thread. Dodato je i lepše prikazivanje dubine poziva, za lakše vizuelno praćenje.

Znači, ništa posebno, samo se vodilo računa da kod Tracer-a bude što efikasniji.

Trejsovanje i analiza #1

Kada se konačno injektuju sve klase programa po opisanom metodu ostaje trejsovati tok rada programa u slučaju kada je vreme ok i kada je probni period prošao.

Prvo, iako značajno ubrzano, trejsovanje je i dalje jako sporo. Programu treba dosta vremena da se pokrene. Međutim, ono što plaši su trejs logovi veličine 75-80 MB! Srećna okolnost je što se za većinu (manjih) threadova trejs log ne razlikuje. Međutim, glavni thread generiše oko 40 MB trejsa, sa skoro 2 miliona trejs linija i sa velikim brojem razlika. Posle puno vremena gledanja razlika trejsova autor nije uspeo ništa da zaključi, a kamoli da formira neku pretpostavku od koje bi se krenulo. Jako poražavajuće, u svakom slučaju, i nimalo bliže rešenju. Trejs je toliko veliki da je skoro neupotrebljiv za praktičnu primenu.

Licencni fajl

Očigledno, vremensko ograničenje nije dobra polazna pretpostavka. Prva sledeća stvar je licencni fajl. U Paradigmi postoji "Key Manager" - deo programa koji kroz dijalog prikazuje informacije o svim aktivnim ključevima i omogućava njihovo dodavanje i brisanje. Ideja je da se programu poturi nevalidan licencni fajl i da se izvrši trejsovanje za taj slučaj.

Najlakše bi bilo nasumično izmeniti originalni licencni fajl za evaluation korisnika ako se posmatra kao i svaki fajl na disku. Međutim, prvim pregledom fajla se uočava sledeće:


Header fajla jako liči na ZIP format ("PK" upada u oči). U stvari, čini se da to jeste ZIP format, sa tom razlikom što ima još 2 prefiksna bajta, dodata pre samog ZIP sadržaja. Ova dva prefiksna bajta služe verovatno za dodatnu proveru checksum-a licencnog fajla. Inače, ZIP format ima smisla koristiti, jer je on Javin prirodan format, tako da je ova pretpostavka zaista možda tačna.

Kada se "odseku" prva dva bajta i tako modifikovan fajl snimi, sledi konačno prva dobra vest: modifikovan fajl je zaista ZIP format, koji sadrži gomilu VPL fajlova, koji su predstavljeni u XML formatu. Pregledom sadržaja ovih fajlova, otkriva se da svaki od njih sadrži informacije za pojedinu vrstu proizvoda, sa svim podacima o korisniku. Naročito su zanimljiva sledeća polja: ExpireDate, VerifyKey i Evaluation.

Ne bi bilo loše izmeniti i lične podatke u svim licencama. Na kraju se sve licence ponovo zipuju, a fajlu nove arhive se doda 2 slučajno izabrana bajta na njen početak. Pošto logika izračunavanja vrednosti ova 2 bajta nije poznata, sve je jedno šta se stavi pošto će ionako biti pogrešno.

Ovakva izmena licencnih fajlova može bar malo da pomogne: ako bude kako treba, može se desiti da na mestu provere treba promeniti manji broj stvari nego kada bi licenca ostala kakva je bila. Nije velika pomoć, ali svakako je ne treba zanemariti.

Trejsovanje #2

Novi krug trejsovanja se odvija po sledećim koracima. Pri tome je neophodno imati dve verzije glavnog Jar-a, jedna koja je originalna, a druga koja je izmenjena sa uključenim trejsovanjem.

Za samo ova četiri koraka treba odvojiti dosta vremena, pošto startovanje programa sa uključenim trejsovanje traje prilično dugo.

Analiza #2

Ukupna veličina trejsovanih fajlova je nesmanjeno zastrašujuća, oko 77 MB. Ipak, ovog puta su stvari drugačije. Prvo, trejs za "AWT-EventQueue-0" thread očigledno treba zanemariti, reč je o sistemskom threadu. Drugo, komparacijom se uočava da glavni thread ("main") generiše isti trejs u oba slučaja! To je ubedljivo najveći trejs od 40 MB koji se sada može potpuno isključiti iz analize, jer se, očigledno, provera ne dešava tokom njegovog izvršavanja.

Konačko se stvari kreću u pozitivnom smeru: daljim upoređivanjem se zaključuje da su praktično trejsovi svih threadova identični, osim za thread "Thread-2", kada je razlika značajno velika: u prvom slučaju je to fajl od svega 100 KB, a u drugom od čak 4 MB.

"Thread-2" trejsovi se razlikuju na više mesta. Prvih nekoliko razlika su očigledno zanemarljiva. Prva značajna i netrivijalna razlika je upravo moguće vruće mesto. Još jedna činjenica koja ide u prilog tome je i to što se thread u slučaju kada je evaluation licenca valida završava vrlo brzo posle razlike. U drugom slučaju izvršavanje threada je još uvek trajalo i u trenutku kada je program bio nasilno ubijen.

Evo i tog mesta razlike (iz trejsa su izuzeti tipovi argumenata pozivanih metoda kako bi fajl bio manji):

1) Evaluation licenca:
|:|:|:|:<v.wwx.o
|:|:|:|:>v.cnx.f
|:|:|:|:<v.cnx.f
|:|:|:|:>v.cnx.r
|:|:|:|:<v.cnx.r
|:|:|:|:>v.yhi.k
|:|:|:|:<v.yhi.k
|:|:|:|:>v.yhi.i
|:|:|:|:<v.yhi.i
|:|:|:|:>v.yhi.b
|:|:|:|:<v.yhi.b
|:|:|:|<v.wwx.i
  2) Izmenjena licenca:
|:|:|:|:<v.wwx.o
|:|:|:|:>v.cnx.f
|:|:|:|:<v.cnx.f
|:|:|:|:>v.cnx.r
|:|:|:|:<v.cnx.r
|:|:|:|:>v.yhi.k
|:|:|:|:<v.yhi.k
|:|:|:|<v.wwx.i
|:|:|:|>v.hio.r
|:|:|:|<v.hio.r
|:|:|:|>v.rlc.i
|:|:|:|:>v.uqt.f

Razlika je ta što se u toku izvršavanja metode v.wwx.i u drugom slučaju izlazi iz metode odmah posle poziva metode v.yhi.k. U prvom slučaju, v.wwx.i traje i dalje. Evo koda v.wwx.i metode:

public yhi i(String arg0, String arg1, long arg2) {
   
o();
   
for (Enumeration enumeration = o.elements(); enumeration.hasMoreElements();) {
       
yhi yhi1 = (yhi) enumeration.nextElement();
       
if (yhi1.f().equals(arg0) && yhi1.r().equals(arg1) && yhi1.k()) {
           
if (yhi1.i() && yhi1.b() > System.currentTimeMillis()) {
               
return yhi1;
           
}
           
if (yhi1.b() > arg2) {
               
return yhi1;
           
}
        }
    }
   
return null;
}

Razlika nastaje na prvom if upitu u for petlji. Poznata je osobina Java jezika da u se slučaju višestrukih logičkih izraza izvršavaju samo oni koji su potrebni. Tako, na primer, ako postoje dva logička izraza koji su vezani operatorom &&, prvo se izvršava prvi izraz. Ukoliko je on tačan, izvršava se i drugi; u suprotnom, uopšte ne dolazi do izvršavanja drugog logičkog izraza. Slično važi i za povezivanje s || operatorom: čim jedan od povezanih logičkih izraza vrati true prestaje dalje izvršavanje i ceo izraz postaje potvrdan.

Iz trejsa se zaključuje da u jednom i drugom slučaju metode iz prvog if upita: v.yhi.f().equals() i v.yhi.r().equals() vraćaju true. Razlika je sa trećom, poslednjom metodom: v.yhi.k(). Ona u prvom slučaju vraća true jer metoda nastavlja da se izvršava, dok u drugom slučaju vraća false i potom se izlazi iz petlje, pa iz metode.

Sledi disasembliranje v.yhi.k(), za koju se pokazuje da je elementarna:

public boolean k() {
   
return h;
}

Inače, sama klasa v.yhi izgleda sumnjivo, jer na par mesta barata sa klasom za vreme Date ponegde je kreirajući od vrednosti long tipa, što liči na podatke koji su bili upisani u licencne fajlove (broj milisekundi). Ovo je već dovoljno da se postavi razumna pretpostavka.

PRETPOSTAVKA: ako v.yhi.k() stalno vraća true, program će se ponašati približnije prvom, regularnom, slučaju.

Atribut h je public, pa nije dovoljno samo prosto vratiti true, jer se može očekivati da neko pristupi direktno atributu klase, a ne preko metode k(). Jedna prosta ideja je da se, na primer, pre nego što se vrati vrednost atributa h, on setuje na true:

public boolean k() {
   
h = true;
   
return h;
}

Odgovarajuća izmena bajtkoda:

Pre izmene:
@signature "()Z"
public boolean k() {
	@aload		0
	@getfield	boolean v.yhi.h
	@ireturn
  Posle izmene:
@signature "()Z"
public boolean k() {
	@aload		0
	@const 1
	@putfield	boolean v.yhi.h
	@aload		0
	@getfield	boolean v.yhi.h
	@ireturn

Rezultat

Kada se modifikovana klasa v.yhi ubaci u originalni Jar programa i kada se koriste modifikovani licencni fajl, program prepoznaje licencu kao validnu. Najbolje je što to važi i za sve modifikovane licence! Posle više sati, posao je konačno završen :)

Sors Tracera

Ovo je trenutna verzija korišćenog Tracer (700 KB) alata. Ako bude bilo vremena biće realizovane neke ideje o proširenju Tracera, da bi se omogućio lakši pregled i praćenje trejs fajlova (drvolika struktura, slično Eclipse-u), externo setovanje skupa metoda za injectovanje itd.