PHP 代碼加密實踐

在咱們開發的項目中,有一部分多是用於商業用途,會部署在客戶提供的機器環境中。由於 PHP 自己是解釋型語言,因此未進行處理的代碼,就會有泄露或被修改的風險。那麼咱們可能會想到最簡單有效的方法就是進行加密混淆,而後配合一系列的校驗,來保護咱們的代碼。那麼本篇文章中,咱們就採用了開源的 PHP 加密擴展 screw-plus 進行相關實踐分析。php

目前市場上有多種加密方案,但基本都是收費的。咱們本次實踐採用了開源的方案。screw-plus 是一款開源 php 加密運行擴展,基於 screw 二次開發,暫時只能運行在 linux 下。雖然有些不足,可是若是增強一下,也是能夠知足一些場景。linux

v2-a0319eb5aca9f0123e56cfd4f7a73fc9_720w.jpg

在介紹實現以前,咱們先簡單介紹下 PHP 擴展的相關知識以及 Hook 機制。vim

1 PHP 擴展週期

v2-e9c4cf14f967db9f30ebc39288573866_720w.jpg

一款擴展的主要生命週期,就是上面的四個宏定義。安全

  • MINIT:擴展模塊初始化時執行的動做
  • MSHUTDOWN:擴展模塊結束時執行的動做
  • RINIT:請求初始化時的動做
  • RSHUTDOWN:請求結束時的動做

對應於 zend_module_entry 結構中的四個函數指針:函數

int (*module_startup_func)(INIT_FUNC_ARGS);        /* MINIT() */int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);   /* MSHUTDOWN() */int (*request_startup_func)(INIT_FUNC_ARGS);       /* RINIT() */int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  /* RSHUTDOWN() */

咱們在實現一款擴展時,基本都會用到上面四個週期宏定義。完整的 PHP 週期見下圖:學習

v2-481b9875201f9a84fc84d14fe58c89f8_720w.jpg

本篇文章中咱們只是介紹下跟該擴展相關的信息,若是想深刻了解,能夠參考底部連接,自行深刻學習。ui

2 PHP 擴展鉤子

在 PHP 內核中,各個執行週期幾乎都有提供可重寫的鉤子,以下表所示。加密

// AST, Zend/zend_ast.h:void (*zend_ast_process_t)(zend_ast *ast) // Compiler, Zend/zend_compile.h:zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type)zend_op_array *(*zend_compile_string)(zval *source_string, char *filename) // Executor, Zend/zend_execute.h:void (*zend_execute_ex)(zend_execute_data *execute_data)void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value) // GC, Zend/zend_gc.h:int (*gc_collect_cycles)(void) // TSRM, TSRM/TSRM.h:void (*tsrm_thread_begin_func_t)(THREAD_T thread_id)void (*tsrm_thread_end_func_t)(THREAD_T thread_id) // Error, Zend/zend.h:void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) / Exceptions, Zend/zend_exceptions.h:void (*zend_throw_exception_hook)(zval *ex) // Lifetime, Zend/zend.h:void (*zend_on_timeout)(int seconds)void (*zend_interrupt_function)(zend_execute_data *execute_data)void (*zend_ticks_function)(int ticks)

咱們能夠經過對函數指針進行重寫,來在週期中執行咱們本身的代碼。url

本次咱們只關心 Compiler 中的 zend_compile_file 函數,該函數的功能就是用來編譯 php 文件,或者咱們指定的輸入文件。spa

3 screw-plus 擴展

項目文件以下:

.├── aes.c├── aes_crypt.c├── aes.h├── b64.h├── config.h├── config.m4├── decode.c├── encode.c├── include├── LICENSE├── Makefile.in├── md5.c├── md5.h├── php_screw_plus.c├── php_screw_plus.h├── README.en├── README.jp├── README.md└── tools    ├── Makefile    └── screw.c

在 php_screw_plus.h 中第一行有定義宏 CAKEY,這個就是咱們用來混淆代碼的祕鑰。

#define CAKEY  "FwWpZKxH7twCAG4JQMO"

tools 文件夾中,是用來加密 PHP 文件的腳本,咱們直接執行 make,就會生成 screw 文件,而後用 screw 來加密 PHP 文件。tools/screw.c 中也是引入了上面的頭文件,使用了 CAKEY 這個宏,用來加密代碼。

4 流程分析

文件操做流程分析

v2-a0714d72992bf3b27b58906dd61ecba6_720w.jpg

經過 tools/screw.c 文件咱們能夠發現,在 main 函數中,主要是對文件或文件夾進行遍歷遞歸,使用固定的加解密步驟執行加密或者解密操做。

screw_encrype 爲加密函數,screw_decrypt 爲解密函數。

v2-6f1cfa58cc8ec917cca256e90ea167bc_720w.jpg

加密函數首先是對上面定義的 CAKEY 宏的值進行 md5 運算,獲得 32 位的 md5 值,而後將前 16 存入 enTag 中,後面直接寫入文件的前 16 個字節。接着會獲取文件的長度,而且寫入 16~32 的字節位置中。後續的內容以 32 位的 md5 值爲祕鑰,以 16 字節長度爲小段,採用 cbc 方式進行加密,而後返回加密以後的長度,根據長度把加密以後的內容寫入到文件中。

v2-ae2dd7974f64b3cb5744038aadf48723_720w.jpg

解密爲對應的逆操做,在此就不詳細介紹了。

5 擴展分析

php_screw_plus.c 文件就是擴展的主要實現入口。咱們能夠看到上面提到的 Compiler 時期的鉤子函數定義。

ZEND_API zend_op_array *(*org_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);

如下是 screw-plus 擴展模塊初始及結束的代碼,就是將系統的編譯函數暫存,而後用本身定義的編譯函數替換爲系統編譯函數。再結束時再還原回去。

PHP_MINIT_FUNCTION(php_screw_plus){  CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;  org_compile_file = zend_compile_file;  zend_compile_file = pm9screw_compile_file;  return SUCCESS;}{1}PHP_MSHUTDOWN_FUNCTION(php_screw_plus){  CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;  zend_compile_file = org_compile_file;  return SUCCESS;}{1}

能夠看到,在編譯 PHP 文件的時候,是會用到自定義的 pm9screw_compile_file 函數來解析。

v2-ecf759a873edc6de8839c73b56d134f0_720w.png

咱們能夠看到該函數中就是作了一些校驗,而後使用自定義的名字爲 pm9screw_ext_fopen 的 handler 對文件進行打開操做,以後又將打開的文件轉交回了系統的原始解析器。

v2-d407f9557e6a5ae7270696109265c1a4_720w.jpg

從上面 handler 的實現可知,就是使用 CAKEY 對加密的文件內容進行了解密,而後使用 tmpfile() 建立了臨時文件,把內容保存進去。

6 思路梳理

1. 設定 CAKEY 祕鑰
2. 對 CAKEY 取 md5 值,而且做爲 aes 的 key。將 md5 值的前 0~15 字節,寫入文件開始位置,獲取文件長度,將長度寫入前 16~32 字節。
3. 以 16 字節爲一段進行 cbc 方式加密,使用上面的 key 做爲祕鑰。
4. 將加密以後的內容追加寫入文件完成靜態加密。
5. 擴展在初始化的時候,使用自定義的 compile 函數;
6. 在解析時,檢測若是文件前 16 字節內容與 md5 值的前 16 字節相同,則使用解密函數對文件內容進行解密,以後寫入臨時文件中,返回給系統去執行。

以上就是整個加密解密過程。

7 優缺點

1. 加解密簡單,並且相對於上面收費的產品,該項目是開源免費。
2. 安全木桶原理,最短板就是 CAKEY 若是泄露了,就能夠解密出來。

用 vim 打開 .so 文件,而後使用 :%!xxd 使用 hex 方式顯示,咱們就能夠看到明文的祕鑰 CAKEY。

v2-029081293c74a367016bb1b133106e44_720w.jpg

或者咱們使用 gdb 對 php 進行跟蹤調試,由於上面調用了 md5 函數,對 md5 進行設斷點,能夠直接追蹤到祕鑰。

v2-855d3511c07292f7a8ced2e148d3d75d_720w.png

8 思考及改進

一、若是咱們是對生成的 .so 文件進行一些加殼壓縮混淆,好比加上 upx 殼,就會增長追蹤難度。
二、能夠基於該項目,進行一些改進,好比對祕鑰值進行稍微複雜一些的合併計算,不讓祕鑰僅爲一個字符串。再配合混淆,也是能夠適用於部分場景。

總結

以上就是本次分享的內容~

若是有什麼改進建議,也能夠在咱們評論區留言,供你們參考學習

 

相關文章
相關標籤/搜索