创建: 2021-09-27 10:17
更新:
http://scz.617.cn:8/web/202109271017.txt
参看
《围观0CTF2018之ezDoor》
http://scz.617.cn:8/web/202109261107.txt
flag.php.bin中的encrypt()是个简单的异或算法,对称加密,decrypt()实际上完全同encrypt()。LyleMi、zsx手工分析encrypt()的Opcode,用PHP实现decrypt()。我是用反编译器得到encrypt()的PHP伪代码,删掉其结尾处对encode()的调用,以此实现decrypt()。
考虑一种更普遍的场景,encrypt()不是简单的异或算法,其内部实现很复杂,通过其他技术手段判断其可能是一种对称加密算法,加解密都可以用encrypt()完成。此时,手工分析encrypt()的Opcode异常艰辛,反编译器也不见得精准输出。怎么继续?
虽然不会PHP,也不搞WEB安全,但我干过二十多年的逆向工程啊,脑洞一直在线。前述场景在逆向工程领域不要太普遍,碰上时我会设法直接调用以二进制形式存在的encrypt(),并不逆向分析它,只关心它的in/out。既然encrypt()、decrypt()本质上一样,只要有办法调用encrypt(),就可以进行解密操作。当年hume和我就是这样调用Skype各种复杂算法函数的。
场景假设我们拿不到some.php,但能拿到OPcache生成之some.php.bin。问题暂时转换成,直接调用some.php.bin中的函数。
在7.0.33中用LyleMi提供的CTF_ezDoor.php生成CTF_ezDoor.php.bin,确保后者已在OPcache中就位。接下来为了逼真,做如下操作
rm CTF_ezDoor.php
touch CTF_ezDoor.php
ls -l CTF_ezDoor.php
确保CTF_ezDoor.php已经是个空文件,为空,但必须存在。检验CTF_ezDoor.php.bin可用
php70
-d opcache.enable_cli=1 -d opcache.file_cache="/home/scz/src/opcache"
-d opcache.validate_timestamps=0 -d opcache.file_cache_consistency_checks=0
-f CTF_ezDoor.php
应该输出"Wrong Answer",表示在没有CTF_ezDoor.php内容的前提下,仍然执行了CTF_ezDoor.php.bin。
OPcache从文件缓存加载some.php.bin时有一些检查,参看
/*
* php-7.0.33extopcachezend_file_cache.c
*/
zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle)
{
...
/* verify header */
if (memcmp(info.magic, "OPCACHE", 8) != 0) {
...
return NULL;
}
if (memcmp(info.system_id, ZCG(system_id), 32) != 0) {
...
return NULL;
}
/* verify timestamp */
if (ZCG(accel_directives).validate_timestamps &&
zend_get_file_handle_timestamp(file_handle, NULL) != info.timestamp) {
...
unlink(filename);
...
return NULL;
}
...
/* verify checksum */
if (ZCG(accel_directives).file_cache_consistency_checks &&
zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size) != info.checksum) {
...
unlink(filename);
...
return NULL;
}
...
return script;
}
执行CTF_ezDoor.php.bin时指定了两个OPcache参数
opcache.validate_timestamps=0
opcache.file_cache_consistency_checks=0
前者关闭时间戳检查,后者关闭校验和检查。关闭这两个检查后,可以Patch CTF_ezDoor.php.bin中的Opcode并使之生效,后面我会演示一下,暂且略过。
已经可以执行CTF_ezDoor.php.bin,当include_once("CTF_ezDoor.php")时实际生效的是CTF_ezDoor.php.bin,理论上就可以调用其中的encrypt()了。
但是,CTF_ezDoor.php有main(),main()结尾有exit(),只是include的话,没机会调其中的encrypt()就退出了。
LyleMi就exit()这事提到一个链接
How to override built-in PHP function(s)
https://stackoverflow.com/questions/15230883/how-to-override-built-in-php-functions
这我哪看得懂啊。rename_function/override_function好像要依赖别的啥,缺省没它们,namespace那招我也用不来。试过php.ini中"disable_functions =",对付不了exit()。试过uopz_allow_exit(false),也要依赖别的啥,缺省用不了。命苦,没心情为这事去装其他PHP组件,我就一过路的妖怪,犯得着费这劲嘛。最后用LyleMi提到的register_shutdown_function(),设法在exit()时执行指定代码。下列代码同时演示了利用析构函数在exit()时执行指定代码。
vi CTF_ezDoor_call_0.php
<?php
function decode ( $string )
{
$ret = "";
for ( $i = 0x0; $i < strlen( $string ); $i+=2 )
{
$ret .= chr( intval( $string[$i].$string[$i+1], 16 ) );
}
return $ret;
}
//
//////////////////////////////////////////////////////////////////////////
//
//
// https://www.php.net/manual/zh/function.exit.php
//
// 底下讨论了一些exit()时执行代码的技巧
//
function shutdown ()
{
echo 'Shutdown: ' . __FUNCTION__ . '()' . PHP_EOL;
printf
(
"[0] %sn",
decode
(
encrypt
(
"this_is_a_very_secret_key",
decode( "af8b20dc63d276caf90064976e4e6cabb5495f989ae6a24a0603cc2632ec95e603fa66348c" )
)
)
);
}
class Foo
{
public function __destruct ()
{
echo 'Destruct: ' . __METHOD__ . '()' . PHP_EOL;
printf
(
"[1] %sn",
decode
(
encrypt
(
"this_is_a_very_secret_key",
decode( "af8b20dc63d276caf90064976e4e6cabb5495f989ae6a24a0603cc2632ec95e603fa66348c" )
)
)
);
}
}
try
{
register_shutdown_function( "shutdown" );
$foo = new Foo();
include_once( "CTF_ezDoor.php" );
}
catch ( Exception $e )
{
}
?>
decode()这个没办法,必须自己实现,CTF_ezDoor.php.bin中只有encode()。解码只是16进制表示转字符串,编码则是反过来。最重要的encrypt()/decrypt()不需要自己实现。
为了减少并不真正熟悉OPcache机制的读者的潜在困惑,执行CTF_ezDoor_call_0.php之前最好删一下潜在存在的CTF_ezDoor_call_0.php.bin。
rm /home/scz/src/opcache/888b1b2b3719b54e59f563400d7ce5f2/home/scz/src/php70/CTF_ezDoor_call*
php70
-d opcache.enable_cli=1 -d opcache.file_cache="/home/scz/src/opcache"
-d opcache.validate_timestamps=0 -d opcache.file_cache_consistency_checks=0
-f CTF_ezDoor_call_0.php
应该看到输出
Wrong AnswerShutdown: shutdown()
[0] flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}
Destruct: Foo::__destruct()
[1] flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}
上例中,全局变量$foo的析构时间点比回调函数shutdown()要晚。
当encrypt()很复杂时,逆向分析有困难时,前面演示的技巧就会派上用场。演示环境是7.0.33,若是其他PHP版本,需将"af…8c"换成匹配值。
到这儿还没完。长期搞逆向工程的,对字节码有着挥之不去的迷恋。CTF_ezDoor.php有main(),能否Patch main(),让它直接return呢?这样include时干挠因素更少。答案是肯定的。
main()的第一条字节码是
[0] (27) = ASSIGN($flag,"input_your_flag_here")
ASSIGN的opcode是0x26(32),将之改成0x3e(62),这是RETURN的opcode
[0] (27) = RETURN($flag,"input_your_flag_here")
只改zend_op.opcode,无需同步修正op1、op2、result、handler等字段,PHP引擎有足够的容错能力。可以用010 Editor套着.bt模板改,找
struct zend_persistent_script persistent_script
struct zend_op_array main_op_array
struct zend_op opcodes[11]
struct zend_op opcodes[0]
uchar opcode
在我的7.0.33环境中
$ fc /b CTF_ezDoor.php.bin.orig CTF_ezDoor.php.bin
00000E74: 26 3E
改过后,相当于
main ()
{
return;
}
此时include("CTF_ezDoor.php")只相当于导入一些库函数,原来的main()清空了。
vi CTF_ezDoor_call_1.php
<?php
function decode ( $string )
{
$ret = "";
for ( $i = 0x0; $i < strlen( $string ); $i+=2 )
{
$ret .= chr( intval( $string[$i].$string[$i+1], 16 ) );
}
return $ret;
}
//
//////////////////////////////////////////////////////////////////////////
//
include_once( "CTF_ezDoor.php" );
printf
(
"[2] %sn",
decode
(
encrypt
(
"this_is_a_very_secret_key",
decode( "af8b20dc63d276caf90064976e4e6cabb5495f989ae6a24a0603cc2632ec95e603fa66348c" )
)
)
);
?>
cp CTF_ezDoor.php.bin /home/scz/src/opcache/888b1b2b3719b54e59f563400d7ce5f2/home/scz/src/php70/CTF_ezDoor.php.bin
rm /home/scz/src/opcache/888b1b2b3719b54e59f563400d7ce5f2/home/scz/src/php70/CTF_ezDoor_call*
php70
-d opcache.enable_cli=1 -d opcache.file_cache="/home/scz/src/opcache"
-d opcache.validate_timestamps=0 -d opcache.file_cache_consistency_checks=0
-f CTF_ezDoor_call_1.php
应该看到输出
PHP Notice: Undefined variable: flag in /home/scz/src/php70/CTF_ezDoor.php on line 27
[2] flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}
第一行提示是Patch main()带来的,不用理它,已成功调用CTF_ezDoor.php.bin中的encrypt()。
有人会说,上哪儿找这种理想环境去?这个不是说搞站,也不是说打CTF,而是告诉你,有这么个技术路线可用。至于能在何处用上?在沙坑边的单双杠上呗,这都不知道,傻缺!我给出那个Patch 7字节的Burp破解方案时,不也是类似脑洞的应用么。
对了,别跟我扯PHP,我是真不会,前面那些PHP写法大部分是临时放狗搜个片段抄一下。放狗,我在行。
相关推荐: Linux 黑话解释:什么是 sudo rm -rf?为什么如此危险? | Linux 中国
导读:当你刚接触 Linux 时,你会经常遇到这样的建议:永远不要运行 sudo rm -rf /。在 Linux 世界里,更是围绕着 sudo rm -rf 有很多梗。 本文字数:2724,阅读时长大约:3分钟 https://linux.cn…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论