參考文獻:
前言
兩次比賽,兩個題目,兩種方式,兩個程序。
一切PHP的代碼終究是要到Zend Engine上走一走的,因此一切PHP的源碼加密都是可以被解密的。(不包括OpCode混淆-VMP)
代碼混淆
比較噁心人的一種處理方式,也不太算是加密。
單獨拿出來是爲了說明代碼混淆和代碼加密是兩種方式。
本質是是對變量進行亂七八糟的修改,多用動態函數處理。處理應該沒什麼難度,就是比較複雜,浪費時間精力。
混淆方式是按照套路隨機生成相關動態函數,替換明文函數,然後批量修改變量名。
該法常與代碼加密聯合使用。
代碼加密解密
用PHP代碼進行PHP代碼的加密,套了層殼。大多數代碼加密都進行了一定的代碼混淆,不同的加密工具也有不同的混淆。
- 殼混淆
- 代碼混淆
- 殼和代碼都分別混淆
常見加密工具
- phpjiami
加密
源碼 -> 加密處理(壓縮,替換,BASE64,轉義)-> 安全處理(驗證文件 MD5 值,限制 IP、限域名、限時間、防破解、防命令行調試)-> 加密程序成品,再簡單的說:密文源碼 + 自解密外殼 == 密文代碼
加密方式
- 獨立加密程序統一對明文代碼進行加密處理
解密
加密也好,混淆也罷,終歸是要變成Zend Engine能處理的源碼,該“加密”方法的的根本是通過殼把代碼解密並通過eval
等函數執行代碼。
因此,只要用HOOK EVAL大法,將相關可執行代碼的函數Hook住就能拿到其中需要執行的數據,也就是我們想要得到的源碼。
調用eval
等代碼執行的函數,最終會調用PHP內核的zend_compile_string
函數。
通過PHP本身提供的一個HOOK機制,寫個插件輕鬆搞定。
// 聲明一個臨時的 compile_string 函數 static zend_op_array *(*orig_compile_string)(zval *source_string, char *filename TSRMLS_DC); // 在 PHP_MINIT_FUNCTION 中替換 orig_compile_string = zend_compile_string; zend_compile_string = phpjiami_decode_compile_string; // 在 PHP_MSHUTDOWN_FUNCTION 中恢復 zend_compile_string = orig_compile_string; // 提取 compile_string 中的代碼並保存 static zend_op_array *phpjiami_decode_compile_string(zval *source_string, char *filename TSRMLS_DC) { int c, len, yes; char *content; FILE *fp = NULL; char fn[512]; if (Z_TYPE_P(source_string) == IS_STRING) { len = Z_STRLEN_P(source_string); content = estrndup(Z_STRVAL_P(source_string), len); if (len > strlen(content)) for (c=0; c<len; c++) if (content[c] == 0) content[c] = '?'; sprintf(fn, "/tmp/%s.php", zend_get_executed_filename(TSRMLS_C)); fp = fopen(fn,"a+"); if (fp!=NULL) fprintf(fp, "<?php\n%s\n?>\n\n", content); fclose(fp); } return orig_compile_string(source_string, filename TSRMLS_CC); }
案例
Challenge: PWNHUB 公開賽 / 傻 fufu 的工作日 Writeup
擴展加密解密
將文本源碼進行加密存儲,在使用的時候通過擴展實現解密。
常見加密工具
- pm9screw
- pm9screw_plus
加密
源碼 -> 加密處理(對稱/非對稱加密、自定義加密)-> 加密成品:密文代碼
加密方式
- 獨立加密程序統一對明文代碼進行加密處理
- 擴展存在加密解密功能,執行前判斷源碼是否經過加密處理,如果沒有就進行加密
解密
還是那句話,一切的源碼都要到Zend Engine上執行,密文也得解密了再執行。那麼在最終的執行之前,提取出來就可以了。
因此Hook住zend_compile_file
函數就可以了。
但是其中有一個坑點,PHP的擴展是“棧”加載的,也就是先加載的先Hook,後執行。我們需要獲取到解密之後的內容,所以需要讓“加密”插件先執行,也就是我們的解密插件要先加載。
要實現這個操作,只需要在INI配置文件中先寫我們的插件。(不保證)
extension="decode.so" extension="encrypt.so"
這個方式需要能夠加載執行encrypt.so,我覺得這個還是可以實現的。通過一定手段獲取到encrypt.so和密文源碼以及服務器,中間件相關信息(版本等)。
利用Docker運行一個基本相同的環境應該是可以做到的。
// 聲明一個臨時的 compile_file 函數 static zend_op_array *(*orig_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC); // 在 PHP_MINIT_FUNCTION 中替換 orig_compile_file = zend_compile_file; zend_compile_file = phpjiami_decode_compile_file; // 在 PHP_MSHUTDOWN_FUNCTION 中恢復 zend_compile_file = orig_compile_file; // 提取 compile_file 中的代碼並保存 static zend_op_array *phpjiami_decode_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC){ char *buf; size_t size; if (zend_stream_fixup(file_handle, &buf, &size TSRMLS_CC) == SUCCESS) { FILE *ff = NULL; int i=0; php_printf("code size :\n%d\n\nsource code :\n%s\n\n", size, buf); ff = fopen("/tmp/decode.php","a+"); if (ff!=NULL) for(i = 0; i <= size; i++) fprintf(ff, "%c", buf[i]); fclose(ff); } return orig_compile_file(file_handle,type TSRMLS_DC); }
案例
Challenge: SCTF2018 BabySyc - Simple PHP Web Writeup - L3m0n
- phpinfo
- login.php
OpCode混淆
一種是比如Swoole Compile的方式,部分脫離了zend虛擬機,對opcode做了混淆,這就比較像是vmp的一種方式。
加密方式
- 獨立加密程序統一對明文代碼進行加密處理(猜測)
解密
我不會啊!emmmmmmm