围观0CTF2018之ezDoor

  • 围观0CTF2018之ezDoor已关闭评论
  • 10 views
  • A+
所属分类:CTF专场
创建: 2021-09-26 11:07
更新:
http://scz.617.cn:8/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]的最后一个字节删掉了,为了套用Ian Bouchard的.bt、.py对之解析,需用二进制编辑器补回这个。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( 0x00xff ) ); // [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()返回结果。

<?php

//
// 前半截取自OPcacheDecompile_x64_7.0.x.py的输出结果
//

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( 0x00xff ) ); // [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)
}

//
//////////////////////////////////////////////////////////////////////////
//

function decode ( $string )
{
    $ret = "";

    for ( $i = 0x0; $i < strlen( $string ); $i+=2 )
    {
        $ret .= chr( intval( $string[$i].$string[$i+1], 16 ) );
    }

    return $ret;
}

function decrypt ( $pwd, $data )
{
    mt_srand( 0x539 );
    $cipher = "";
    $pwd_length = strlen( $pwd );
    $data_length = strlen( $data );
    $i = 0x0;

    for ( ; $i < $data_length; ++$i )
    {
        $cipher .= chr( ord( $data[$i] ) ^ ord( $pwd[$i % $pwd_length] ) ^ mt_rand( 0x00xff ) );
    }

    return $cipher;
}

//
// php72 -f CTF_ezDoor_test.php
//
printf
(
    "%sn",
    decrypt
    (
        "this_is_a_very_secret_key",
        decode( "85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab" )
    )
);

//
// php70 -f CTF_ezDoor_test.php
//
// printf
// (
//     "%sn",
//     encrypt
//     (
//         "this_is_a_very_secret_key",
//         "flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}"
//     )
// );
//
// printf
// (
//     "%sn",
//     decrypt
//     (
//         "this_is_a_very_secret_key",
//         decode( "af8b20dc63d276caf90064976e4e6cabb5495f989ae6a24a0603cc2632ec95e603fa66348c" )
//     )
// );

?>

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安全,偶然路过,只是围观一下。

相关推荐: 红帽杯 - WriteUp

Webfind_it<?php $link = mysql_connect('localhost', 'root'); ?><html><head> <tit…