1. 程式人生 > >分享一下某PHP加密檔案“除錯解密”過程

分享一下某PHP加密檔案“除錯解密”過程

實驗樣本

http://www.phpjiami.com/

據說“加密效果同行最高”?

到 http://www.phpjiami.com/phpjiami.html 隨意上傳一個 php 檔案,然後下載加密後的檔案,這就是我們要解密的檔案。

簡單分析一下

先看看加密後的檔案

可以看出這是一個正常的 php 檔案,只不過所有的變數名都是亂碼,還真虧了 php 引擎支援任意字符集的變數名,這個加密後的檔案變數名的位元組部都在 ASCII 範圍以外,全是 0x80以上的字元。

我們看到中間有一個 php 程式碼段結束標籤 ?>,而他的前面還有一個 return $xxx;來結束指令碼執行,這說明結束標籤後面的資料都不會被正常輸出,後面極可能是原始檔加密後的資料,而前面的 php 程式碼只是用來解密的。

除錯之前的準備

這裡使用的 IDE 是 VSCode(最開始我使用的是 PHPStorm,後來我發現 VSCode的效果更好)。首先,安裝 PHP Debug 外掛。

然後,按照 https://xdebug.org/docs/install 的說明安裝 XDebug外掛。

注意:執行未知的 php 程式碼還是很危險的,最好能在虛擬機器上執行,真機上一定要保證你的 XDebug和 PHP Debug除錯外掛可以正常下斷點。斷開網路。最好同時開啟工作管理員,一旦發生未知現象(比如 CPU 佔用率或磁碟佔用率),或者除錯斷點沒斷下來,或者出現某些問題,立刻結束 php 程序。

開始除錯

程式碼格式化

這個程式碼太亂了,我們需要格式化一下程式碼。

最開始我用的是 PHPStorm 自帶的程式碼格式化,格式化之後資料變了,PHPStorm 對未知字符集的支援還是比較差的。

然後我就想對 php 檔案的 AST (Abstract Syntax Tree 抽象語法樹)進行分析,看能不能順便把變數名都改成可顯示字元。後來想想似乎不行,因為這種程式碼肯定是帶 eval 的,改了變數名之後,eval 的字串中的變數名就對應不上了。

我找到了這個工具:https://github.com/nikic/PHP-Parser

首先 composer require nikic/php-parser。

然後將下列程式碼儲存到一個檔案中(比如 format.php),讀取下載下來的 1.php,把格式化之後的程式碼寫入 2.php。

然後,執行 php format.php。

使用這個方法格式化的 php 檔案內容並沒有被損壞,我們可以繼續分析了。

如果,還不行,那就只能用十六進位制編輯器查詢 ; 和 } 手動替換了,新增\n 了。

除錯

最前面這兩行我們得先註釋掉,不然出了什麼錯誤的話會莫名其妙的。

error_reporting(0);

ini_set("display_errors", 0);

儲存。然後完蛋了,程式碼又亂了。

我們需要一個支援非可顯示字元的編輯器,或者...更改顯示編碼,選擇一個不是多位元組的字符集,比如 Western (ISO 8859-1)

現在,開始我們的除錯。

在第一行下斷點。執行 php 2.php執行程式。然後單步除錯,一邊執行,一邊注意變數的值,分析函式的執行流程。

使用 VSCode的除錯功能,我們可以方便的檢視變數的具體內容。

單步除錯到這一行,似乎有些不對勁。

php_sapi_name() == 'cli' ? die() : '';

我們用命令列執行的,所以執行完這一句,肯定程式就結束了。

那就讓他結束吧,我們把這一行註釋掉,在他下面下斷點。重新執行程式。

下面這行是就是讀取當前檔案,這句話沒有什麼問題。

$f = file_get_contents(constant('rnfzwpch'));

然後就又是驗證執行環境。

if(!isset($_SERVER['HTTP_HOST']) !isset($_SERVER['SERVER_ADDR']) && !isset($_SERVER['REMOTE_ADDR'])) {

die();

}

註釋掉,儲存,重新執行。

當然,也可以通過除錯控制檯,執行類似 $_SERVER['HTTP_HOST'] = '127.0.0.1'; 這類指令,來讓驗證通過。

再看下面的程式碼,我想到 exe 反除錯了,不得不佩服想這個方法的人。防止下斷點除錯的,如果下斷點除錯,這裡就超過 100 毫秒了。

$t = microtime(true) * 1000;eval("");if (microtime(true) * 1000 - $t > 100) {

die();

}

我們直接在這條語句之後下斷點,讓他們一連串執行完,這樣就不會超過 100 毫秒了。當然,直接註釋掉是最粗暴的方法。

下面的 eval我們需要通過“單步進入”來研究,不過結果是對我們的影響不大,當然註釋掉也沒問題。

接下來這個就是校驗資料完整性的了

!strpos(decode_func(substr($f, -45, -1)), md5(substr($f, 0, -46))) ? $undefined1() : $undefined2;

這裡的$undefined1和 $undefined2都沒有定義。如果驗證失敗,就會呼叫 $undefined1會直接 Error退出程式。而如果驗證成功,雖然 $undefined2變數不存在,但是隻是一個 Warning,並沒有太大問題。decode_func就是檔案中最後一個函式,專門負責字串解碼的。

這個驗證方法就是把檔案尾部分解密和前面的檔案主體部分的 md5 對比,這次執行肯定又不能通過。

退出程式,註釋掉,再重新執行。

$decrypted = str_rot13(@gzuncompress(decode_func(substr($f, -2358, -46))));

我們找到了這個解碼的關鍵語句了,可以看到解密之後的程式碼已經出來了。

到了程式碼的最後,終於要執行指令碼了。

$f_varname = '_f_';$decrypted = check_and_decrypt(${$f_varname});

set_include_path(dirname(${$f_varname}));$base64_encoded_decrypted = base64_encode($decrypted);$eval_string = 'eval(base64_decode($base64_encoded_decrypted));';$result = eval($eval_string);

set_include_path(dirname(${$f_varname}));return $result;

折騰了半天,還是 eval語句。如何把內容輸出呢。直接在 $decrypted後面加上一行 file_put_contents就可以了。

成果

通用解密程式

我們可以繼續分析一下他的解密演算法

演算法是固定的,只是其中內聯了一個祕鑰,我們只要通過字串函式截取出這個祕鑰就可以了。

最後的解碼程式如下。

這個程式可以解密此網站全部免費加密的程式碼。

使用方法:php decrypt.php 1.php

總結

php 這種動態解釋語言還想加密?做夢去吧。不過混淆還是有可能的。

這個程式碼中的暗樁挺有意思,算是學到了點知識。

php 這種東西為什麼要加密?php 的開源社群多麼龐大。

附錄

程式碼賞析