Jelikož je Java velmi jednoduše decompilovatelný jazyk, s obfuskací kódu se čas od času setká každý z nás – ať už z vlastního popudu, či jako zadání zákazníka. V dnešním článku se zaměříme na konkrétní nástroj pro obfuskaci a úpravu kódu – ProGuard. Jak si ukážeme, nástroj je to velmi užitečný a pomocí něho bude obfuskace vašeho kódu otázkou několika sekund.
Obfuskace
Pro ty z vás, kteří se s pojmem obfuskace setkávají poprvé, nejprve malé vysvětlení této techniky. Obfuskací zdrojového kódu rozumíme snahu o znemožnění analýzy kódu. Při tomto procesu dochází obvykle k přejmenování názvů tříd, metod a proměnných. Volitelně se pak obfuskátor může pokusit odstranit z vašeho kódu prázdné znaky či nepoužívané metody a proměnné.
Výsledkem je pak kód, který po dekompilaci nedává na první pohled smysl – ani obfuskace vám však nezaručí, že vám kód někdo neokopíruje. Podobně jako kód obfuskujeme, můžeme totiž provést i jeho deobfuskaci do čitelnější podoby, z které opět půjde získat přehled o logice vašeho kódu (nedostaneme ovšem nikdy původní tvar kódu).
Spolu s obfuskovaným kódem máme vždy k dispozici také převodní schéma obfuskace – tedy informaci pomocí které lze náš kód zrekonstruovat. Toto schéma je pak velmi vhodné například při analýze logů.
Ukázka obfuskovaného kódu:
public class b extends c { protected Boolean e; protected Boolean j; public b() {} public a a(a a1){ if(!i.booleanValue()) return a1; if(j == null || j != null && j.a(a1)) {if(a1 == null || a1.getSource() == null) return a1; if(e == null) e = Boolean.TRUE; if(e.booleanValue()) return c(a1); else return b(a1); } else { return a1; } }
Na začátek ovšem musím zdůraznit jednu velkou nevýhodu použití obfuskace společně se Springem a jeho XML konfigurací, či například s reflexí a metodami s ní spojenými. V tomto případě vznikají problémy – při přejmenování třídy/metody během obfuskace se musí změnit i její název v XML souboru (podobně tak příslušný setter či getter dané proměnné, kterou obfuskátor přejmenuje). Stejně tak pokud například vyhledáváte třídy podle jejich názvu, můžete se dostat do komplikací. S těmito problémy se buď vypořádáte (např. zákazem obfuskace daných tříd a metod) či se s nimi vypořádá obfuskační nástroj.
Nadále je třeba zmínit, že obfuskace se nehodí do testovacího prostředí. Důvodem je, že případné chyby jsou pak těžko dohledatelné, protože v logu aplikace najdete jen obfuskované třídy a metody, které budete muset složitě dohledat v schématu obfuskace. Pokud použijete i odstranění nadbytečných řádků a metod, nepomohou vám ani čísla řádků v kódu – ty se totiž budou lišit od těch skutečných.
ProGuard
ProGuard je jeden z nejznámějších obfuskátorů pro Javu – neposkytuje totiž pouze služby obfuskace, ale také umožňuje optimalizaci a zmenšení kódu či odstranění nepotřebných metod a proměnných. ProGuard je také standardní součástí sestavovacího mechanismu pro androidí aplikace. Obrovskou výhodou je možnost integrace do Mavenu – na samotný proces obfuskace pak můžete lehce zapomenout. Je vyvíjen pod licencí GPL.
Jak tedy zprovoznit ProGuard ve vaší aplikaci? Nejprve potřebujeme definovat build task do Mavenu (pomocí Antu):
<plugin> <artifactid>maven-antrun-plugin</artifactid> <dependencies> <dependency> <groupid>proguard</groupid> <artifactid>proguard</artifactid> <version>4.5.1</version> <scope>system</scope> <systempath>${pom.basedir}/resources/lib/proguard.jar</systempath> </dependency> </dependencies> <executions> <execution> <phase>package</phase> <configuration> <tasks> <taskdef resource="proguard/ant/task.properties" classpath="${pom.basedir}/resources/lib/proguard.jar" /> <proguard configuration="proguard.conf" /> < /tasks> </tasks></configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin>
Jediná zajímavá informace v této konfiguraci (kromě získání knihovny ProGuard z system scope) nás vede k souboru proguard.conf – tento soubor obsahuje kompletní nastavení ProGuard pro obfuskaci našeho kódu.
Pojďme se nyní podívat na jeho textovou strukturu (ano opravdu, ne žádné další XML):
-injars target/ourapp-nob.jar -outjars target/ourapp.jar -libraryjars <java.home>/lib/rt.jar -libraryjars <java.home>/lib/jsse.jar -libraryjars <java.home>/lib/jce.jar -libraryjars <java.home>/../lib/tools.jar -libraryjars resources/lib/ -libraryjars target/lib/ -printmapping target/out.map -dontshrink -dontoptimize -adaptresourcefilecontents **.xml
Na začátku konfigurace definujeme vstupní soubory pro obfuskaci. Těchto souborů může být více – v naše případě se jedná o soubor ourapp-nob.jar, který se bude tranformovat do ourapp.jar. Dále definujeme knihovny, které potřebuje naše aplikace. Jelikož sám obfuskátor využívá reflexe, musíme definovat opravdu všechny knihovny (i z JDK), protože jinak vám ProGuard vypíše chybu ohledně chybějících závislostí.
Dalším důležitým parametrem je printmapping, který určuje soubor se schématem obfuskace (pro případné vyhledávání názvů tříd, metod, …). Dále následují parametry pro zákaz optimalizace (odstranění nepoužívaných metod, …) a shrinkování (odstranění všech white space atd.). Jak bylo zmíněno výše, zapnutí těchto metod znemožní následné vyhledávání chyb.
Zcela posledním parametrem je pak zapnutí zpracování externích souborů – v našem případě to jsou XML soubory. ProGuard je prohledá a případně upraví v závislosti na změnách názvů tříd a další prvků.
Konfigurace obfuskace
Konfigurace nástroje ProGuard je poměrně jednoduchá – ukažme si ji na příkladu:
-keep class xxx.yyy.* -keepclassmembers class xxx.yyy.zzz* { private ** **; public ** **; protected ** **; private % **; public % **; protected % **; } -keepclassmembernames class xxx.yyy.** { void set*(***); *** get*(); }
V předchozí ukázce jsem vybral nejpoužívanější konstrukce s kterými se nejčastěji setkáváme. Konfigurace obfuskace je vlastně vyjmenováním případů, které nechceme obfuskovat. První příklad uvádí použití zachování konkrétních tříd z daného balíku. Třídám budou zachovány pouze jejich názvy – obfuskace bude provedena pro jejich metody a/i atributy (pokud tedy nebudou vyjmenovány v jiném případě – viz níže).
Další příklad ukazuje možnosti zachování všech metod tříd z daného balíku. Můžete zde vidět, že nás zajímají všechny metody/atributy objektu nehledě na jejich viditelnost, typ a název. Samotné názvy tříd budou normálně obfuskovány (pokud nejsou omezeny jinou podmínkou). Pro zajímavost je zde uveden jak příklad z zástupným znakem * znamenající objektový typ a se zástupným znakem % pro primitivní datové typy. Existuje i zástupný znak ***, kterým lze tyto dva zástupné znaky spojit.
Poslední příklad nám ukazuje právě zástupný znak *** a navíc zahrnuje i všechny podbalíčky daného balíku (tato konstrukce je užitečná, pokud používáte Spring a potřebujete zachovat set a get metody). Rozdíl mezi keepclassmembers a keepclassmembernames je v tom, že zatímco první zmiňovaný parametr zabraňuje i přesunu třídy (přejmenování balíku, v kterém třída je), druhý zajišťuje pouze to, že se zachová název třídy, přesunutí je možné.
Spuštění obfuskace
Jakmile spustíte obfuskaci – je součástí package fáze buildu mavenu, vytvoří se soubor out.map, který slouží pro informaci o obfuskaci. Najdete v něm přesný postup obfuskace – jaké třídy/metody/atributy byly nahrazeny, jaké zachovány a pod jakými názvy je musíte ve své aplikaci hledat. Tyto informace jsou užitečné zejména při analýze problému v produkčním prostředí. Zmiňovaný soubor vypadá následovně:
xxx.yyy.MyClass -> xxx.a.c: java.util.List myList -> a java.util.List getMyList() -> getMyList java.lang.String toString() -> toString
Na této krátké ukázce vidíme, že došlo k nahrazení názvu třídy, názvu balíku a názvu atributu třídy. Ostatní metody třídy zůstaly zachovány.
Závěr
Jak sami vidíte, obfuskace není žádná magie a při správném nastavení vám umožní rychle skrýt vámi napsaný kód před nenechavci. Ochrana samozřejmě není všemocná – kód je i nadále decompilovatelný a útočník může i tak váš kód analyzovat – bude mu to ale určitě trvat déle. Jak jsem zmínil na začátku obfuskace může přinést i nemalé potíže pokud používáte Spring, reflexi či konfiguraci pomocí XML – v tomto případě musíte dbát i na správnou záměnu názvů či obfuskaci na daných třídách/metodách/atributech zakázat.
Dejte nám vědět do komentářů, jaké máte zkušenosti s obfuskací a jaké nástroje používáte.
23.8.2011 at 19:27
Používal jsem Proguard na Java ME projekty. Hlavní motivací bylo zmenšení kódu, což fungovalo velmi dobře (aplikace se zmenšila z 500 kB až na asi 150 kB a to je pro starší mobily s pár megabajty paměti dost podstatné). Obfuskoval jsem dokonce i použitou knihovnu LWUIT a nebyl s tím žádný problém. Jak je psáno v článku narazil jsem na problémy s reflexí, které jsem vyřešil neobfuskováním problémových tříd.
26.8.2011 at 11:39
my v praci koc v C++ obfuskovat nemusime, vypada tak uz od vytvoreni :-) :-(