标题: 围观0CTF2018之ezDoor 创建: 2021-09-26 11:07 更新: 2021-09-27 13:08 链接: https://scz.617.cn/web/202109261107.txt 上半年看PHP 7的Opcode时放狗命中两个有趣的链接 -------------------------------------------------------------------------- My-CTF-Challenges - LyleMi [2018] https://github.com/LyleMi/My-CTF-Challenges/blob/master/ezDoor/README_ZH.md 0CTF2018之ezDoor的全盘非预期解法 - zsx [2018-04-02] https://blog.zsxsoft.com/post/36 -------------------------------------------------------------------------- zsx展示了一个技巧,让VLD可以作用于OPcache生成的some.php.bin。两位作者都提 到一个非凡的项目 -------------------------------------------------------------------------- Binary Webshell Through OPcache in PHP 7 - Ian Bouchard [2016-04-27] https://www.gosecure.net/blog/2016/04/27/binary-webshell-through-opcache-in-php-7/ Detecting Hidden Backdoors in PHP OPcache - Ian Bouchard [2016-05-26] https://www.gosecure.net/blog/2016/05/26/detecting-hidden-backdoors-in-php-opcache/ PHP OPcache Override https://github.com/GoSecure/php7-opcache-override https://github.com/GoSecure/php7-opcache-override/issues/6 -------------------------------------------------------------------------- 该项目可以解析OPcache生成的some.php.bin,有它打底,省了好多从头看源码、文 档的麻烦。当时要对付的是ionCube 7.x,计划从some.php.bin起步熟悉Opcode,事 实证明这样做相当靠谱。 some.php.bin是PHP版本强相关的,Ian Bouchard的原始实现适配不了当时我的目标 版本,我选择完全重写一版,这事就过去了。后来我适配过7.0.33、7.1.33、7.2.34 以及7.4.23。正事告一段落时,回头来测Ian Bouchard提供的test.php.bin,意外发 现前述4个版本无一适配。Ian Bouchard的.bt、.py可以解析他自己的test.php.bin, 同时,从LyleMi与zsx的文档看,也能解析"0CTF2018之ezDoor"提供的flag.php.bin, 实测确实如此。这就令人纳闷了。 https://www.php.net/distributions/php-7.0.4.tar.gz https://www.php.net/distributions/php-7.0.8.tar.gz https://www.php.net/distributions/php-7.0.33.tar.gz 我只有7.0.33的运行环境,一度怀疑7.0.4相关数据结构有变,看了几份源码,确认 相关数据结构没有变化,重点对比zend_op_array结构。 昨天确认Ian Bouchard的php7-opcache-override项目对应7.0.x。 之前因看到system_id_scraper.py中对PHP 7.4特别处理,误以为该项目升级适配了 7.4。后来发现该项目只适配7.0.x,而且这个x是个较低的版本,比如7.0.4、7.0.8 等等,该项目并不适配7.0.33。 OPcache生成的some.bin是版本强相关的,但我没想到7.0.4与7.0.33能有大差异。懒 得细究源码和文档,凭经验直接用作者提供的的test.php.bin愣找了一下差异所在。 数据结构相同,结构优化对齐带来的填充相同,但7.0.4、7.0.8所有涉及相对偏移运 算的地方,都只有 0x50 + some_off 0x50是zend_file_cache_metainfo的内存大小。 "0CTF2018之ezDoor"提供的flag.php.bin是下面第二个链接,第一个链接是其源码 https://github.com/LyleMi/My-CTF-Challenges/blob/master/ezDoor/source/flag.php https://github.com/LyleMi/My-CTF-Challenges/blob/master/ezDoor/source/src/flag/93f4c28c0cf0b07dfd7012dca2cb868cc0228cad LyleMi为增加CTF难度,将flag.php.bin中zend_file_cache_metainfo.magic[8]的最 后一个字节\0删掉了,为了套用Ian Bouchard的.bt、.py对之解析,需用二进制编辑 器补回这个\0。LyleMi和zsx写明了这点。 对flag.php.bin进行反汇编,一种可能的结果展示 -------------------------------------------------------------------------- main() [0] (27) = ASSIGN($flag,"input_your_flag_here") [1] (29) = INIT_FCALL(,"encrypt") [2] (29) = SEND_VAL("this_is_a_very_secret_key",) [3] (29) = SEND_VAR($flag,) [4] (29) var_2 = DO_UCALL(,) [5] (29) tmp_1 = IS_IDENTICAL(var_2,"85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab") [6] (29) = JMPZ(tmp_1,->9) [7] (30) = ECHO("Congratulation! You got it!",) [8] (35) = EXIT(,) [9] (32) = ECHO("Wrong Answer",) [10] (35) = EXIT(,) encrypt($pwd,$data) // function_table[0] key=encrypt [0] (16) $pwd = RECV(,) [1] (16) $data = RECV(,) [2] (17) = INIT_FCALL(,"mt_srand") [3] (17) = SEND_VAL(0x539,) [4] (17) = DO_ICALL(,) [5] (18) = ASSIGN($cipher,"") [6] (19) tmp_6 = STRLEN($pwd,) [7] (19) = ASSIGN($pwd_length,tmp_6) [8] (20) tmp_6 = STRLEN($data,) [9] (20) = ASSIGN($data_length,tmp_6) [10] (21) = ASSIGN($i,0x0) [11] (21) = JMP(->32,) [12] (22) = INIT_FCALL(,"chr") [13] (22) = INIT_FCALL(,"ord") [14] (22) var_6 = FETCH_DIM_R($data,$i) [15] (22) = SEND_VAR(var_6,) [16] (22) var_6 = DO_ICALL(,) [17] (22) = INIT_FCALL(,"ord") [18] (22) tmp_8 = MOD($i,$pwd_length) [19] (22) var_7 = FETCH_DIM_R($pwd,tmp_8) [20] (22) = SEND_VAR(var_7,) [21] (22) var_8 = DO_ICALL(,) [22] (22) tmp_7 = BW_XOR(var_6,var_8) [23] (22) = INIT_FCALL(,"mt_rand") [24] (22) = SEND_VAL(0x0,) [25] (22) = SEND_VAL(0xff,) [26] (22) var_8 = DO_ICALL(,) [27] (22) tmp_6 = BW_XOR(tmp_7,var_8) [28] (22) = SEND_VAL(tmp_6,) [29] (22) var_6 = DO_ICALL(,) [30] (22) = ASSIGN_CONCAT($cipher,var_6) [31] (21) = PRE_INC($i,) [32] (21) tmp_6 = IS_SMALLER($i,$data_length) [33] (21) = JMPNZ(tmp_6,->12) [34] (24) = INIT_FCALL(,"encode") [35] (24) = SEND_VAR($cipher,) [36] (24) var_6 = DO_UCALL(,) [37] (24) = RETURN(var_6,) encode($string) // function_table[1] key=encode [0] (3) $string = RECV(,) [1] (4) = ASSIGN($hex,"") [2] (5) = ASSIGN($i,0x0) [3] (5) = JMP(->20,) [4] (6) = INIT_FCALL(,"dechex") [5] (6) = INIT_FCALL(,"ord") [6] (6) var_4 = FETCH_DIM_R($string,$i) [7] (6) = SEND_VAR(var_4,) [8] (6) var_4 = DO_ICALL(,) [9] (6) = SEND_VAR(var_4,) [10] (6) var_4 = DO_ICALL(,) [11] (6) = ASSIGN($tmp,var_4) [12] (7) tmp_5 = STRLEN($tmp,) [13] (7) tmp_4 = IS_EQUAL(tmp_5,0x1) [14] (7) = JMPZ(tmp_4,->18) [15] (8) tmp_4 = CONCAT("0",$tmp) [16] (8) = ASSIGN_CONCAT($hex,tmp_4) [17] (8) = JMP(->19,) [18] (10) = ASSIGN_CONCAT($hex,$tmp) [19] (5) = PRE_INC($i,) [20] (5) tmp_5 = STRLEN($string,) [21] (5) tmp_4 = IS_SMALLER($i,tmp_5) [22] (5) = JMPNZ(tmp_4,->4) [23] (13) = RETURN($hex,) -------------------------------------------------------------------------- 对flag.php.bin进行反编译,一种可能的结果展示 -------------------------------------------------------------------------- function main () { $flag = "input_your_flag_here"; // [0] (27) if ( encrypt( "this_is_a_very_secret_key", $flag ) === "85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab" ) { echo "Congratulation! You got it!"; // [7] (30) die; // [8] (35) } echo "Wrong Answer"; // [9] (32) die; // [10] (35) } function encrypt ( $pwd, $data ) // function_table[0] key=encrypt { mt_srand( 0x539 ); // [4] (17) $cipher = ""; // [5] (18) $pwd_length = strlen( $pwd ); // [7] (19) $data_length = strlen( $data ); // [9] (20) $i = 0x0; // [10] (21) for ( ; $i < $data_length; ++$i ) { $cipher .= chr( ord( $data[$i] ) ^ ord( $pwd[$i % $pwd_length] ) ^ mt_rand( 0x0, 0xff ) ); // [30] (22) } return encode( $cipher ); // [37] (24) } function encode ( $string ) // function_table[1] key=encode { $hex = ""; // [1] (4) $i = 0x0; // [2] (5) for ( ; $i < strlen( $string ); ++$i ) { $tmp = dechex( ord( $string[$i] ) ); // [11] (6) if ( strlen( $tmp ) == 0x1 ) { $hex .= "0" . $tmp; // [16] (8) } else { $hex .= $tmp; // [18] (10) } } return $hex; // [23] (13) } -------------------------------------------------------------------------- 与flag.php对比了一下,反编译结果能用。encrypt()是个对称加密算法,异或,因 此decrypt()几乎一样,除了不要encode()返回结果。 -------------------------------------------------------------------------- -------------------------------------------------------------------------- zsx指出PHP 7.1改过mt_rand(),LyleMi提供的flag.php中的"85...ab"是用PHP 7.2 生成的,为了正确decrypt(),必须用php72跑。同样的明文,php70生成的密文是 "af...8c"。 本文没有任何技术价值,要点只有一个,如果对PHP Opcode感兴趣,Ian Bouchard的 php7-opcache-override是个非凡的起点,可以用它来实战一下"0CTF2018之ezDoor", 绝对值得。LyleMi出的这道CTF题真地很有趣,而zsx让VLD作用于OPcache生成的 some.php.bin,初见时,很是赞叹。 我不会PHP,更不会WEB安全,偶然路过,只是围观一下。 2021-09-27 13:08 scz 有个后续,参看 《直接调用OPcache生成之some.php.bin中的函数》 https://scz.617.cn/web/202109271017.txt