创建: 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( 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()返回结果。
<?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( 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)
}
//
//////////////////////////////////////////////////////////////////////////
//
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( 0x0, 0xff ) );
}
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安全,偶然路过,只是围观一下。
Webfind_it<?php $link = mysql_connect('localhost', 'root'); ?><html><head> <tit…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论