Home / Indice sezione
 www.icosaedro.it 

 Offuscatore di codice PHP

Ultimo aggiornamento: 2008-02-07

Qui presento un programma Bash che rende irriconoscibile il sorgente di PHP eliminando spazi, commenti, cambiando il nome a funzioni e variabili. Ovviamente, ciò non impedisce la copia del programma offuscato, e neppure impedisce a persone determinate di modificarlo a loro piacimento. Inoltre, le stringhe non vengono crittate nè oscurate in alcun modo, sicché le informazioni riservate come le password rimangono in chiaro. Tuttavia penso che possa essere una soluzione sufficiente in molte occasioni, ed inoltre non richiede di installare nulla sul server.

Indice

Che cosa fa php-obfuscator
Funzionalità e limitazioni
Opzioni del programma
Esempio di programma offuscato
Download
Altri offuscatori e compilatori per PHP
Riferimenti

Che cosa fa php-obfuscator

Bisogna indicare da linea di comando tutti i file .php che compongono l'applicazione. php-obfuscator analizza questi file uno per uno e raccoglie l'elenco degli identificatori in essi definiti, per il momento solo variabili e funzioni. Quindi sostituisce ogni identificatore con un nome anonimo ed elimina commenti e spaziature. Il sorgente così ottenuto risulta poco leggibile e difficilmente modificabile. Notare che i riferimenti a variabili o funzioni esterne non vengono offuscati, altrimenti questo comprometterebbe la possibilità di interfacciarsi con questi componenti non offuscati. Ovviamente il codice HTML che circonda il codice PHP non viene toccato.

In definitiva, i vari file che compongono l'applicazione WEB vengono resi (quasi) illeggibili, ma rimangono pur sempre normali programmi PHP e non richiedono alcun particolare ambiente di esecuzione o libreria di runtime.

Attenzione! Offuscare non impedisce di copiare il sorgente, e neppure impedisce del tutto che venga modificato. Alcuni prodotti elencati più avanti offrono un livello maggiore di protezione.

Funzionalità e limitazioni

Questo offuscatore non ha alcuna pretesa di essere completo nè inviolabile. Inoltre questo programma non implementa un parser del linguaggio PHP, ma opera solo sulle stringhe. Ad esempio, tutto quello che segue il carattere $ è il nome di una variabile; tutto quello che sta tra la parola function e la parentesi tonda aperta è il nome di una funzione; ecc. Limiti e restrizioni:

Un aspetto interessante di questo programma è il modo in cui risolve il problema dei riferimenti incrociati tra file inclusi. Se ad esempio ho un modulo A.php che importa B.php, è necessario che gli identificatori dichiarati nei due moduli corrispondano. Ad esempio, se in B.php dichiaro una funzione di nome b che a sua volta viene richiamata all'interno dell'altro modulo A.php, è necessario che il nome della funzione venga convertito nello stesso nome in entrambi i file. A questo scopo l'offuscatore richiede di specificare tutti i file coinvolti nel progetto, in modo da poter collezionare tutti gli identificatori coinvolti: a nome in chiaro uguale corrisponderà un uguale nome offuscato.

Opzioni del programma

L'offuscatore accetta uno o più file da trattare. Questi file verranno sostituiti dalle versioni offuscate. Quindi bisogna fare preventivamente una copia dei file originali! Tipicamente, per ogni applicazioni dovremo confezionare un apposito programma per il rilascio e il deploy del progetto. Questo programma dovrebbe occuparsi di creare una copia dei file necessari in una directory provvisoria, quindi dovrebbe richiamare l'offuscatore sui file di questa directory prima di eseguire l'upload. La sintassi del comando che avvia l'offuscatore è:

php-obfuscator opzioni files...

dove le opzioni principali sono:

-r Rimuove commenti, spazi e a-capo non essenziali.
-v Offusca i nomi delle variabili e i campi delle classi.
-f Offusca i nomi delle funzioni dichiarate nei file indicati.
-a Offusca tutto. Equivale alle opzioni -r -v -f.
-h Stampa un riassunto delle opzioni disponibili.

Esempio di programma offuscato

Questo è un piccolo programma usato in questo sito:

<?
$i = 0;
$examples[$i++] = array("x", "--");
$examples[$i++] = array("bugged-web-page.txt",
	"Very bugged WEB page");
$examples[$i++] = array("../php/esempio1.cgi",
	"Call-back handling in WEB pages");
$examples[$i++] = array("../php/esempio2.cgi",
	"Secure call-back handling in WEB pages");
$examples[$i++] = array("index.html",
	"The index page of the section dedicated to PHPLint");
$examples[$i++] = array("phplint-on-line.html",
	"The code of this same WEB page!");
$n_examples = $i;


class FormData {
	public $example_n = 0;
	public $width = 80;
	public $height = 12;
	public $check_extended = TRUE;
	public $check_controls = TRUE;
	public $print_context = TRUE;
	public $errors = TRUE;
	public $warnings = TRUE;
	public $notices = FALSE;
	public $show_source = 1; # 0=no, 1=text+line no., 2=colors
	public $src = "";
}


function PrintSizer()
{
	echo '<table cellspacing=0 cellpadding=0><tr><td valign=center>';
	Button('<', 'SizeButton', 'w');
	echo '</td><td>';
	Button('^', 'SizeButton', 'n'); echo BR;
	Button('V', 'SizeButton', 's');
	echo '</td><td valign=center>';
	Button('>', 'SizeButton', 'e');
	echo '</td></tr></table>';
}


function Parse($fd)
{
	$src_file = "/tmp/phplint-" . time();
	#$err_file = "/tmp/phplint-b";

	$f = @fopen($src_file, "w");
	if( $f == FALSE ){
		trigger_error("fopen() failed: $php_errormsg");
	}
	fwrite($f, $fd->src);
	fclose($f);

	echo '<h2>PHPLint report</h2><pre>';
	$opt = "--version --no-file-name --tab-size ". TAB_SIZE;
	if( $fd->debug == 1 )
		$opt .= " -d";
	if( ! $fd->check_extended ) $opt .= " --no-ascii-ext-check";
	if( ! $fd->check_controls ) $opt .= " --no-ctrl-check";
	if( $fd->print_context ) $opt .= " --print-context";
	if( ! $fd->errors ) $opt .= " --no-errors";
	if( ! $fd->warnings ) $opt .= " --no-warnings";
	if( ! $fd->notices ) $opt .= " --no-notices";
	$cmd = PHPLINT ." $opt $src_file";
	#system("$cmd 2>&1 | ". CODE2HTML . ' | ' . NL2BR);
	system("$cmd 2>&1 | expand -t ". TAB_SIZE ." | ". TEXT2HTML);
	echo '</pre>';

	/***
	if( file_exists($err_file) && filesize($err_file) > 0 ){
		echo("Errors:<blockquote><pre>");
		system("cat $err_file");
		echo("</pre></blockquote>");
	}
	***/

	if( $fd->show_source == 1 ){
		echo '<h2>Source text for line references</h2><pre>';
		system("nl -ba $src_file | expand -t ". TAB_SIZE ." | ". CODE2HTML);
		echo '</pre>';
	} else if( $fd->show_source == 2 ){
		echo '<h2>Source with highlighted syntax</h2>';
		highlight_file($src_file);
	}

	unlink($src_file);
}


function DrawForm($fd)
{
	global $examples, $n_examples;

	echo '<div style="background-color: #cccccc; border: 10px solid #cccccc;">';

	Form();

	echo '<table cellspacing=0 cellpadding=0 width=100%><tr><td>';

	echo '<b>PHPLint on-line - PHP source parser and validator</b>';
	echo '<p>Choose an example, or paste your code here.</p>';

	echo 'Example: ';
	Select('example_n');
	for($i=0; $i<$n_examples; $i++)
		Option($i, $fd->example_n, TextToHtml($examples[$i][1]));
	Select_();
	echo ' ';
	Button("See Example", "SeeButton");

	echo '</td><td valign=bottom align=right>';

	PrintSizer();

	echo '</td></tr></table>';


	# Form del sorgente:
	Hidden('width', $fd->width);
	Hidden('height', $fd->height);
	echo '<TEXTAREA name=src',
		' cols=', $fd->width,
		' rows=', $fd->height, '>',
		TextToHtml($fd->src),
		'</TEXTAREA><br>';

	echo '<table width=100% cellspacing=0 cellpadding=0>',
		'<tr><td valign=top>';

	Box("Report:");
	CheckBox("errors", $fd->errors, "Show Errors"); echo BR;
	CheckBox("warnings", $fd->warnings, "Show Warnings"); echo BR;
	CheckBox("notices", $fd->notices, "Show Notices"); echo P;
	CheckBox("print_context", $fd->print_context, 'Print a line of context');
	Box_();

	echo '</td><td valign=top>';

	Box('Optional checks:');
	CheckBox("check_extended", $fd->check_extended,
		'Presence of extended-ASCII characters'); echo BR;
	CheckBox("check_controls", $fd->check_controls,
		'Presence of ASCII control characters'); echo BR;
	Box_();
	
	Box("Show:");
	RadioButton('show_source', $fd->show_source, 0,
		"Report"); echo BR;
	RadioButton('show_source', $fd->show_source, 1,
		"Report + source with line numbers"); echo BR;
	RadioButton('show_source', $fd->show_source, 2,
		"Report + source with syntax highlighted");
	Box_();

	#CheckBox('debug', $fd->debug, "Debug");

	echo '</td></tr></table>';

	echo P, CENTER;
	Button("Clear", "ClearButton");
	echo HSPACE, HSPACE;
	Button("Parse", "ParseButton");
	echo CENTER_, FORM_;

	echo("</div>");

}


function GetPost()
{
	global $n_examples;
	$fd = new FormData();
	$fd->example_n = GetSelect('example_n', $n_examples);
	$fd->width = min(max((int) $_POST['width'], 20), 150);
	$fd->height = min(max((int) $_POST['height'], 5), 100);
	$fd->check_extended = GetCheckBox('check_extended');
	$fd->check_controls = GetCheckBox('check_controls');
	$fd->print_context = GetCheckBox('print_context');
	$fd->errors = GetCheckBox('errors');
	$fd->warnings = GetCheckBox('warnings');
	$fd->notices = GetCheckBox('notices');
	$fd->debug = GetCheckBox('debug');
	$fd->show_source = GetRadioButton('show_source', 3);
	$fd->src = $_POST['src'];
	return $fd;
}


function SeeButton()
{
	global $examples, $n_examples;
	$fd = GetPost();
	if( $fd->example_n > 0 ){
		$f = fopen($examples[$fd->example_n][0], "r");
		$fd->src = fread($f, 100000);
		fclose($f);
	}
	DrawForm($fd);
	Piede();
}


function ClearButton()
{
	$fd = GetPost();
	$fd->example_n = 0;
	$fd->src = "";
	DrawForm($fd);
	Piede();
}


function SizeButton($size)
{
	$fd = GetPost();
	switch( $size ){
		case 'n': $fd->height -= 5; break;
		case 's': $fd->height += 5; break;
		case 'w': $fd->width -= 10; break;
		case 'e': $fd->width += 10; break;
	}
	$fd->width = min(max($fd->width, 20), 150);
	$fd->height = min(max($fd->height, 5), 100);
	DrawForm($fd);
	Piede();
}


function ParseButton()
{
	$fd = GetPost();
	DrawForm($fd);
	Parse($fd);
	Piede();
}


if( ! RequestHandler() ){
	DrawForm(new FormData());
	Piede();
}

?>

Ed ecco la versione offuscata con il mio programma, ottenuta usando l'opzione -a (ho inserito gli a-capo in modo da troncare le righe a una lunghezza ragionevole, perché l'offuscatore restituisce tutto su di una sola riga):

<? $v_2 = 0; $v_3[$v_2++] = array("x", "--"); $v_3[$v_2++] =
array("bugged-web-page.txt", "Very bugged WEB page"); $v_3[$v_2++]
= array("../php/esempio1.cgi", "Call-back handling in WEB pages");
$v_3[$v_2++] = array("../php/esempio2.cgi", "Secure call-back
handling in WEB pages"); $v_3[$v_2++] = array("index.html", "The
index page of the section dedicated to PHPLint"); $v_3[$v_2++] =
array("phplint-on-line.html", "The code of this same WEB page!");
$v_7 = $v_2; class FormData { public $v_12 = 0; public $v_a = 80;
public $v_11 = 12; public $v_14 = TRUE; public $v_15 = TRUE; public
$v_e = TRUE; public $v_13 = TRUE; public $v_b = TRUE; public $v_10
= FALSE; public $v_d = 1; public $v_c = ""; } function f_6() { echo
'<table cellspacing=0 cellpadding=0><tr><td valign=center>'; Button('<',
'SizeButton', 'w'); echo '</td><td>'; Button('^', 'SizeButton', 'n');
echo BR; Button('V', 'SizeButton', 's'); echo '</td><td valign=center>';
Button('>', 'SizeButton', 'e'); echo '</td></tr></table>'; } function
f_4($v_1) { $v_6 = "/tmp/phplint-" . time(); $v_5 = @fopen($v_6, "w"); if(
$v_5 == FALSE ){ trigger_error("fopen() failed: $v_f"); } fwrite($v_5,
$v_1->v_c); fclose($v_5); echo '<h2>PHPLint report</h2><pre>'; $v_4 =
"--version --no-file-name --tab-size ". TAB_SIZE; if( $v_1->debug ==
1 ) $v_4 .= " -d"; if( ! $v_1->v_14 ) $v_4 .= " --no-ascii-ext-check";
if( ! $v_1->v_15 ) $v_4 .= " --no-ctrl-check"; if( $v_1->v_e ) $v_4 .=
" --print-context"; if( ! $v_1->v_13 ) $v_4 .= " --no-errors"; if(
! $v_1->v_b ) $v_4 .= " --no-warnings"; if( ! $v_1->v_10 ) $v_4 .= "
--no-notices"; $v_9 = PHPLINT ." $v_4 $v_6"; system("$v_9 2>&1 | expand
-t ". TAB_SIZE ." | ". TEXT2HTML); echo '</pre>'; if( $v_1->v_d == 1 ){
echo '<h2>Source text for line references</h2><pre>'; system("nl -ba
$v_6 | expand -t ". TAB_SIZE ." | ". CODE2HTML); echo '</pre>'; } else
if( $v_1->v_d == 2 ){ echo '<h2>Source with highlighted syntax</h2>';
highlight_file($v_6); } unlink($v_6); } function f_2($v_1) { global
$v_3, $v_7; echo '<div style="background-color: #cccccc; border: 10px
solid #cccccc;">'; Form(); echo '<table cellspacing=0 cellpadding=0
width=100%><tr><td>'; echo '<b>PHPLint on-line - PHP source parser and
validator</b>'; echo '<p>Choose an example, or paste your code here.</p>';
echo 'Example: '; Select('example_n'); for($v_2=0; $v_2<$v_7; $v_2++)
Option($v_2, $v_1->v_12, TextToHtml($v_3[$v_2][1])); Select_(); echo '
'; Button("See Example", "SeeButton"); echo '</td><td valign=bottom
align=right>'; f_6(); echo '</td></tr></table>'; Hidden('width',
$v_1->v_a); Hidden('height', $v_1->v_11); echo '<TEXTAREA name=src',
' cols=', $v_1->v_a, ' rows=', $v_1->v_11, '>', TextToHtml($v_1->v_c),
'</TEXTAREA><br>'; echo '<table width=100% cellspacing=0 cellpadding=0>',
'<tr><td valign=top>'; Box("Report:"); CheckBox("errors", $v_1->v_13,
"Show Errors"); echo BR; CheckBox("warnings", $v_1->v_b, "Show
Warnings"); echo BR; CheckBox("notices", $v_1->v_10, "Show Notices");
echo P; CheckBox("print_context", $v_1->v_e, 'Print a line of
context'); Box_(); echo '</td><td valign=top>'; Box('Optional
checks:'); CheckBox("check_extended", $v_1->v_14, 'Presence of
extended-ASCII characters'); echo BR; CheckBox("check_controls",
$v_1->v_15, 'Presence of ASCII control characters'); echo BR;
Box_(); Box("Show:"); RadioButton('show_source', $v_1->v_d, 0,
"Report"); echo BR; RadioButton('show_source', $v_1->v_d, 1, "Report
+ source with line numbers"); echo BR; RadioButton('show_source',
$v_1->v_d, 2, "Report + source with syntax highlighted"); Box_(); echo
'</td></tr></table>'; echo P, CENTER; Button("Clear", "ClearButton");
echo HSPACE, HSPACE; Button("Parse", "ParseButton"); echo CENTER_, FORM_;
echo("</div>"); } function f_3() { global $v_7; $v_1 = new FormData();
$v_1->v_12 = GetSelect('example_n', $v_7); $v_1->v_a = min(max((int)
$_POST['width'], 20), 150); $v_1->v_11 = min(max((int) $_POST['height'],
5), 100); $v_1->v_14 = GetCheckBox('check_extended'); $v_1->v_15 =
GetCheckBox('check_controls'); $v_1->v_e = GetCheckBox('print_context');
$v_1->v_13 = GetCheckBox('errors'); $v_1->v_b = GetCheckBox('warnings');
$v_1->v_10 = GetCheckBox('notices'); $v_1->debug = GetCheckBox('debug');
$v_1->v_d = GetRadioButton('show_source', 3); $v_1->v_c = $_POST['src'];
return $v_1; } function f_7() { global $v_3, $v_7; $v_1 = f_3(); if(
$v_1->v_12 > 0 ){ $v_5 = fopen($v_3[$v_1->v_12][0], "r"); $v_1->v_c =
fread($v_5, 100000); fclose($v_5); } f_2($v_1); Piede(); } function f_1()
{ $v_1 = f_3(); $v_1->v_12 = 0; $v_1->v_c = ""; f_2($v_1); Piede(); }
function f_8($v_8) { $v_1 = f_3(); switch( $v_8 ){ case 'n': $v_1->v_11
-= 5; break; case 's': $v_1->v_11 += 5; break; case 'w': $v_1->v_a -= 10;
break; case 'e': $v_1->v_a += 10; break; } $v_1->v_a = min(max($v_1->v_a,
20), 150); $v_1->v_11 = min(max($v_1->v_11, 5), 100); f_2($v_1); Piede();
} function f_5() { $v_1 = f_3(); f_2($v_1); f_4($v_1); Piede(); } if(
! RequestHandler() ){ f_2(new FormData()); Piede(); }
?>

Alcune cose da osservare in questo esempio:

Download

php-obfuscator.txt - Questo è il testo sorgente del programma Bash. Salvare nel file php-obfuscator e rendere eseguibile con chmod +x php-obfuscator. REQUISITI: occorre avere l'interprete Bash (è la shell comunemente installata in Linux) *e* l'interprete PHP stand-alone (cioè la versione CGI o CLI del PHP).

Altri offuscatori e compilatori per PHP

Ecco alcuni prodotti che offuscano o altrimenti rendono illeggibile il sorgente del programma. Alcuni cambiano i nomi delle variabili, delle funzioni ecc. Altri intercettano il bytecode prodotto dall'interprete PHP e quindi sono una specia di "compilatori". Altri applicano algoritmi di crittografia al codice sorgente e poi lo decrittano al volo per eseguirlo. Alcuni combinano due o più di questi metodi.

Offuscatori a parte, ci sono anche i compilatori. Un compilatore ha come effetto collaterale di eliminare del tutto il codice sorgente. In generale questi compilatori non producono codice del processore, ma intercettano il bytecode prodotto dall'interprete PHP ed eseguoni poi quello, con un vantaggio prestazionale comunque notevole. Il problema della compilazione di un linguaggio interpretato a tipizzazione dinamica come PHP viene discusso nell'articolo PHP Analisi Critica (www.icosaedro.it/articoli/php-analisi-critica.html).

Ovviamente il server deve essere configurato opportunamente per poter eseguire il prodotto di questi compilatori. A volte basta patchare l'interprete PHP con una estensione, a volte è richiesta l'installazione di ulteriore software o una particolare configurazione del WEB server.

Riferimenti


Umberto Salsi

Contatto
Mappa
Home / Indice sezione