看我如何玩轉PHP代碼加密與解密

原文鏈接:https://xz.aliyun.com/t/2403

參考文獻:

https://xz.aliyun.com/t/2403

前言

兩次比賽,兩個題目,兩種方式,兩個程序。

一切PHP的代碼終究是要到Zend Engine上走一走的,因此一切PHP的源碼加密都是可以被解密的。(不包括OpCode混淆-VMP)

代碼混淆

比較噁心人的一種處理方式,也不太算是加密。

單獨拿出來是爲了說明代碼混淆和代碼加密是兩種方式。

本質是是對變量進行亂七八糟的修改,多用動態函數處理。處理應該沒什麼難度,就是比較複雜,浪費時間精力。

混淆方式是按照套路隨機生成相關動態函數,替換明文函數,然後批量修改變量名。

該法常與代碼加密聯合使用。

代碼加密解密

用PHP代碼進行PHP代碼的加密,套了層殼。大多數代碼加密都進行了一定的代碼混淆,不同的加密工具也有不同的混淆。

  • 殼混淆
  • 代碼混淆
  • 殼和代碼都分別混淆

常見加密工具

  • phpjiami

加密

源碼 -> 加密處理(壓縮,替換,BASE64,轉義)-> 安全處理(驗證文件 MD5 值,限制 IP、限域名、限時間、防破解、防命令行調試)-> 加密程序成品,再簡單的說:密文源碼 + 自解密外殼 == 密文代碼

加密方式

  1. 獨立加密程序統一對明文代碼進行加密處理

解密

加密也好,混淆也罷,終歸是要變成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

加密

源碼 -> 加密處理(對稱/非對稱加密、自定義加密)-> 加密成品:密文代碼

加密方式

  1. 獨立加密程序統一對明文代碼進行加密處理
  2. 擴展存在加密解密功能,執行前判斷源碼是否經過加密處理,如果沒有就進行加密

解密

還是那句話,一切的源碼都要到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的一種方式。

加密方式

  1. 獨立加密程序統一對明文代碼進行加密處理(猜測)

解密

我不會啊!emmmmmmm

參考

  1. phpjiami 數種解密方法 - PHITHON
  2. Decrypt php VoiceStar encryption extension - 小鹿師傅
  3. PHPDecode 在線解密工具 - Medici.Yan
  4. Decoding a User Space Encoded PHP Script - Stefan Esser
  5. PHP代碼加密技術 郭新華 PHPCON2018 - swoole郭新華
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章