202505最新调研:PHP Opcode加密混淆哪家强?

heguangyu5

近来看到有网友咨询PHP源码保护、防破解的问题, 我也很久没有了解了. 借机更新一下自己的认识, 了解了解市场现状.

PHP源码保护方案有多种,本文说的是对opcode进行加密混淆的方案.一般认为,这种方案的加密强度较强,保护程度也较高.

本文调研了两款PHP源码加密产品.调研过程中关注两个重点:

  1. 如何拿到加密混淆后的opcode.
  2. opcode能反编译回PHP代码吗?

为了不对产品本身造成不好的影响, 我们称这两款产品分别为 AAABBB.

AAA是国内产品,号称 "最佳PHP源代码加密编译器".
BBB是国外产品,号称 "the most widely trusted PHP protection tool".

先来看AAA.

AAA

首先,我们需要一段PHP代码作为被保护对象.这里选取一个对 PDO 类进行简易封装的 Db 类. 完整源码见: Db.php

然后,使用 AAA试用版Db.php 加密, 加密时选择 PHP版本 8.0,加密完成后下载回来,然后将对应的 AAA_loader_80_nts.so也下载回来.

php的opcache扩展有个方便的功能,可以把php代码的opcode dump出来.

$ ~/tmp/php-8.0.30/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x10000' ../Db.php

$_main:
     ; (lines=1, args=0, vars=0, tmps=0)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:1-97
     ; return  [] RANGE[0..0]
0000 RETURN int(1)

OurBlog_Db::__construct:
     ; (lines=36, args=0, vars=0, tmps=18)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:9-17
     ; return  [] RANGE[0..0]
0000 V1 = NEW 3 string("PDO")
0001 INIT_FCALL 1 96 string("getenv")
0002 SEND_VAL string("DB_HOST") 1
0003 V2 = DO_ICALL
0004 T3 = CONCAT string("mysql:host=") V2
0005 T4 = CONCAT T3 string(";port=")
0006 INIT_FCALL 1 96 string("getenv")
0007 SEND_VAL string("DB_PORT") 1
0008 V5 = DO_ICALL
0009 T6 = CONCAT T4 V5
0010 T7 = CONCAT T6 string(";dbname=")
0011 INIT_FCALL 1 96 string("getenv")
0012 SEND_VAL string("DB_DATABASE") 1
0013 V8 = DO_ICALL
0014 T9 = CONCAT T7 V8
0015 T10 = CONCAT T9 string(";charset=utf8")
0016 SEND_VAL_EX T10 1
0017 INIT_FCALL 1 96 string("getenv")
0018 SEND_VAL string("DB_USER") 1
0019 V11 = DO_ICALL
0020 SEND_VAR_NO_REF_EX V11 2
0021 INIT_FCALL 1 96 string("getenv")
0022 SEND_VAL string("DB_PASSWORD") 1
0023 V12 = DO_ICALL
0024 SEND_VAR_NO_REF_EX V12 3
0025 DO_FCALL
0026 ASSIGN_OBJ THIS string("pdo")
0027 OP_DATA V1
0028 T14 = FETCH_OBJ_R THIS string("pdo")
0029 INIT_METHOD_CALL 2 T14 string("setAttribute")
0030 T15 = FETCH_CLASS_CONSTANT string("PDO") string("ATTR_ERRMODE")
0031 SEND_VAL_EX T15 1
0032 T16 = FETCH_CLASS_CONSTANT string("PDO") string("ERRMODE_EXCEPTION")
0033 SEND_VAL_EX T16 2
0034 DO_FCALL
0035 RETURN null
LIVE RANGES:
     1: 0001 - 0026 (new)
     4: 0006 - 0009 (tmp/var)
     7: 0011 - 0014 (tmp/var)

OurBlog_Db::__clone:
     ; (lines=1, args=0, vars=0, tmps=0)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:19-20
     ; return  [] RANGE[0..0]
0000 RETURN null

OurBlog_Db::getInstance:
     ; (lines=10, args=0, vars=0, tmps=6)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:22-28
     ; return  [] RANGE[0..0]
0000 T0 = FETCH_STATIC_PROP_R string("instance") (self) (exception)
0001 T1 = IS_EQUAL T0 null
0002 JMPZ T1 0007
0003 V3 = NEW 0 (self) (exception)
0004 DO_FCALL
0005 ASSIGN_STATIC_PROP string("instance")
0006 OP_DATA V3
0007 T5 = FETCH_STATIC_PROP_R string("instance") (self) (exception)
0008 RETURN T5
0009 RETURN null
LIVE RANGES:
     3: 0004 - 0005 (new)

OurBlog_Db::fetchOne:
     ; (lines=14, args=2, vars=3, tmps=5)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:30-35
     ; return  [] RANGE[0..0]
0000 CV0($sql) = RECV 1
0001 CV1($bind) = RECV_INIT 2 array(...)
0002 T3 = FETCH_OBJ_R THIS string("pdo")
0003 INIT_METHOD_CALL 1 T3 string("prepare")
0004 SEND_VAR_EX CV0($sql) 1
0005 V4 = DO_FCALL
0006 ASSIGN CV2($stmt) V4
0007 INIT_METHOD_CALL 1 CV2($stmt) string("execute")
0008 SEND_VAR_EX CV1($bind) 1
0009 DO_FCALL
0010 INIT_METHOD_CALL 0 CV2($stmt) string("fetchColumn")
0011 V7 = DO_FCALL
0012 RETURN V7
0013 RETURN null

OurBlog_Db::fetchRow:
     ; (lines=16, args=3, vars=4, tmps=5)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:37-42
     ; return  [] RANGE[0..0]
0000 CV0($sql) = RECV 1
0001 CV1($bind) = RECV_INIT 2 array(...)
0002 CV2($style) = RECV_INIT 3 zval(type=11)
0003 T4 = FETCH_OBJ_R THIS string("pdo")
0004 INIT_METHOD_CALL 1 T4 string("prepare")
0005 SEND_VAR_EX CV0($sql) 1
0006 V5 = DO_FCALL
0007 ASSIGN CV3($stmt) V5
0008 INIT_METHOD_CALL 1 CV3($stmt) string("execute")
0009 SEND_VAR_EX CV1($bind) 1
0010 DO_FCALL
0011 INIT_METHOD_CALL 1 CV3($stmt) string("fetch")
0012 SEND_VAR_EX CV2($style) 1
0013 V8 = DO_FCALL
0014 RETURN V8
0015 RETURN null

OurBlog_Db::fetchAll:
     ; (lines=16, args=3, vars=4, tmps=5)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:44-49
     ; return  [] RANGE[0..0]
0000 CV0($sql) = RECV 1
0001 CV1($bind) = RECV_INIT 2 array(...)
0002 CV2($style) = RECV_INIT 3 zval(type=11)
0003 T4 = FETCH_OBJ_R THIS string("pdo")
0004 INIT_METHOD_CALL 1 T4 string("prepare")
0005 SEND_VAR_EX CV0($sql) 1
0006 V5 = DO_FCALL
0007 ASSIGN CV3($stmt) V5
0008 INIT_METHOD_CALL 1 CV3($stmt) string("execute")
0009 SEND_VAR_EX CV1($bind) 1
0010 DO_FCALL
0011 INIT_METHOD_CALL 1 CV3($stmt) string("fetchAll")
0012 SEND_VAR_EX CV2($style) 1
0013 V8 = DO_FCALL
0014 RETURN V8
0015 RETURN null

OurBlog_Db::fetchCol:
     ; (lines=16, args=2, vars=3, tmps=6)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:51-56
     ; return  [] RANGE[0..0]
0000 CV0($sql) = RECV 1
0001 CV1($bind) = RECV_INIT 2 array(...)
0002 T3 = FETCH_OBJ_R THIS string("pdo")
0003 INIT_METHOD_CALL 1 T3 string("prepare")
0004 SEND_VAR_EX CV0($sql) 1
0005 V4 = DO_FCALL
0006 ASSIGN CV2($stmt) V4
0007 INIT_METHOD_CALL 1 CV2($stmt) string("execute")
0008 SEND_VAR_EX CV1($bind) 1
0009 DO_FCALL
0010 INIT_METHOD_CALL 1 CV2($stmt) string("fetchAll")
0011 T7 = FETCH_CLASS_CONSTANT string("PDO") string("FETCH_COLUMN")
0012 SEND_VAL_EX T7 1
0013 V8 = DO_FCALL
0014 RETURN V8
0015 RETURN null

OurBlog_Db::insert:
     ; (lines=58, args=2, vars=7, tmps=31)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:58-74
     ; return  [] RANGE[0..0]
0000 CV0($table) = RECV 1
0001 CV1($row) = RECV 2
0002 T7 = BOOL_NOT CV1($row)
0003 JMPZ T7 0012
0004 T8 = FETCH_OBJ_R THIS string("pdo")
0005 INIT_METHOD_CALL 1 T8 string("exec")
0006 T10 = ROPE_INIT 3 string("INSERT INTO `")
0007 T10 = ROPE_ADD 1 T10 CV0($table)
0008 T9 = ROPE_END 2 T10 string("` VALUES (NULL)")
0009 SEND_VAL_EX T9 1
0010 DO_FCALL
0011 RETURN null
0012 ASSIGN CV2($columns) array(...)
0013 INIT_FCALL 1 96 string("array_keys")
0014 SEND_VAR CV1($row) 1
0015 V14 = DO_ICALL
0016 V15 = FE_RESET_R V14 0024
0017 FE_FETCH_R V15 CV3($key) 0024
0018 T18 = ROPE_INIT 3 string("`")
0019 T18 = ROPE_ADD 1 T18 CV3($key)
0020 T17 = ROPE_END 2 T18 string("`")
0021 ASSIGN_DIM CV2($columns) NEXT
0022 OP_DATA T17
0023 JMP 0017
0024 FE_FREE V15
0025 INIT_FCALL 2 112 string("implode")
0026 SEND_VAL string(",") 1
0027 SEND_VAR CV2($columns) 2
0028 V20 = DO_ICALL
0029 ASSIGN CV2($columns) V20
0030 INIT_FCALL 2 112 string("str_repeat")
0031 SEND_VAL string("?,") 1
0032 T22 = COUNT CV1($row)
0033 T23 = SUB T22 int(1)
0034 SEND_VAL T23 2
0035 V24 = DO_ICALL
0036 T25 = CONCAT V24 string("?")
0037 ASSIGN CV4($placeholders) T25
0038 T28 = ROPE_INIT 7 string("INSERT INTO `")
0039 T28 = ROPE_ADD 1 T28 CV0($table)
0040 T28 = ROPE_ADD 2 T28 string("` (")
0041 T28 = ROPE_ADD 3 T28 CV2($columns)
0042 T28 = ROPE_ADD 4 T28 string(") VALUES (")
0043 T28 = ROPE_ADD 5 T28 CV4($placeholders)
0044 T27 = ROPE_END 6 T28 string(")")
0045 ASSIGN CV5($sql) T27
0046 T33 = FETCH_OBJ_R THIS string("pdo")
0047 INIT_METHOD_CALL 1 T33 string("prepare")
0048 SEND_VAR_EX CV5($sql) 1
0049 V34 = DO_FCALL
0050 ASSIGN CV6($stmt) V34
0051 INIT_METHOD_CALL 1 CV6($stmt) string("execute")
0052 INIT_FCALL 1 96 string("array_values")
0053 SEND_VAR CV1($row) 1
0054 V36 = DO_ICALL
0055 SEND_VAR_NO_REF_EX V36 1
0056 DO_FCALL
0057 RETURN null
LIVE RANGES:
     10: 0006 - 0008 (rope)
     15: 0017 - 0024 (loop)
     18: 0018 - 0020 (rope)
     28: 0038 - 0044 (rope)

OurBlog_Db::update:
     ; (lines=46, args=3, vars=7, tmps=22)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:76-90
     ; return  [] RANGE[0..0]
0000 CV0($table) = RECV 1
0001 CV1($row) = RECV 2
0002 CV2($where) = RECV_INIT 3 string("1")
0003 T7 = BOOL_NOT CV1($row)
0004 JMPZ T7 0009
0005 V8 = NEW 1 string("Exception")
0006 SEND_VAL_EX string("update with empty row is not allowed!") 1
0007 DO_FCALL
0008 THROW V8
0009 ASSIGN CV3($updateData) array(...)
0010 INIT_FCALL 1 96 string("array_keys")
0011 SEND_VAR CV1($row) 1
0012 V11 = DO_ICALL
0013 V12 = FE_RESET_R V11 0021
0014 FE_FETCH_R V12 CV4($key) 0021
0015 T15 = ROPE_INIT 3 string("`")
0016 T15 = ROPE_ADD 1 T15 CV4($key)
0017 T14 = ROPE_END 2 T15 string("` = ?")
0018 ASSIGN_DIM CV3($updateData) NEXT
0019 OP_DATA T14
0020 JMP 0014
0021 FE_FREE V12
0022 INIT_FCALL 2 112 string("implode")
0023 SEND_VAL string(",") 1
0024 SEND_VAR CV3($updateData) 2
0025 V17 = DO_ICALL
0026 ASSIGN CV3($updateData) V17
0027 T20 = ROPE_INIT 6 string("UPDATE `")
0028 T20 = ROPE_ADD 1 T20 CV0($table)
0029 T20 = ROPE_ADD 2 T20 string("` SET ")
0030 T20 = ROPE_ADD 3 T20 CV3($updateData)
0031 T20 = ROPE_ADD 4 T20 string(" WHERE ")
0032 T19 = ROPE_END 5 T20 CV2($where)
0033 ASSIGN CV5($sql) T19
0034 T24 = FETCH_OBJ_R THIS string("pdo")
0035 INIT_METHOD_CALL 1 T24 string("prepare")
0036 SEND_VAR_EX CV5($sql) 1
0037 V25 = DO_FCALL
0038 ASSIGN CV6($stmt) V25
0039 INIT_METHOD_CALL 1 CV6($stmt) string("execute")
0040 INIT_FCALL 1 96 string("array_values")
0041 SEND_VAR CV1($row) 1
0042 V27 = DO_ICALL
0043 SEND_VAR_NO_REF_EX V27 1
0044 DO_FCALL
0045 RETURN null
LIVE RANGES:
     8: 0006 - 0008 (new)
     12: 0014 - 0021 (loop)
     15: 0015 - 0017 (rope)
     20: 0027 - 0032 (rope)

OurBlog_Db::__call:
     ; (lines=11, args=2, vars=2, tmps=3)
     ; (before optimizer)
     ; /home/hgy/Downloads/php-opcode-test/Db.php:92-95
     ; return  [] RANGE[0..0]
0000 CV0($name) = RECV 1
0001 CV1($args) = RECV 2
0002 T2 = FETCH_OBJ_R THIS string("pdo")
0003 T3 = INIT_ARRAY 2 (packed) T2 NEXT
0004 T3 = ADD_ARRAY_ELEMENT CV0($name) NEXT
0005 INIT_USER_CALL 0 string("call_user_func_array") T3
0006 SEND_ARRAY 0 CV1($args)
0007 CHECK_UNDEF_ARGS
0008 V4 = DO_FCALL
0009 RETURN V4
0010 RETURN null
LIVE RANGES:
     3: 0004 - 0005 (tmp/var)

现在我们拿到了 Db.php 未加密混淆的 opcode.

再来看看 AAA 加密混淆过的 Db-AAA.php 的 opcode 长什么样.
AAA_loader_80_nts.so 加到 php.ini 里并配置好.

~/tmp/php-8.0.30/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x1000' Db-AAA.php

什么输出都没有.

可以理解,应该是 AAA_loader_80_nts.so 来接管处理 Db-AAA.php, opcache扩展不起作用了.

那还有什么办法能拿到opcode吗?可以用 phpdbg.

$ ~/tmp/php-8.0.30/bin/phpdbg -p* Db-AAA.php
function name: (null)
L1-97 {main}() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e09cb0 + 1 ops
 L97   #0     FETCH_DIM_W<-1>         1                    NEXT

user class: OurBlog_Db
10 methods: __construct, __clone, getInstance, fetchOne, fetchRow, fetchAll, fetchCol, insert, update, __call

function name: __construct
L9-17 OurBlog_Db::__construct() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e065a0 + 36 ops
 L11   #0     NEW<3>                  "PDO"                                     @0
 L2147483647 #1     YIELD<1>                                                          "VTM]\\V"
 L1073741823 #2     PRE_INC                                      "ssipe  "
 L12   #3     DO_FCALL                                                          @1
 L12   #4     FETCH_DIM_W             "mysql:host="        @1                   ~2
 L12   #5     FETCH_DIM_W             ~2                   ";port="             ~1
 L2147483647 #6     MATCH_ERROR<1>                                                    "VTM]\\V"
 L1073741823 #7     JMPZ_EX                                      "ssihe  "
 L12   #8     DO_FCALL                                                          @3
 L12   #9     FETCH_DIM_W             ~1                   @3                   ~2
 L12   #10    FETCH_DIM_W             ~2                   ";dbname="           ~1
 L2147483647 #11    MATCH_ERROR<1>                                                    "VTM]\\V"
 L1073741823 #12    JMPZ_EX                                      "usmru~  eyu"
 L12   #13    DO_FCALL                                                          @3
 L12   #14    FETCH_DIM_W             ~1                   @3                   ~2
 L12   #15    FETCH_DIM_W             ~2                   ";charset=utf8"      ~1
 L1073741823 #16    UNKNOWN                                      ~1
 L2147483647 #17    BOOL_XOR<1>                                                       "VTM]\\V"
 L1073741823 #18    FE_RESET_RW                                  "ssimy \t"
 L13   #19    DO_FCALL                                                          @1
 L13   #20    SEND_USER               @1                   2
 L2147483647 #21    MATCH_ERROR<1>                                                    "VTM]\\V"
 L1073741823 #22    UNKNOWN                                      "usmfuy  kxt"
 L14   #23    DO_FCALL                                                          @1
 L14   #24    SEND_USER               @1                   3
 L14   #25    DO_FCALL
 L11   #26    FETCH_DIM_W                                  "pdo"
 L14   #27    FETCH_DIM_W             @0                   NEXT
 L4294967295 #28    EXT_STMT<72>            ~0                                        "FTY"
 L2147483647 #29    CASE<2>                                      ~0                   "AVMw@TS)FGLU"
 L16   #30    FETCH_DIM_W<112>        "PDO"                "ATTR_ERRMODE"       ~0
 L1073741823 #31    SR                                           ~0
 L16   #32    FETCH_DIM_W<128>        "PDO"                "ERRMODE_EXCEPTION"  ~0
 L1073741823 #33    GET_CLASS                                    ~0
 L16   #34    DO_FCALL
 L17   #35    FETCH_DIM_W<-1>         null                 NEXT

function name: __clone
L19-20 OurBlog_Db::__clone() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e06a98 + 1 ops
 L20   #0     FETCH_DIM_W<-1>         null                 NEXT

function name: getinstance
L22-28 OurBlog_Db::getInstance() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e06b80 + 9 ops
 L24   #0     FETCH_DIM_W             "instance"           NEXT                 ~1
 L24   #1     FETCH_DIM_W             ~1                   null
 L24   #2     JMPZ                    ~0                   J7
 L25   #3     NEW                                                               @0
 L25   #4     DO_FCALL
 L25   #5     FETCH_DIM_W<24>         "instance"           NEXT
 L25   #6     FETCH_DIM_W             @0                   NEXT
 L27   #7     FETCH_DIM_W<48>         "instance"           NEXT                 ~0
 L27   #8     FETCH_DIM_W             ~0                   NEXT

function name: fetchone
L30-35 OurBlog_Db::fetchOne() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e06eb8 + 13 ops
 L30   #0     RECV                    1                                         $Sh40
 L30   #1     RECV_INIT               2                    array(0)             $Sh41
 L4294967295 #2     UNKNOWN                 ~0                                        "FTY"
 L2147483647 #3     FETCH_DIM_W<1>                               ~0                   "AA\\GXRD"
 L32   #4     SEND_USER               $Sh40                1
 L32   #5     DO_FCALL                                                          @0
 L32   #6     FETCH_DIM_W             @0                   NEXT                 $Sh42
 L2147483647 #7     BIND_LEXICAL<1>                              $Sh42                "TK\\TLTD"
 L33   #8     SEND_USER               $Sh41                1
 L33   #9     DO_FCALL
 L2147483647 #10    UNKNOWN                                      $Sh42                "TTMU_cN,Q_V"
 L34   #11    DO_FCALL                                                          @0
 L34   #12    FETCH_DIM_W             @0                   NEXT

function name: fetchrow
L37-42 OurBlog_Db::fetchRow() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e07370 + 15 ops
 L37   #0     RECV                    1                                         $Sh43
 L37   #1     RECV_INIT               2                    array(0)             $Sh44
 L37   #2     RECV_INIT               3                    <constant>           $Sh45
 L4294967295 #3     DECLARE_LAMBDA_FUNCTION ~0                                        "FTY"
 L2147483647 #4     UNKNOWN<1>                                   ~0                   "AA\\GXRD"
 L39   #5     SEND_USER               $Sh43                1
 L39   #6     DO_FCALL                                                          @0
 L39   #7     FETCH_DIM_W             @0                   NEXT                 $Sh46
 L2147483647 #8     SWITCH_STRING<1>                             $Sh46                "TK\\TLTD"
 L40   #9     SEND_USER               $Sh44                1
 L40   #10    DO_FCALL
 L2147483647 #11    BW_XOR<1>                                    $Sh46                "_\\LVH"
 L41   #12    SEND_USER               $Sh45                1
 L41   #13    DO_FCALL                                                          @0
 L41   #14    FETCH_DIM_W             @0                   NEXT

function name: fetchall
L44-49 OurBlog_Db::fetchAll() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e078a0 + 15 ops
 L44   #0     RECV                    1                                         $Sh47
 L44   #1     RECV_INIT               2                    array(0)             $Sh48
 L44   #2     RECV_INIT               3                    <constant>           $Sh49
 L4294967295 #3     UNKNOWN                 ~0                                        "FTY"
 L2147483647 #4     UNKNOWN<1>                                   ~0                   "AA\\GXRD"
 L46   #5     SEND_USER               $Sh47                1
 L46   #6     DO_FCALL                                                          @0
 L46   #7     FETCH_DIM_W             @0                   NEXT                 $Sh410
 L2147483647 #8     UNKNOWN<1>                                   $Sh410               "TK\\TLTD"
 L47   #9     SEND_USER               $Sh48                1
 L47   #10    DO_FCALL
 L2147483647 #11    YIELD_FROM<1>                                $Sh410               "WPMT^aM,"
 L48   #12    SEND_USER               $Sh49                1
 L48   #13    DO_FCALL                                                          @0
 L48   #14    FETCH_DIM_W             @0                   NEXT

function name: fetchcol
L51-56 OurBlog_Db::fetchCol() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e07da8 + 15 ops
 L51   #0     RECV                    1                                         $Sh411
 L51   #1     RECV_INIT               2                    array(0)             $Sh412
 L4294967295 #2     UNKNOWN                 ~0                                        "FTY"
 L2147483647 #3     FETCH_DIM_W<1>                               ~0                   "AA\\GXRD"
 L53   #4     SEND_USER               $Sh411               1
 L53   #5     DO_FCALL                                                          @0
 L53   #6     FETCH_DIM_W             @0                   NEXT                 $Sh413
 L2147483647 #7     CASE<1>                                      $Sh413               "TK\\TLTD"
 L54   #8     SEND_USER               $Sh412               1
 L54   #9     DO_FCALL
 L2147483647 #10    BIND_LEXICAL<1>                              $Sh413               "WPMT^aM,"
 L55   #11    FETCH_DIM_W<72>         "PDO"                "FETCH_COLUMN"       ~0
 L1073741823 #12    UNKNOWN                                      ~0
 L55   #13    DO_FCALL                                                          @0
 L55   #14    FETCH_DIM_W             @0                   NEXT

function name: insert
L58-74 OurBlog_Db::insert() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e08500 + 55 ops
 L58   #0     RECV                    1                                         $Sh414
 L58   #1     RECV                    2                                         $Sh415
 L60   #2     JMPNZ                   $Sh415               J11
 L4294967295 #3     CONCAT                  ~0                                        "FTY"
 L2147483647 #4     SWITCH_STRING<1>                             ~0                   "RA]["
 L61   #5     DECLARE_ANON_CLASS<3>                        "INSERT INTO `"      ~1
 L61   #6     DECLARE_ANON_CLASS<1>   ~1                   $Sh414               ~1
 L61   #7     FETCH_DIM_W<2>          ~1                   "` VALUES (NULL)"    ~0
 L1073741823 #8     UNKNOWN                                      ~0
 L61   #9     DO_FCALL
 L62   #10    FETCH_DIM_W             null                 NEXT
 L65   #11    FETCH_DIM_W             array(0)             NEXT                 $Sh416
 L2147483647 #12    DEFINED<1>                                                        "PKKVIJ%]A"
 L66   #13    SEND_USER               $Sh415               1
 L66   #14    DO_FCALL                                                          @1
 L66   #15    FETCH_DIM_W             @1                   NEXT                 @0
 L66   #16    FETCH_DIM_W<224>        @0                   $Sh417
 L67   #17    DECLARE_ANON_CLASS<3>                        "`"                  ~2
 L67   #18    DECLARE_ANON_CLASS<1>   ~2                   $Sh417               ~2
 L67   #19    FETCH_DIM_W<2>          ~2                   "`"                  ~1
 L67   #20    FETCH_DIM_W             $Sh416               NEXT
 L67   #21    FETCH_DIM_W             ~1                   NEXT
 L66   #22    FETCH_DIM_W                                  NEXT
 L66   #23    FE_FREE                 @0
 L2147483647 #24    BW_NOT<2>                                                         "X^I[VDD"
 L1073741823 #25    FETCH_OBJ_IS            THIS                 " "
 L69   #26    SEND_USER               $Sh416               2
 L69   #27    DO_FCALL                                                          @0
 L69   #28    FETCH_DIM_W             $Sh416               @0
 L2147483647 #29    MUL<2>                                                            "BMKhBEQ%EF"
 L1073741823 #30    FE_RESET_RW                                  "  "
 L70   #31    FETCH_DIM_W             $Sh415               NEXT                 ~1
 L70   #32    FETCH_DIM_W             ~1                   1                    ~0
 L70   #33    SEND_USER               ~0                   2
 L70   #34    DO_FCALL                                                          @1
 L70   #35    FETCH_DIM_W             @1                   "?"                  $Sh418
 L71   #36    DECLARE_ANON_CLASS<7>                        "INSERT INTO `"      ~1
 L71   #37    DECLARE_ANON_CLASS<1>   ~1                   $Sh414               ~1
 L71   #38    DECLARE_ANON_CLASS<2>   ~1                   "` ("                ~1
 L71   #39    DECLARE_ANON_CLASS<3>   ~1                   $Sh416               ~1
 L71   #40    DECLARE_ANON_CLASS<4>   ~1                   ") VALUES ("         ~1
 L71   #41    DECLARE_ANON_CLASS<5>   ~1                   $Sh418               ~1
 L71   #42    FETCH_DIM_W<6>          ~1                   ")"                  $Sh419
 L4294967295 #43    CONCAT<64>              ~0                                        "FTY"
 L2147483647 #44    BIND_LEXICAL<1>                              ~0                   "AA\\GXRD"
 L72   #45    SEND_USER               $Sh419               1
 L72   #46    DO_FCALL                                                          @0
 L72   #47    FETCH_DIM_W             @0                   NEXT                 $Sh420
 L2147483647 #48    UNKNOWN<1>                                   $Sh420               "TK\\TLTD"
 L2147483647 #49    MATCH_ERROR<1>                                                    "SAKWMW!HG]C"
 L73   #50    SEND_USER               $Sh415               1
 L73   #51    DO_FCALL                                                          @0
 L73   #52    SEND_USER               @0                   1
 L73   #53    DO_FCALL
 L74   #54    FETCH_DIM_W<-1>         null                 NEXT

function name: update
L76-90 OurBlog_Db::update() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e09190 + 44 ops
 L76   #0     RECV                    1                                         $Sh421
 L76   #1     RECV                    2                                         $Sh422
 L76   #2     RECV_INIT               3                    "1"                  $Sh423
 L78   #3     JMPNZ                   $Sh422               J8
 L79   #4     NEW<1>                  "Exception"                               @0
 L1073741823 #5     UNKNOWN                                      "update with empty"+
 L79   #6     DO_FCALL
 L79   #7     FETCH_DIM_W             @0                   NEXT
 L82   #8     FETCH_DIM_W             array(0)             NEXT                 $Sh424
 L2147483647 #9     MUL<1>                                                            "PKKVIJ%]A"
 L83   #10    SEND_USER               $Sh422               1
 L83   #11    DO_FCALL                                                          @1
 L83   #12    FETCH_DIM_W             @1                   NEXT                 @0
 L83   #13    FETCH_DIM_W<224>        @0                   $Sh425
 L84   #14    DECLARE_ANON_CLASS<3>                        "`"                  ~2
 L84   #15    DECLARE_ANON_CLASS<1>   ~2                   $Sh425               ~2
 L84   #16    FETCH_DIM_W<2>          ~2                   "` = ?"              ~1
 L84   #17    FETCH_DIM_W             $Sh424               NEXT
 L84   #18    FETCH_DIM_W             ~1                   NEXT
 L83   #19    FETCH_DIM_W                                  NEXT
 L83   #20    FE_FREE                 @0
 L2147483647 #21    FETCH_FUNC_ARG<2>                                                 "X^I[VDD"
 L1073741823 #22    UNKNOWN                                      " "
 L86   #23    SEND_USER               $Sh424               2
 L86   #24    DO_FCALL                                                          @0
 L86   #25    FETCH_DIM_W             $Sh424               @0
 L87   #26    DECLARE_ANON_CLASS<6>                        "UPDATE `"           ~1
 L87   #27    DECLARE_ANON_CLASS<1>   ~1                   $Sh421               ~1
 L87   #28    DECLARE_ANON_CLASS<2>   ~1                   "` SET "             ~1
 L87   #29    DECLARE_ANON_CLASS<3>   ~1                   $Sh424               ~1
 L87   #30    DECLARE_ANON_CLASS<4>   ~1                   " WHERE "            ~1
 L87   #31    FETCH_DIM_W<5>          ~1                   $Sh423               $Sh426
 L4294967295 #32    POST_INC<24>            ~0                                        "FTY"
 L2147483647 #33    BW_XOR<1>                                    ~0                   "AA\\GXRD"
 L88   #34    SEND_USER               $Sh426               1
 L88   #35    DO_FCALL                                                          @0
 L88   #36    FETCH_DIM_W             @0                   NEXT                 $Sh427
 L2147483647 #37    UNKNOWN<1>                                   $Sh427               "TK\\TLTD"
 L2147483647 #38    FETCH_FUNC_ARG<1>                                                 "SAKWMW!HG]C"
 L89   #39    SEND_USER               $Sh422               1
 L89   #40    DO_FCALL                                                          @0
 L89   #41    SEND_USER               @0                   1
 L89   #42    DO_FCALL
 L90   #43    FETCH_DIM_W<-1>         null                 NEXT

function name: __call
L92-95 OurBlog_Db::__call() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e09998 + 10 ops
 L92   #0     RECV                    1                                         $Sh428
 L92   #1     RECV                    2                                         $Sh429
 L4294967295 #2     DECLARE_LAMBDA_FUNCTION ~1                                        "FTY"
 L94   #3     FETCH_DIM_W<8>          ~1                   NEXT                 ~0
 L94   #4     DECLARE_ANON_CLASS      $Sh428                                    ~0
 L94   #5     NEW                     "call_user_func_ar"+ ~0
 L94   #6     SEND_UNPACK             $Sh429
 L94   #7     FETCH_DIM_W                                  NEXT
 L94   #8     DO_FCALL                                                          @0
 L94   #9     FETCH_DIM_W             @0                   NEXT
[Script ended normally]

不过 phpdbg 输出的 opcode 没有 opcache 输出的易读,比如最后一个函数OurBlog_Db::__call()里的call_user_func_array()没显示完整,只显示了个call_user_func_ar.

有没有办法让 php-8.0.30 的 phpdbg 输出像 opcache 那种样式的 opcode 呢?

这里只所以要强调 php-8.0.30 的 phpdbg , 是因为 php-8.3 的 phpdbg 输出的opcode已经和 opcache 风格统一了.

我们可以对 phpdbg 稍做修改,把 opcache 输出 opcode 的代码用在 phpdbg 里,这样就可以了.

给phpdbg新加一个参数-p**,来调用opcache里的dump相关代码.

$ ~/tmp/php-8.0.30/bin/phpdbg -p** Db-AAA.php

$_main:
     ; (lines=1, args=0, vars=0, tmps=0)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:1-97
     ; return  []
0000 FETCH_DIM_W int(1) NEXT

OurBlog_Db::__construct:
     ; (lines=36, args=0, vars=0, tmps=4, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:9-17
     ; return  [class] RANGE[--..136834057266072]
0000 V0 = NEW 3 string("PDO")
0001 YIELD (function) string("VTM]\V")
0002 PRE_INC string("ssipe")
0003 V1 = DO_FCALL
0004 T2 = FETCH_DIM_W string("mysql:host=") V1
0005 T1 = FETCH_DIM_W T2 string(";port=")
0006 MATCH_ERROR string("VTM]\V")
0007 JMPZ_EX string("ssihe")
0008 V3 = DO_FCALL
0009 T2 = FETCH_DIM_W T1 V3
0010 T1 = FETCH_DIM_W T2 string(";dbname=")
0011 MATCH_ERROR string("VTM]\V")
0012 JMPZ_EX string("usmru~eyu")
0013 V3 = DO_FCALL
0014 T2 = FETCH_DIM_W T1 V3
0015 T1 = FETCH_DIM_W T2 string(";charset=utf8")
0016 OP_242 T1
0017 BOOL_XOR string("VTM]\V")
0018 FE_RESET_RW string("ssimy  ")
0019 V1 = DO_FCALL
0020 SEND_USER V1 2
0021 MATCH_ERROR string("VTM]\V")
0022 OP_244 string("usmfuy
                          kxt")
0023 V1 = DO_FCALL
0024 SEND_USER V1 3
0025 DO_FCALL
0026 FETCH_DIM_W string("pdo")
0027 FETCH_DIM_W V0 NEXT
0028 EXT_STMT T0 string("FTY")
0029 CASE T0 string("AVMw@TS)FGLU")
0030 T0 = FETCH_DIM_W string("PDO") string("ATTR_ERRMODE")
0031 SR T0
0032 T0 = FETCH_DIM_W string("PDO") string("ERRMODE_EXCEPTION")
0033 GET_CLASS T0
0034 DO_FCALL
0035 FETCH_DIM_W null NEXT

OurBlog_Db::__clone:
     ; (lines=1, args=0, vars=0, tmps=0)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:19-20
     ; return  [undef, ref, class] RANGE[--..136834057268056]
0000 FETCH_DIM_W null NEXT

OurBlog_Db::getInstance:
     ; (lines=9, args=0, vars=0, tmps=2, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:22-28
     ; return  [] RANGE[--..2207613190024]
0000 T1 = FETCH_DIM_W string("instance") NEXT
0001 T0 = FETCH_DIM_W T1 null
0002 JMPZ T0 0007
0003 V0 = NEW 0 (self) (exception)
0004 DO_FCALL
0005 FETCH_DIM_W string("instance") NEXT
0006 FETCH_DIM_W V0 NEXT
0007 T0 = FETCH_DIM_W string("instance") NEXT
0008 FETCH_DIM_W T0 NEXT

OurBlog_Db::fetchOne:
     ; (lines=13, args=2, vars=3, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:30-35
     ; return  [ref, class] RANGE[--..6262542]
0000 CV0($Sh40) = RECV 1
0001 CV1($Sh41) = RECV_INIT 2 array(...)
0002 OP_204 T3 string("FTY")
0003 FETCH_DIM_W T3 string("AA\GXRD")
0004 SEND_USER CV0($Sh40) 1
0005 V3 = DO_FCALL
0006 CV2($Sh42) = FETCH_DIM_W V3 NEXT
0007 BIND_LEXICAL (ref) CV2($Sh42) string("TK\TLTD")
0008 SEND_USER CV1($Sh41) 1
0009 DO_FCALL
0010 OP_216 CV2($Sh42) string("TTMU_cN,Q_V")
0011 V3 = DO_FCALL
0012 FETCH_DIM_W V3 NEXT

OurBlog_Db::fetchRow:
     ; (lines=15, args=3, vars=4, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:37-42
     ; return  [class] RANGE[--..136834057270096]
0000 CV0($Sh43) = RECV 1
0001 CV1($Sh44) = RECV_INIT 2 array(...)
0002 CV2($Sh45) = RECV_INIT 3 zval(type=11)
0003 DECLARE_LAMBDA_FUNCTION T4 string("FTY")
0004 OP_216 T4 string("AA\GXRD")
0005 SEND_USER CV0($Sh43) 1
0006 V4 = DO_FCALL
0007 CV3($Sh46) = FETCH_DIM_W V4 NEXT
0008 SWITCH_STRING CV3($Sh46) 0008 string("TK\TLTD")
0009 SEND_USER CV1($Sh44) 1
0010 DO_FCALL
0011 BW_XOR CV3($Sh46) string("_\LVH")
0012 SEND_USER CV2($Sh45) 1
0013 V4 = DO_FCALL
0014 FETCH_DIM_W V4 NEXT

OurBlog_Db::fetchAll:
     ; (lines=15, args=3, vars=4, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:44-49
     ; return  [class] RANGE[--..136834057271416]
0000 CV0($Sh47) = RECV 1
0001 CV1($Sh48) = RECV_INIT 2 array(...)
0002 CV2($Sh49) = RECV_INIT 3 zval(type=11)
0003 OP_204 T4 string("FTY")
0004 OP_246 T4 string("AA\GXRD")
0005 SEND_USER CV0($Sh47) 1
0006 V4 = DO_FCALL
0007 CV3($Sh410) = FETCH_DIM_W V4 NEXT
0008 OP_220 CV3($Sh410) string("TK\TLTD")
0009 SEND_USER CV1($Sh48) 1
0010 DO_FCALL
0011 YIELD_FROM CV3($Sh410) string("WPMT^aM,")
0012 SEND_USER CV2($Sh49) 1
0013 V4 = DO_FCALL
0014 FETCH_DIM_W V4 NEXT

OurBlog_Db::fetchCol:
     ; (lines=15, args=2, vars=3, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:51-56
     ; return  [ref, class] RANGE[--..136834057272704]
0000 CV0($Sh411) = RECV 1
0001 CV1($Sh412) = RECV_INIT 2 array(...)
0002 OP_222 T3 string("FTY")
0003 FETCH_DIM_W T3 string("AA\GXRD")
0004 SEND_USER CV0($Sh411) 1
0005 V3 = DO_FCALL
0006 CV2($Sh413) = FETCH_DIM_W V3 NEXT
0007 CASE CV2($Sh413) string("TK\TLTD")
0008 SEND_USER CV1($Sh412) 1
0009 DO_FCALL
0010 BIND_LEXICAL (ref) CV2($Sh413) string("WPMT^aM,")
0011 T3 = FETCH_DIM_W string("PDO") string("FETCH_COLUMN")
0012 OP_231 T3
0013 V3 = DO_FCALL
0014 FETCH_DIM_W V3 NEXT

OurBlog_Db::insert:
     ; (lines=55, args=2, vars=7, tmps=5, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:58-74
     ; return  [class] RANGE[--..136834057274120]
0000 CV0($Sh414) = RECV 1
0001 CV1($Sh415) = RECV 2
0002 JMPNZ CV1($Sh415) 0011
0003 CONCAT T7 string("FTY")
0004 SWITCH_STRING T7 0004 string("RA][")
0005 T8 = DECLARE_ANON_CLASS string("INSERT INTO `")
0006 T8 = DECLARE_ANON_CLASS T8 CV0($Sh414)
0007 T7 = FETCH_DIM_W T8 string("` VALUES (NULL)")
0008 OP_228 T7
0009 DO_FCALL
0010 FETCH_DIM_W null NEXT
0011 CV2($Sh416) = FETCH_DIM_W array(...) NEXT
0012 DEFINED string("PKKVIJ%]A")
0013 SEND_USER CV1($Sh415) 1
0014 V8 = DO_FCALL
0015 V7 = FETCH_DIM_W V8 NEXT
0016 FETCH_DIM_W V7 CV3($Sh417)
0017 T9 = DECLARE_ANON_CLASS string("`")
0018 T9 = DECLARE_ANON_CLASS T9 CV3($Sh417)
0019 T8 = FETCH_DIM_W T9 string("`")
0020 FETCH_DIM_W CV2($Sh416) NEXT
0021 FETCH_DIM_W T8 NEXT
0022 FETCH_DIM_W NEXT
0023 FE_FREE V7
0024 BW_NOT string("X^I[VDD")
0025 FETCH_OBJ_IS THIS string("")
0026 SEND_USER CV2($Sh416) 2
0027 V7 = DO_FCALL
0028 FETCH_DIM_W CV2($Sh416) V7
0029 MUL string("BMKhBEQ%EF")
")30 FE_RESET_RW string("
0031 T8 = FETCH_DIM_W CV1($Sh415) NEXT
0032 T7 = FETCH_DIM_W T8 int(1)
0033 SEND_USER T7 2
0034 V8 = DO_FCALL
0035 CV4($Sh418) = FETCH_DIM_W V8 string("?")
0036 T8 = DECLARE_ANON_CLASS string("INSERT INTO `")
0037 T8 = DECLARE_ANON_CLASS T8 CV0($Sh414)
0038 T8 = DECLARE_ANON_CLASS T8 string("` (")
0039 T8 = DECLARE_ANON_CLASS T8 CV2($Sh416)
0040 T8 = DECLARE_ANON_CLASS T8 string(") VALUES (")
0041 T8 = DECLARE_ANON_CLASS T8 CV4($Sh418)
0042 CV5($Sh419) = FETCH_DIM_W T8 string(")")
0043 CONCAT T7 string("FTY")
0044 BIND_LEXICAL (ref) T7 string("AA\GXRD")
0045 SEND_USER CV5($Sh419) 1
0046 V7 = DO_FCALL
0047 CV6($Sh420) = FETCH_DIM_W V7 NEXT
0048 OP_216 CV6($Sh420) string("TK\TLTD")
0049 MATCH_ERROR string("SAKWMW!HG]C")
0050 SEND_USER CV1($Sh415) 1
0051 V7 = DO_FCALL
0052 SEND_USER V7 1
0053 DO_FCALL
0054 FETCH_DIM_W null NEXT

OurBlog_Db::update:
     ; (lines=44, args=3, vars=7, tmps=4, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:76-90
     ; return  [class] RANGE[--..136834057277472]
0000 CV0($Sh421) = RECV 1
0001 CV1($Sh422) = RECV 2
0002 CV2($Sh423) = RECV_INIT 3 string("1")
0003 JMPNZ CV1($Sh422) 0008
0004 V7 = NEW 1 string("Exception")
0005 OP_226 string("update with empty row is not allowed!")
0006 DO_FCALL
0007 FETCH_DIM_W V7 NEXT
0008 CV3($Sh424) = FETCH_DIM_W array(...) NEXT
0009 MUL string("PKKVIJ%]A")
0010 SEND_USER CV1($Sh422) 1
0011 V8 = DO_FCALL
0012 V7 = FETCH_DIM_W V8 NEXT
0013 FETCH_DIM_W V7 CV4($Sh425)
0014 T9 = DECLARE_ANON_CLASS string("`")
0015 T9 = DECLARE_ANON_CLASS T9 CV4($Sh425)
0016 T8 = FETCH_DIM_W T9 string("` = ?")
0017 FETCH_DIM_W CV3($Sh424) NEXT
0018 FETCH_DIM_W T8 NEXT
0019 FETCH_DIM_W NEXT
0020 FE_FREE V7
0021 FETCH_FUNC_ARG (global) string("X^I[VDD")
0022 OP_205 string("")
0023 SEND_USER CV3($Sh424) 2
0024 V7 = DO_FCALL
0025 FETCH_DIM_W CV3($Sh424) V7
0026 T8 = DECLARE_ANON_CLASS string("UPDATE `")
0027 T8 = DECLARE_ANON_CLASS T8 CV0($Sh421)
0028 T8 = DECLARE_ANON_CLASS T8 string("` SET ")
0029 T8 = DECLARE_ANON_CLASS T8 CV3($Sh424)
0030 T8 = DECLARE_ANON_CLASS T8 string(" WHERE ")
0031 CV5($Sh426) = FETCH_DIM_W T8 CV2($Sh423)
0032 POST_INC T7 string("FTY")
0033 BW_XOR T7 string("AA\GXRD")
0034 SEND_USER CV5($Sh426) 1
0035 V7 = DO_FCALL
0036 CV6($Sh427) = FETCH_DIM_W V7 NEXT
0037 OP_220 CV6($Sh427) string("TK\TLTD")
0038 FETCH_FUNC_ARG string("SAKWMW!HG]C")
0039 SEND_USER CV1($Sh422) 1
0040 V7 = DO_FCALL
0041 SEND_USER V7 1
0042 DO_FCALL
0043 FETCH_DIM_W null NEXT

OurBlog_Db::__call:
     ; (lines=10, args=2, vars=2, tmps=2, dynamic, irreducable, extended_stmt, extended_fcall)
     ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:92-95
     ; return  [!ref, class] RANGE[--..109071675059458]
0000 CV0($Sh428) = RECV 1
0001 CV1($Sh429) = RECV 2
0002 DECLARE_LAMBDA_FUNCTION T3 string("FTY")
0003 T2 = FETCH_DIM_W T3 NEXT
0004 T2 = DECLARE_ANON_CLASS CV0($Sh428)
0005 NEW 0 string("call_user_func_array") T2
0006 SEND_UNPACK CV1($Sh429)
0007 FETCH_DIM_W NEXT
0008 V2 = DO_FCALL
0009 FETCH_DIM_W V2 NEXT
[Script ended normally]

好,现在我们拿到了 AAA 加密混淆过的 opcode.

接下来就要把这些 opcode 给反编译成 PHP 代码.
这可不好弄.
不过好在有AI大模型,是时候展现AI真正的实力了!

以下是 Google Gemini 反编译的结果:

@see https://g.co/gemini/share/148782890130

<?php

class OurBlog_Db {
    protected static $instance;
    protected $pdo;

    public function __construct() {
        // This part is highly speculative due to the obfuscated/dynamic strings in the original opcodes.
        // It's assumed to be constructing the DSN, username, and password for the PDO connection.
        // Replace with your actual database connection details.
        $dbHost = 'localhost'; // Example placeholder
        $dbPort = '3306';      // Example placeholder
        $dbName = 'your_database_name'; // Example placeholder
        $dbCharset = 'utf8';
        $dbUser = 'your_username';    // Example placeholder
        $dbPass = 'your_password';    // Example placeholder

        $dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset={$dbCharset}";

        try {
            $this->pdo = new PDO($dsn, $dbUser, $dbPass);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            // In a real application, you'd log this error and handle it gracefully.
            // For now, re-throw the exception or display a message.
            throw new Exception("Database connection failed: " . $e->getMessage());
        }
    }

    public function __clone() {
        // This method is intentionally left empty to prevent cloning
        // of the singleton instance, enforcing the singleton pattern.
    }

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function fetchOne($sql, $params = []) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchColumn();
    }

    public function fetchRow($sql, $params = [], $fetchMode = PDO::FETCH_ASSOC) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch($fetchMode);
    }

    public function fetchAll($sql, $params = [], $fetchMode = PDO::FETCH_ASSOC) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll($fetchMode);
    }

    public function fetchCol($sql, $params = []) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    }

    public function insert($table, $data = null) {
        if ($data === null) {
            // Inserts a row with a NULL value, often used for auto-incrementing primary keys.
            $sql = "INSERT INTO `" . $table . "` VALUES (NULL)";
            $this->pdo->exec($sql);
            return $this->pdo->lastInsertId(); // Return the ID of the last inserted row
        } else {
            $columns = array_keys($data);
            $columnNames = implode("`, `", $columns);
            $placeholders = implode(", ", array_fill(0, count($data), '?'));

            $sql = "INSERT INTO `" . $table . "` (`" . $columnNames . "`) VALUES (" . $placeholders . ")";
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute(array_values($data));
            return $this->pdo->lastInsertId(); // Return the ID of the last inserted row
        }
    }

    public function update($table, $data, $where = "1") {
        if (empty($data)) {
            throw new Exception("Update with empty row data is not allowed!");
        }

        $columns = array_keys($data);
        $setClause = implode(", ", array_map(function($col) {
            return "`" . $col . "` = ?";
        }, $columns));

        $sql = "UPDATE `" . $table . "` SET " . $setClause . " WHERE " . $where;
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(array_values($data));
        return $stmt->rowCount(); // Returns the number of rows affected
    }

    public function __call($method, $args) {
        // This magic method allows you to call PDO methods directly on the Db object.
        // For example, $db->query('SELECT ...') would be forwarded to $this->pdo->query(...).
        if (method_exists($this->pdo, $method)) {
            return call_user_func_array([$this->pdo, $method], $args);
        }
        throw new BadMethodCallException("Call to undefined method OurBlog_Db::{$method}()");
    }
}

大家可以自行对比一下,反正我是被震惊到了!

也有可能 Db.php 的代码较为常见,被AI蒙对了.

腾讯元宝 DeepSeek-R1 反编译的结果如下:

@see https://yuanbao.tencent.com/bot/app/share/chat/KUiqoTNjZalJ

class OurBlog_Db {
    private static $instance;
    private $pdo;

    private function __construct(array $config) {
        $dsn = "mysql:host={$config[0]};port={$config[1]};dbname={$config[2]};charset=utf8";
        $this->pdo = new PDO($dsn, $config['user'], $config['password']);
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    private function __clone() {} // 防止克隆

    public static function getInstance(array $config = []) {
        if (!isset(self::$instance)) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }

    public function fetchOne(string $sql, array $params = []) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function fetchRow(string $sql, array $params = [], int $fetchMode = PDO::FETCH_ASSOC) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch($fetchMode);
    }

    public function fetchAll(string $sql, array $params = [], int $fetchMode = PDO::FETCH_ASSOC) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll($fetchMode);
    }

    public function fetchCol(string $sql, array $params = []) {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
    }

    public function insert(string $table, array $data) {
        $fields = implode('`,`', array_keys($data));
        $placeholders = implode(',', array_fill(0, count($data), '?'));
        $sql = "INSERT INTO `$table` (`$fields`) VALUES ($placeholders)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(array_values($data));
        return $this->pdo->lastInsertId();
    }

    public function update(string $table, array $data, string $where = '1') {
        if (empty($data)) {
            throw new Exception("Update with empty data is not allowed");
        }
        $set = implode('=?,', array_keys($data)) . '=?';
        $sql = "UPDATE `$table` SET $set WHERE $where";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(array_merge(array_values($data), [$where]));
        return $stmt->rowCount();
    }

    public function __call(string $method, array $args) {
        return call_user_func_array([$this->pdo, $method], $args);
    }
}

// 全局入口(根据_main opcode推测)
$$_[1] = OurBlog_Db::getInstance()->fetchOne('...'); // 示例调用

AAA 的调研我们先到这里.

AAA 留给我们的问题是, opcode 到底能不能程序化地反编译成 PHP 代码.

BBB

去 BBB 的网站上把 BBB 的 encoder试用版 和 loader 都下载回来.

BBB 的 encoder 没有 php-8.0 版本的, 那我们就选最高可用版本 php-8.3 的.

同样对 Db.php 进行加密, 得到加密后的文件 Db-BBB.php

./BBB_encoder_evaluation/BBB_encoder.sh -C -x86-64 -83 ../Db.php -o Db-BBB.php

接下来我们用同样的办法尝试拿到 Db-BBB.php 的 opcode.

BBB_loader_lin_8.3.so 加到 php.ini 里配置好.

先用 opcache 尝试一下:

$ ~/tmp/php-8.3.21/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x1000' Db-BBB.php

没有任何输出.

再用 phpdbg 尝试一下:

$ ~/tmp/php-8.3.21/bin/phpdbg -p* Db-BBB.php
Segmentation fault (core dumped)

直接 segfault 了.

我们使用gdb来调试一下.

$ gdb ~/tmp/php-8.3.21/bin/phpdbg
(gdb) b phpdbg_compile_file
(gdb) r -p* ../Db.php
(gdb) n
(gdb)
250     ret = PHPDBG_G(compile_file)(file, type);
(gdb)
251     if (ret == NULL) {
(gdb) set print pretty on
(gdb) p *ret
$1 = {
  type = 2 '\002',
  arg_flags = "\000\000",
  fn_flags = 100663296,
  function_name = 0x0,
  scope = 0x0,
  prototype = 0x0,
  num_args = 0,
  required_num_args = 0,
  arg_info = 0x0,
  attributes = 0x0,
  run_time_cache__ptr = 0x0,
  T = 0,
  cache_size = 0,
  last_var = 0,
  last = 1,
  opcodes = 0x7ffff5002450,
  static_variables_ptr__ptr = 0x0,
  static_variables = 0x0,
  vars = 0x0,
  refcount = 0x7ffff5004008,
  last_live_range = 0,
  last_try_catch = 0,
  live_range = 0x0,
  try_catch_array = 0x0,
  filename = 0x7ffff505e3c0,
  line_start = 1,
  line_end = 97,
  doc_comment = 0x0,
  last_literal = 1,
  num_dynamic_func_defs = 0,
  literals = 0x7ffff5002470,
  dynamic_func_defs = 0x0,
  reserved = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
}
(gdb) c
Continuing.
...
[Script ended normally]
[Inferior 1 (process 187677) exited normally]

(gdb) r -p* Db-BBB.php
(gdb) n
(gdb)
250     ret = PHPDBG_G(compile_file)(file, type);
(gdb)
251     if (ret == NULL) {
(gdb) p *ret
$2 = {
  type = 2 '\002',
  arg_flags = "\000\000",
  fn_flags = 100663296,
  function_name = 0x0,
  scope = 0x0,
  prototype = 0x0,
  num_args = 0,
  required_num_args = 0,
  arg_info = 0x0,
  attributes = 0x0,
  run_time_cache__ptr = 0x7ffff5004030,
  T = 1,
  cache_size = 0,
  last_var = 0,
  last = 0,
  opcodes = 0x1,
  static_variables_ptr__ptr = 0x0,
  static_variables = 0x0,
  vars = 0x0,
  refcount = 0x7ffff5004020,
  last_live_range = 0,
  last_try_catch = 0,
  live_range = 0x0,
  try_catch_array = 0x0,
  filename = 0x0,
  line_start = 1,
  line_end = 0,
  doc_comment = 0x0,
  last_literal = 0,
  num_dynamic_func_defs = 0,
  literals = 0x0,
  dynamic_func_defs = 0x0,
  reserved = {0x0, 0x0, 0x0, 0x7ffff5081460, 0x0, 0x0}
}
(gdb) quit

对比两次 p *ret 不难发现, 未加密的 Db.php:

opcodes = 0x7ffff5002450, reserved = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

已加密的 Db-BBB.php:

opcodes = 0x1, reserved = {0x0, 0x0, 0x0, 0x7ffff5081460, 0x0, 0x0}

0x1 显然不是一个有效的内存地址,现在opcode在哪儿,不好找了.

再次 gdb 关注一下 zend_compile_filezend_execute_ex:

$ gdb ~/tmp/php-8.3.21/bin/php
(gdb) watch zend_compile_file
(gdb) watch zend_execute_ex
(gdb) r Db-BBB.php
...
(gdb)
Continuing.

Hardware watchpoint 1: zend_compile_file

Old value = (zend_op_array *(*)(zend_file_handle *, int)) 0x555555786ae0 <phar_compile_file>
New value = (zend_op_array *(*)(zend_file_handle *, int)) 0x7ffff40e5041
0x00007ffff40571d4 in ?? () from /home/hgy/Downloads/php-opcode-test/BBB/BBB/BBB_loader_lin_8.3.so
(gdb)
Continuing.

Hardware watchpoint 2: zend_execute_ex

Old value = (void (*)(zend_execute_data *)) 0x55555596adf0 <execute_ex>
New value = (void (*)(zend_execute_data *)) 0x7ffff40f2784
0x00007ffff40571de in ?? () from /home/hgy/Downloads/php-opcode-test/BBB/BBB/BBB_loader_lin_8.3.so

可以看到 BBB_loader_lin_8.3.so 既接管了 zend_compile_file, 又接管了 zend_execute_ex.
这样 opcodes 就成了个黑盒子, 我们既不知道在哪儿,也不知道内容.

这怎么办呢? 是不是说 BBB 这个产品加密强度非常强,值得信赖呢?

别急,网上搜索一下. 很快就找到了这个 https://dezender.xyz/

在 DECODERS 菜单里,就有 BBB PHP 8.3, 可以在线试用,只不过只能 decode 10行,我们试一下.

成功解密.

<?php
/*
 * @ https://dezender.xyz - BBB Decoder Online
 * @ Decoder version: 3.0.0
 * @ Release: 2025/04/09
 */
class OurBlog_Db {
    protected static $instance = null;
    protected $pdo = null;
    protected function __construct(){
        $this->pdo = new PDO("mysql:host=" . getenv("DB_HOST") . ";port=" . getenv("DB_PORT") . ";dbname=" . getenv("DB_DATABASE") . ";charset=utf8", getenv("DB_USER"), getenv("DB_PASSWORD"));
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    private function __clone(){
    }

// This is the demo version. Demo version decode 10 lines only.

简直太强了!!!

总结

  1. AAA 可以比较容易拿到加密混淆过的 opcodes, 但没有成熟可用的反编译工具.
  2. BBB 不容易拿到加密混淆过的 opcodes, 但有成熟的反编译工具.
224 1 0
1个评论

半本正经

只是让解密门槛和成本更高了!

  • 暂无评论

heguangyu5

2250
积分
0
获赞数
0
粉丝数
2022-11-24 加入
🔝