近来看到有网友咨询PHP源码保护、防破解的问题, 我也很久没有了解了. 借机更新一下自己的认识, 了解了解市场现状.
PHP源码保护方案有多种,本文说的是对opcode进行加密混淆的方案.一般认为,这种方案的加密强度较强,保护程度也较高.
本文调研了两款PHP源码加密产品.调研过程中关注两个重点:
为了不对产品本身造成不好的影响, 我们称这两款产品分别为 AAA 和 BBB.
AAA是国内产品,号称 "最佳PHP源代码加密编译器".
BBB是国外产品,号称 "the most widely trusted PHP protection tool".
先来看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 的 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_file
和 zend_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.
简直太强了!!!
只是让解密门槛和成本更高了!