windows下用c寫php擴充(加密解密php原始碼)
首先用hello world試手一下。
下載php源碼包,ext目錄就是擴充目錄了裡面有2個重要的檔案是ext_skel以及ext_skel_win32.php.
下載cygwin.有了這個就可以方便的在windows下建立php擴充了。
下載中。。。
下載完後用php ext_skel_win32.php --extname=hello來編譯產生我們的擴充開發目錄hello
然後開始寫測試程式hello world
在hello.c檔案裡添加函數定義以及函數註冊語句:
函數註冊語句:const zend_function_entry hello_functions[] = {
?PHP_FE(confirm_hello_compiled, NULL)?
??? PHP_FE(sayHello,NULL)//這句是我們手動添加的
?{NULL, NULL, NULL}
};
函數定義:PHP_FUNCTION(sayHello){
?php_printf("Hello C extension");
}
好像說是一定要以PHP_FUNCTION出現的宏形式,因為如果直接裸寫c代碼可能會發生命名衝突或是其他的衝突。
然後在php_hello.h裡面添加函式宣告語句:PHP_FUNCTION(sayHello);
寫好測試程式,編譯結果出現../main/config.w32.h': No such file or directory
網上查了一下,好像是要下載額外的包。。。麻煩啊
http://www.php.net/extra/bindlib_w32.zip
http://www.php.net/extra/win32build.zip
將這兩個包放在一起,我把它們放在win32/build目錄下
然後執行php源碼包根目錄下的buildconf.bat(最好在命令列運行,不然顯示結果會一閃而過)
然後把bison.exe(在剛下載的包裡面)所在的目錄設定為環境變數,再運行configure.bat。完後就產生/main/config.w32.h這個檔案了。
然後再次編譯剛才hello項目結果出現一大推錯誤。
形如:..\..\main\streams/php_stream_transport.h(85) : error C2143: syntax error : missing ')' before '*'
..\..\main\streams/php_stream_transport.h(85) : error C2081: 'socklen_t' : name in formal parameter list illegal
網上說是由於找不到宏定義才會這樣,那應該是socklen_t這個宏沒定義了,但是它具體的宏定義應該是怎麼樣的呢,總不能隨便寫一個吧。????????? 所以開啟\main\streams/php_stream_transport.h發現應該是個類型別名,而且是個int ,因為有socklen_t addrlen;addrlen按字面應該是儲存長度的值。
所以在這個檔案中添加typedef int socklen_t;儲存
在編譯剛才的項目,錯誤少了很多,但是還有7個,經檢查發現是輸入了中文符號。改正再編譯。。。還有一個錯誤:
LINK : fatal error LNK1181: cannot open input file "php5ts.lib"
於是將尋找php5ts.lib這個檔案並將它放到項目目錄下或是VC6 lib檔案預設搜尋目錄下也可以。找啊找啊找。。。
tmd,用windows搜尋找了好久都沒找到。百度是說在php二進位程式碼封裝裡面。所以先下個同版本的二進位程式碼封裝(應該就是平時寫php所必須下載的那個包吧)
這裡先說下環境吧windows+vc6+php5.3.5(二進位程式碼封裝和源碼包)+cygwin
下載完畢,找到,複製 ,編譯,成功!
但是出現了一個很嚴重的問題,dll檔案沒出來,,,我哭
建立出來的是php_hello.exp和php_hello.lib。怎麼會是靜態呢??
其實已經產生了,只是不在本目錄下的Release_TS目錄下,而是在ext上級的Release_TS目錄下。
然後測試。哈哈,說是未定義函數,有沒可能是測試的php版本和我擴充的php版本不一樣的關係呢?
測試了一下也不是,只要在ini裡一設定載入php_hello.dll重啟apache就會出現記憶體不能讀的錯誤。
總覺得代碼沒有問題,應該還是前面配置編譯的時候有問題。
經過一番又一番的測試發現時php二進位包下錯了我下了vc9的應該下vc6的。
?
接下來要開始寫加密和解密了。加密解密演算法本身不是這裡的重點,重點是如何在zend層用zend本身的介面結合c來編程,在zend編譯源檔案之前將檔案解密(當然檔案之前要是有加過密的)。為了使用的方便。我的想法是像php_screw一樣產生dll的同時,產生一個加密的可執行檔,這加密的可執行檔由我們手動執行,傳入目錄參數,能夠對該目錄下的所有檔案進行加密。
在網上找了一堆資料後,再看看php_screw的代碼,還有有些吃力,所以決定根據自己的思路來寫。當然有些地方時會借鑒php_screw的代碼
首先是先寫一個對檔案進行解密的函數。這個函數利用我們現成的解密演算法對檔案內容進行解密。
這個函數應該有一個參數用來接收當前請求檔案的控制代碼(貌似是zend_compile_file這個東西,百度之,確認一下,幾篇文章說是函數指標,我看了下原始碼確實是函數指標)externZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle*file_handle, int type TSRMLS_DC);
那麼要如何得到當前請求檔案的檔案指標呢?或許和zend_compile_file函數指標所指函數的作用有關。在原始碼中發現了zend_compile_file=compile_file;該函數的定義在zend_language_scanner.c,不過要看懂該函數有點難度,網上的說法是:
-------------
zend_compile_file負責將要執行的指令檔編譯成由ZE的基本指令序列構成的op codes 。
PHP執行這段代碼會經過如下4個步驟:
1. Scanning (Lexing) ,將PHP代碼轉換為語言片段(Tokens)
2. Parsing , 將Tokens轉換成簡單而有意義的運算式
3. Compilation , 將運算式編譯成Opocdes
4. Execution , 順次執行Opcodes,每次一條,從而實現PHP指令碼的功能。
-------------
所以我們應該要在這個四個步驟之前將檔案解密。
想法是重寫一個函數a判斷在檔案被compile之前先將它解密,然後再調用預設的complie函數。定義好函數a後,應該在請求初始化的時候將函數a傳遞給函數指標zend_compile_file
PHP_MINIT_FUNCTION(dencrypt){
?old_comlie_file =zend_complie_file;//保留預設的compile,以便等一下調用
?zend_complie_file = 函數a;
?return SUCCESS;
}
ZEND_API zend_op_array *a(zend_file_handle *file_handle,int typeTSRMLS_DC){//這裡的TSRMLS_DC是一個宏類似於,...(宏的定義暫時找不到)總之是跟多線程環境下全域變數的安全執行緒有關係的,以後再深究
?解密代碼。。。
?old_comlie_file(file_handle);
?....
}
但是問題還是沒有解決,因為我們還是不知道如何擷取到檔案指標。我看php_screw裡面的解密步驟挺長的,參考之。。。找到了fp =fopen(file_handle->filename,"r");原來file_handle裡面有檔案名稱的資訊(其實如果找到file_handle的結構體定義語句也就知道了)。
但是php_screw裡面還有這麼一段
char?fname[32];
?memset(fname, 0, sizeof fname);
?if (zend_is_executing(TSRMLS_C)){//TSRMLS_C獲得全域變數
??if(get_active_function_name(TSRMLS_C)){//擷取當前調用函數的名字(當前調用函數?哪裡的函數?php函數?想想當然是zend裡面的函數。而不是php層的函數,因為現在都還沒編譯,更沒執行呢)
???strncpy(fname,get_active_function_name(TSRMLS_C), sizeof fname - 2);
??}
?}
?if (fname[0]) {
??if ( strcasecmp(fname,"show_source") == 0//也就是說如果當前是這兩個函數則不解密也不編譯了。恩,看樣子沒錯。
??? ||strcasecmp(fname, "highlight_file") == 0) {
???returnNULL;
??}
?}?所以這段還是必要的,不然遇到以上兩個函數就沒法實現了,compile_file函數裡面應該也有這步才對。所以這裡主要是不要讓它被解密,而是直接顯示密文。
還有這麼一段
?fp = fopen(file_handle->filename,"r");
?if (!fp) {//如果開啟失敗則直接調用預設compile函數
??returnorg_compile_file(file_handle, type);
?}
?fread(buf, PM9SCREW_LEN, 1,fp);//一下5句的作用是:如果發現時未加密的檔案則不進行解密。
?if (memcmp(buf, PM9SCREW, PM9SCREW_LEN) != 0){
??fclose(fp);
??returnorg_compile_file(file_handle, type);
?}
?
if (file_handle->type ==ZEND_HANDLE_FP)fclose(file_handle->handle.fp);//判斷檔案控制代碼類型,應用相應的關閉函數。
?if (file_handle->type ==ZEND_HANDLE_FD) close(file_handle->handle.fd);
?
?file_handle->handle.fp =pm9screw_ext_fopen(fp);//調用解密檔案的函數,並用file_handle裡面的fp接收函數返回結果
?file_handle->type =ZEND_HANDLE_FP;//將控制代碼類型設定為檔案指標類型
?file_handle->opened_path =expand_filepath(file_handle->filename, NULLTSRMLS_CC);//這裡有點不懂,猜想是不是接受當前檔案的路徑呢?一路追蹤找到了expand_filepath_ex這個函數這個函數的最後一句是returnreal_path;看樣子應該就是返回當前要編譯檔案的路徑吧。但是為什麼要有上面那兩步呢?就是設定類型和路徑這兩部。如果我們不做解密操作就不用這兩步,我的猜想是因為fclose(file_handle->handle.fp)這裡改變了file_handle的狀態,所以才需要重新設定吧!
//(但是有一個疑問是,這樣做是Compilation之前解密,還是在Scanning之前解密?我初步認為從語義上來講是在Compilation之前解密,但是從實際情況來看應該是要在Scanning之前解密,這邊後期可以驗證,事實證明後者是對的,因為compile_file函數裡有對open_file_for_scanning函數的調用,也就是說在compile_file函數裡執行了前面所說的123步)
進入點有了(還有4分之3的問題需要解決)。
接下來開始寫解密檔案的函數
需要考慮的問題有,解密檔案後的明文代碼,並不需要寫入檔案,那麼如何取得這些明文的檔案指標呢?用臨時檔案來做?但是如果每個請求都用不同的臨時檔案那會產生很多臨時檔案(顯然不可行),如果對同一個php檔案的不同請求只用一個臨時檔案,也會出現資源等待的問題(顯然也不可行)。能不能直接在記憶體中指定一個檔案指標呢?先看看php_screw的做法,調用tmpfile()產生臨時檔案,但是在程式退出時便會自動刪除!其實我認為的在記憶體中的檔案也是這樣實現的。
直接下來把之前用php寫的可逆加解密演算法用c來實現,可是c並沒有現成了md5和base64函數,看來只能拋棄這2個了。
寫好後就開始編譯了,遇到了一些錯誤也都改過來了。
然後連結的時候出現了錯誤:error LNK2001: unresolved external symbol_zend_compile_file
搜尋了一下,貌似是因為函數編譯方式的不一樣所導致的找不到函數加入
BEGIN_EXTERN_C()
ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle*file_handle, int type TSRMLS_DC);
END_EXTERN_C()
即可
產生dll檔案成功,但是不知道如何產生用於加密的可執行檔,當然最簡單的解決方案就是另外建一個項目。看來只好先這樣了,因為對vc的一些編譯機制還有參數設定也不是很熟悉(以後再研究程式員的自我修鍊好像有講這個的)
之後又遇到c的指標傳遞問題(很久沒弄,一些基礎的都忘了,最後用二級指標來解決指標作為參數回傳的問題)
又遇到了連結時找不到_zend_compile_file符號的問題(跟zend_api宏有關dllimport\dllexport)
然後又遇到讀寫檔案的問題,其實沒錯,只是我把寫入檔案的密文手動複製內容到另一個檔案中去測試,結果會出錯,如果直接複製檔案,或直接操作該原始檔案則正確。
最後又遇到了記憶體不能讀的錯誤。。。
最後是發現是由於檔案讀取方式的問題,以二進位讀取就可以了,如果以文本方式讀寫,會將某些特殊字元進行處理。
...最後測試功能是可以了,不過如果真的要應用的話要改進一下,比如我加密的時候是在原來目錄下直接產生加密後的檔案,更好的做法應該是要把原來的php檔案打包起來,方便管理。
接下來開始搞zf架構了,都快忘光了,而且當初還有還多東西沒用到的。
?
=======================================
原文地址:
http://blog.sina.com.cn/s/blog_4d06da1f0100pgmj.html
http://blog.sina.com.cn/s/blog_4d06da1f0100pni3.html
?
?
=======================================
相關參考:
在Windows下寫PHP的C擴充:
http://koda.iteye.com/blog/315779
?
用C/C++擴充你的PHP :
http://www.laruence.com/2009/04/28/719.html
?
?
自己動手用 C 擴充 PHP(一)
http://blog.ly5.org/archives/122.html
?
自己動手用 C 擴充 PHP(二)- 函數
http://blog.ly5.org/archives/123.html
?
自己動手用 C 擴充 PHP(三)- 重要ZEND API函數
http://blog.ly5.org/archives/124.html
?
?
如何用C 編寫PHP擴充
http://www.ej38.com/showinfo/c-php-212877.html
?