ugrás a tartalomhoz

PHP obfuszkátor

bbalint · 2004. Ápr. 27. (K), 22.00
PHP obfuszkátor
Akik érdeklődő figyelemmel olvasták a PHP Tokenizer kiterjesztéséről szóló korábbi cikkemet, azoknak minden bizonnyal érdekes csemege lesz az ott bemutatottak felhasználása a kódszépítés helyett a forrás összevazarására, azaz obfuszkálására. Habár kész obfuszkátorok sőt PHP forrás kódolók is rendelkezésre állnak, érdemes egy kicsit elmerülni egy lehetséges megvalósítás részleteiben. Cikkemben ezúttal be szeretnék mutatni egy elég egyszerű obfuszkátort, amely sokmindenki hasznára válhat.

Mi is az obfuszkálás?

Az obfuszkálás, angolul obfuscation, tulajdonképpen összezavarás, keverés-kavarás - jelen esetben a PHP kód összezavarásáról van szó. Itt nem arra kell gondolni, hogy fogunk egy habverőt és azzal jól összekavarjuk a szkriptünket, hogy az többet ne legyen működőképes. A kód funkcióját meg szeretnénk tartani, csupán "olvashatatlanná" szeretnénk tenni azáltal, hogy az eredeti kódban fellelhető (remélhetőleg) beszédes elnevezéseket lecseréljük valami zagyvaságra. Nézzük az alábbi egyszerű példát:

<?php
// eredeti kód
function EzAFüggvényKiírEgyÚjSorkaraktert()
{
  print(chr(10));
}
?>
Egy kiadós obfuszkálás után a megjegyzések eltüntetésével, az elnevezések megváltoztatásával:
<?php function __124G_gf(){print(chr(012));} ?>
Remélem, nem én vagyok az egyetlen, aki számára könnyebben érthetőbb, olvashatóbb volt az eredeti változat...

Mégis mire jó az összezavarás?

Bosszantani a többi fejlesztőt, védeni a programkódunkat, futtatási időt spórolni.

Az obfuszkálás nagyon zavaró, mivel például véletlen karakterekből összeállított elnevezéseket igen nehéz észben tartani, vagy állandóan azt keresgélni, hogy a $yGf78_a vagy pedig a $yGf78_A változó tartalmazza a hibakódot (vagy bármit, ami csak fontos lehet). Az összevarás az elnevezések nehezebben olvashatóvá tételével is operál - elnevezés alatt minden változó-, index-, állandó-, függvény- és objektumnevet értve.

Aki nem szeretné, nincs szüksége rá vagy nem áll szándékában, hogy mások által is olvasható (érthető, továbbfejleszthető) legyen a forráskódja, az nyugodtan obfuszkálhatja kódját.

Szkripjeinket az érdeklődő felhasználók elől rejtve lehet hagyni azáltal, hogy ha még értik is a PHP-t valamennyire - tehát a látottakkal, a kódok alapján beindul a pavlovi reflex és ők is mennek például portálrendszert írni -, hiába olvasnak sok-sok összefüggéstelen szöveget, nem fogják megérteni.

Közel olyan hatást lehet elérni - jól megírt PHP programmal és obfuszkátorral -, hogy egy szöveges PHP állomány nagyban hasonlítani fog egy-egy bináris állományra!

Ráadásul "időt" is lehet az obfuszkátorral spórolni. Azért csak "időt", így idézőjelesen, mivel a PHP programok igen gyorsak szoktak lenni (még a sok-sok adatbázis lekérdezéssel és állományművelettel együtt is), hogy ezzel a módszerrel szerintem legfeljebb csak ezredmásodperceket lehet spórolni - persze, kellően rosszul megírt programok esetén az időmegtakarítást lehet akár másodpercben is mérni.

Viszont, ha belegondolunk, hogy ez egy gyakran használt PHP alkalmazás esetén gyorsan összeadódik, akkor a végén egész órákat takaríthatunk meg azzal, hogy minden elnevezést egy karakteresnek választunk meg. Ez persze a fejlesztést nem segítené, ezért bár nem ilyen formában fejlesztjük kódunkat, a végleges lefuttatott változatot összezavartan is használhatjuk. Ezáltal lemez- és memóriaterületet, ill. az azokkal járó műveleteket takaríthatjuk meg!

Mit érdemes?

Tulajdonképpen mindent. Ha esetleg még valamennyire szeretnénk használni az obfuszkált kódot - például külső include állományként -, akkor szerintem a függvényneveket ki lehet hagyni, hogy azok továbbra is jól olvashatóak legyenek...

Hogyan lehet?

PHP-hoz írtak már néhány obfuszkátort. A teljesség igénye nélkül két példa:
A meglévő megoldások nem voltak számomra megfelelőek, ezért saját obfuszkátort fejlesztettem ki. Az alternatív megoldás, melyet én is használok (és ajánlok) ill. a következőkben bemutatok, a már említett Tokenizer kiterjesztésre épül. Ez a kiegészítés egy nagyon egyszerű de nagyszerű funkciót biztosít. Egy tömbben visszaadja a megadott PHP-kód "bináris" mását, tehát a Zend opkódokat, azokat a dolgokat, amiket tulajdonképpen a PHP magja a Zend Engine értelmez. Azáltal, hogy nekünk is megvan ez az elemzett kód, kicsemegézhetjük belőle a megfelelő dolgokat és kedvünkre átformálhatjuk.

A program

Az alábbiakban egy nagyon egyszerű obfuszkátor látható, PHP nyelven megvalósítva. A futtatásához szükség van a tokenizer kiterjesztésre, mely a PHP 4.3.0 óta alapértelmezésben rendelkezésre áll.

<?php
// $filename tartalmazza az obfuszkálni kívánt file
// nevét, melyből beolvassuk az állomány teljes tartalmát
$file = implode('', file($filename));

// ezekben tároljuk a már "átváltozott" elnevezéseket
$Függvénynevek = $Változónevek = $Osztálynevek = array();

// a beépített függvények neveit nem szabad (TILOS!) obfuszkálni
$Függvények = get_defined_functions();

// ezért hozzá kell adni őket a Függvénynevekhez, hogy bajuk ne essen
foreach($Függvények['internal'] as $Függvény) {
  $Függvénynevek[$Függvény] = $Függvény;
}
    
// elvégzi az elnevezések konvertálását, és megadja
// például azt is, hogy mi lett az új elnevezés
function Elnevezés($EredetiElnevezés, &$HovaTegyem_HolKeressem)
{
  //  ha nincs még ilyen elnevezés a megadott tömbben...
  if (!isset($HovaTegyem_HolKeressem[$EredetiElnevezés])) {

     // generálunk egy újat:
     $ÚjElnevezés = '';

     // legyen ugyanolyan hosszú, mint az eredeti
     for ($i = 0; $i < strlen($EredetiElnevezés); $i++) {
       // de csak az (angol) ABC nagybetűit tartalmazza
       $ÚjElnevezés .= chr(mt_rand(65, 90));
     }

     // regisztráljuk az értéket
     $HovaTegyem_HolKeressem[$EredetiElnevezés] = $ÚjElnevezés;
  }

  // adjuk vissza a kért értéket, ami lehet, hogy csak most készült el
  return($HovaTegyem_HolKeressem[$EredetiElnevezés]);
}

// file szintaktikai vizsgálata, tokenek előállítása
$Tokenek = token_get_all($file);

// végignézzük az összes tokent, $i segítségével
// hivatkozunk majd relatívan a többi tokenre
foreach ($Tokenek as $i => $Token) { 
    
  // egy egyszerű karakterrel nincs semmi dolgunk:
  if (is_string($Token)) {
    print($Token);
  }
  else {
    // ellenkező esetben $Token egy tömb; vizsgáljuk meg
    // az első elemet, mely a token típusát mutatja meg
    switch ($Token[0]) {
      
      // osztálydefiníció; a következő tokennek térköznek
      // kell lennie, utána viszont az osztály neve következik;
      // írjuk hát át az értékét, ill. írjunk ki egy 'class'-t
      // a tisztesség kedvéért:
      case T_CLASS:
        $Tokenek[$i + 2][1] = Elnevezés($Tokenek[$i + 2][1], $Osztálynevek);
        print('class');
        break;
      
      // function kulcsszóval találkoztunk; ebben az esetben
      // is a második "szomszéd" a hunyó, azt kell átírni;
      // 'function'-t pedig kiírni:
      case T_FUNCTION:
        $Tokenek[$i + 2][1] = Elnevezés($Tokenek[$i + 2][1], $Függvénynevek);
        print('function');
        break;
      
      // egy new operátor... egy szünet után már jön is a származtatandó
      // osztály neve; módosítsuk hát és ne feledkezzünk meg a 'new'-ról sem:
      case T_NEW:                      
        $Tokenek[$i + 2][1] = Elnevezés($Tokenek[$i + 2][1], $Osztálynevek);
        print('new');
        break;
     
     
      // változó-név, azonban a token értéke mindig $valami
      // vagy $semmi, ezért a $Változónevek tömb indexei mind
      // egy $karakterrel fognak kezdődni; ebben az esetben nem
      // értéket fogunk átírni, hanem csak mást fogunk kiirni
      case T_VARIABLE:                 
        print('$'.Elnevezés($Token[1], $Változónevek));
        break;
     
      // T_STRING... ezt kapjuk akkor, ha függvényhívás történik,
      // vagy állandóra hivatkozunk
      case T_STRING:
        // ha a következő token egy zárójel, akkor függvényhívásról van szó
        if ($Tokenek[$i + 1] === '(') {    
          print(Elnevezés($Token[1], $Függvénynevek));
        }
        // ellenkező esetben állandórol van szó, de ezzel most
        // nem foglalkozunk; írjuk csak ki a kimenetre
        else {            
          print($Token[1]);
        }
        break;
     
      // minden más tokent csak ki kell írni; a token második eleme
      // tartalmazza a token szöveges "értékét"
      default:
        print($Token[1]);
        break;
    }
  }
}
?>
Ennyi volna hát a program. Nagyon egyszerű, nagyon puritán, de működik és használható. Talán eléggé jól szemlélteti, hogy mi mindenre használható a tokenizer kiterjesztés.
 
bbalint
PHP-vel 2000 óta foglalkozik, köszönhetően az index.hu forráskódjának és maga kíváncsi természetének. Jelenleg egy reklámszervező cégnél dolgozik, mint (web)programozó és rendszergazda.
1

csak 1-2 eszrevetel: [list]

Benjamin · 2004. Ápr. 28. (Sze), 11.27
csak 1-2 eszrevetel:
  • valtozo valtozoknal elverzik barmelyik obfuscator
  • neadj isten valtozobol jovo fuggvenyek eseten is (ritkabb), classnal szinten
  • ha hibakezelot hasznalsz az eredeti kodhoz kepest tok mas helyeken fogja adni a jelzeseket, es raadasul atlathatatlan lesz
  • a modszer visszafele is mukodik :) (deobfuscator) bbalint elozo kodjaval szepited a PHP-t majd ranezel a fuggvenyekre es a fenti programot atalakitva siman "visszaalakithatod"

sajnos itt csak az encoder segit :) ezek a magyar (raadasul ekezetes) elnevezesek vmi borzasztoak :)

--
bye, Benjamin
2

Tókenájzer

Anonymous · 2004. Ápr. 28. (Sze), 11.30
Szerintem nem zavaró. Azt is írhatták volna, hogy tókenájzer :)
3

Re: csak 1-2 eszrevetel

Bártházi András · 2004. Ápr. 28. (Sze), 11.56
A változó változóknál csak akkor vérzik el, ha nem következetesen használod, vagyis hivatkozol rá simán is, és változón keresztül is. :)

A függvények tényleg probléma lehet.

Hibakezelőt nem a már "obfuszkált" :)) kódban kell használni, hiszen nyilván nem az összezavart kóddal fogsz debuggolni, sőt, ott le kell tiltani inkább...

Bár tényleg működik visszafele a módszer, de azért első ránézésre fogalmad sem lesz, második körben pedig ad elég munkát ahhoz, hogy jól elmenjen tőle az ember kedve. Persze aki szeret ilyet csinálni, annak lehet, hogy élvezetes, de az egy mazochista. ;)

Az encoder-t pedig én is ajánlanám, ha árulnám. ;) Egyébként tényleg az az a megoldás, mely elég jól elrejti a kódodat, viszont a rendszergazdák nem mindenhol szeretik (van, ahol PHP-t sem telepítenek...), és általános megoldást így nem tud nyújtani.

Összefoglalva: mind az encoder-nek, mind ennek megvan a maga helye, előnye és hátránya... ;)

-boogie-
4

Hello, En eleg sokszor has

Benjamin · 2004. Ápr. 28. (Sze), 13.05
Hello,

En eleg sokszor hasznalok ilyeneket: $items, $item majd:
<?php
   $f = array ('item');
   foreach ($f as $varname) {
       if (!in_array(${$varname}, ${$varname.'s'})) {
           ${$varname} = key(${$varname.'s'});
       }
   }
?>

Foleg ha sok elemrol beszelunk, szeretem automatizalni egyszerusiteni amit lehet... sztem ez meg kovetkezetes marad - de az obfuscator elverzik rajta.

Ugy latom felreertettel, nem a hibakezelot akarom obfuscalni (talan azt is) de ha egy publikus siteon figyelni akarod az esetlegesen felmerulo (pl. nincs dbconnect, barmi mas hiba ami specko helyzetben jon elo) hibakat/kiveteleket akkor az erdeti forraskodhoz kepest az obfuscaltban (hisz ezt teszed ki) nem ugyanott lesznek a kodok / hibak -> plusszmunka mig megkeresed

Engem egy ilyen obfuscalt kod nem akadalyozna meg h. szamomra olvashato formara hozzam (ha dolgoznom kellene vele, es mindenkepp abba kellene javitani) es nem hiszem h. mazohista lennek, hisz ha megnezed bbalint kodjait max 3-4 oldal, es ezt csak ki kell egeszitened 1 tombbel ami helyesen visszacserelgeti a neveket.

Amennyiben nincs PHP obfuscalni se kell :) ha van akkor egy optimizer (ZEND) se lehet gond hisz 1 "ceg/csoport" kesziti (all mogotte) es ez eleg utos erv ;)

--
bye, Benjamin
5

tömb, hehe

Anonymous · 2004. Ápr. 28. (Sze), 15.52
dehát nem ismert a tömb másik oldala, neked kell kitatláni, hogy melyik zagyva névhez milyen jó nevet rendelsz, a jó nevek ismerete nélkül, a kommenteket nem tudod visszahozni, stb
6

Hello, Fogod eloszor befor

Benjamin · 2004. Ápr. 28. (Sze), 16.08
Hello,

Fogod eloszor beformazod a kodot (olvashatora), kicsit belenezel megnezed a functionoket es a mukodesuk alapjan elnevezed, majd a valtozokat is atnezed, persze logikusan van 1 hatar h. mit erdemes meg mit nem... en csak azt mondom h. ezzel max 1-2h oras munkat csinalsz az illetonek, ettol ha olyan szerveren van a cuccod ahol "belatni" masok directoryjaba is (marha sok helyen van igy) ott siman lenyuljak.
Ettol meg ha nagyon akar valaki meg az obfuscalt kodba is belenyul, szal szerintem ez max csak jo poen :) de nem ved semmitol es senkitol. Ennyi kar tovabb ragozni.

Amugy a "jo neveket" te adod, rad van bizva minek nevezel egy fuggvenyt, valtozot :) meg az sincs h. nem tetszik a szintaxis LOL :))))

--
bye, Benjamin
7

Re: Fogod eloszor

Bártházi András · 2004. Ápr. 28. (Sze), 16.39
Azért jó sok függvénynél ez lehet több is egy-két óránál, nem mindig könnyű átlátni mások hülyeségeit. :) Akkor leírom megint: ez a megoldás egy olyan védelmet nyújt, hogy csak akkor "töri" fel valaki, ha eléggé akarja. Nem ránéz és lenyúlja.

Egyébként nem tudom, ti hogy vagytok vele, de én nem szívesen használom mások kódjait, akkor sem, ha ideadja. :) Meg kell tanulni, fel kell fogni a működését, stb. De ez már egy másik kérdés.

A cikket illetően ettől még szerintem egy valóban életképes megoldásról van szó, ami egy átlagos védelmet nyújthat a kódokkal szemben. Azt viszont nem szabad hinni, hogy egy kis munkával nem lehet belőle algoritmusokat, megoldásokat felfedni.

Arra tényleg az encoder való, bár mint tudjuk, feltörhetetlen rendszer nincsen.

-boogie-
8

izé, bigyó

bbalint · 2004. Ápr. 28. (Sze), 20.33
cső!

ezzel a cikkel csak egy egyszerű obfuszkátor akart lenni, nem valami ,,csilivili'' dolog, ami tudja, hogy melyik függénv vár a zargumentumában függvénynevet; nem figyel a "változó változókra"/indexnevekre sem.
az ilyesmi, a jövő zenéje [lehet]. ,,egyszer, majd...'' valami ilyesmit is talán írni fogjak/fognak, egyelőre csak "elméletek" vannak róla, hogy mit és hogyan kéne.

no, ennyit mára; ha nem tetszik tessék jobbat írni/mutatni...

cső, bbalint

ui: a magyar nevek pedig nem szőrnyűek.