使用PHP opcache运行bin文件,利用vld扩展获取字节码,猜解程序逻辑,最后成功得到答案。
题目背景 题目链接为PHP逆向工程趣味迷题 。 具体要求为运行PHP opcache生成的bin文件,并解决题目给出的问题,具体为加密一个字符串。
环境准备 Linux 安装多版本PHP 由于我使用的操作系统为Kali Linux,系统默认的库只有部分PHP,老版本PHP无法安装。因此需要准备多版本PHP。 实现方法参考了这篇文章在Debian 10上安装和切换PHP 8、7.4和5.6版本 。
相关代码为:
sudo apt-get update sudo apt-get install lsb-release apt-transport-https ca-certificates sudo wget https://packages.sury.org/php/apt.gpg -O /etc/apt/trusted.gpg.d/php.gpg echo "deb https://packages.sury.org/php/ bullseye main" | sudo tee /etc/apt/sources.list.d/php.listsudo apt-get update
其他资料及相关文章学习 相关链接及文章里提到的链接都值得观看。
GoSecure Binary Webshell Through OPcache in PHP 7 - Ian Bouchard
GoSecure Detecting Hidden Backdoors in PHP OPcache - Ian Bouchard
GoSecure php7-opcache-override
scz 直接调用OPcache生成之some.php.bin中的函数
scz 《围观0CTF2018之ezDoor》
010 Editor
zsx 0CTF2018之ezDoor的全盘非预期解法
zsx 从PHP源码与扩展开发谈PHP任意代码执行与防御
PHP vld 扩展
看完上面这些文章,你应该知道:
PHP opcache的工作原理
PHP opcache 后门的原理
使用 vld 分析PHP opcache生成的字节码
使用 010 Editor 对二进制文件的简单修改
在没有源代码情况下调用PHP opcache生成bin文件的函数
题目分析 根据题目的bin文件,需要确定PHP版本。通过linux下的strings命令,查看源文件路径为/home/scz/src/php73/scz_puzzles.php
,可以判断版本为PHP 7.3,具体小版本不知道、也不影响后续解题。 同时命令输出结尾也会有一些奇怪的信息。
$ strings scz_puzzles.php.bin.bak OPCACHE a9ef565f37f8a07aab1bd494d026e597 scz_puzzles.php:2293176:2199960 /home/scz/src/php73/scz_puzzles.php oooo00o ... ooooooo Right! But you need to guess another puzzles ... What's this? fd81682965a6a8c1289ed6478ad2740647509a847d7483c3008b647cd7e1270e2a40d618719696983c6ad9550e2ea81e71d322a5cf51b16629551db8c3ef2f499262ec558bb7ca6d Wrong! ooo oooo00o 0000ooo ooo oooo00o 0000ooo ooo oooo00o 0000ooo ooo oooo00o 000 ooo oooo00o 000 ooo oooo00o 000 ooo oooo00o 000 ooo0
安装php7.3 : sudo apt-get install -y php7.3 php7.3-dev php7.3-opcache
运行程序 因为没有源文件,所以必须引导PHP访问opcache生成的二进制文件,根据zsx 和 scz的博客,我成功运行了文件。
错误的system id 理论上的步骤为首先php -S 运行一个phpinfo页面,使用 system_id_scraper.py
计算出system id,然后修改题目的二进制文件。 实际上system_id_scraper.py
算出来的system id 是错的。
正确的system id 为了得到正确的system id,只需要开启opchche功能即可,然后访问本地的某个页面,在opcache 文件缓存路径上查看即可。 (注:我Linux本地用户名也同样为3个字母,所以我修改了opcache文件里路径中的用户名,并与opcahce文件建立相同的路径,没有测试这里不一致的影响 。)
[username]@[hostame] ~/s/php73> pwd /home/[username]/src/php73 [username]@[hostame] ~/s/php73> ls /home/[username]/src/php73/ -alh total 28K drwxr-xr-x 3 [username] sudo 4.0K Oct 4 06:56 ./ drwxr-xr-x 4 [username] sudo 4.0K Oct 3 11:52 ../ -rw-r--r-- 1 [username] sudo 0 Oct 4 04:59 ezdoor_chall.php -rw-r--r-- 1 [username] sudo 1.5K Oct 4 05:00 patch_chall.php -rw-r--r-- 1 [username] sudo 2.1K Oct 4 06:48 patch_exit.php -rw-r--r-- 1 [username] sudo 21 Oct 3 22:10 phpinfo.php -rw-r--r-- 1 [username] sudo 0 Oct 3 11:46 scz_puzzles.php -rw-r--r-- 1 [username] sudo 72 Oct 4 07:11 test_vld.php drwxr-xr-x 11 [username] sudo 4.0K Oct 3 21:58 vld/ [username]@[hostame] ~/s/php73 [1]> php7.3 -S [ip_address]:9999 -d opcache.enable_cli=1 -d opcache.file_cache="/home/[username]/src/opcache" -d opcache.file_cache_only=1 -d opcache.validate_timestamps=0 PHP 7.3.31-1+0~20210923.88+debian11~1.gbpac4058 Development Server started at Mon Oct 4 20:37:38 2021 Listening on http://[ip_address]:9999 Document root is /home/[username]/src/php73 Press Ctrl-C to quit.
然后在另外终端窗口访问对应的phpinfo地址,curl http://[ip_address]:9999/phpinfo.php
,访问后就会生成对应的opcache bin文件。 查看opcache.file_cache配置路径的目录结构,发现正确的system id 为 ac616b4a451981be62b807ad0bcc0769
。 (注:即使在同一个php环境下,相同端口的php -S服务,每次不同启动时对应的system id 也不同,具体生成system id细节需要看源码才能弄清楚。)
[username]@[hostame] ~/s/php73> tree ~/src/opcache/ -D /home/[username]/src/opcache/ └── [Oct 3 22:05] ac616b4a451981be62b807ad0bcc0769 └── [Oct 3 22:05] home └── [Oct 3 22:05] [username] └── [Oct 3 22:05] src └── [Oct 4 07:11] php73 ├── [Oct 4 04:57] ezdoor_chall.php.bin ├── [Oct 4 05:00] patch_chall.php.bin ├── [Oct 4 06:48] patch_exit.php.bin ├── [Oct 3 22:10] phpinfo.php.bin <---- ├── [Oct 3 22:11] scz_puzzles.php.bin └── [Oct 4 07:11] test_vld.php.bin 5 directories, 6 files
使用 010 Editor 修改题目给出的二进制文件,将文件的system id 修改为本地的system id,然后将文件放入对应的路径中,即可通过在本地实现访问。
[username]@[hostame] ~/s/php73> curl http://[ip_address]:9999/scz_puzzles.php Right! But you need to guess another puzzles ... What's this? fd81682965a6a8c1289ed6478ad2740647509a847d7483c3008b647cd7e1270e2a40d618719696983c6ad9550e2ea81e71d322a5cf51b16629551db8c3ef2f499262ec558bb7ca6d
至此,我们已经成功运行了题目给出的opcache文件,输出结果与此前通过strings命令查看结果一致,但题目给出的hash含义需要进一步分析。
程序逻辑分析 运行vld 安装vld,进行配置重新运行php -S。这里system id会变更,需要重新更改opcache文件。再次访问,即可看到vld解析出来的字节码。
[username]@[hostame] ~/s/php73> php7.3 -S [ip_address]:9999 -d opcache.enable_cli=1 -d opcache.file_cache="/home/[username]/src/opcache" -d opcache.file_cache_only=1 -d opcache.validate_timestamps=0 -d"extension=/home/[username]/src/php73/vld/modules/vld.so" -d vld.active=1 -d opcache.file_cache_consistency_checks=0 PHP 7.3.31-1+0~20210923.88+debian11~1.gbpac4058 Development Server started at Sun Oct 3 22:32:32 2021 Listening on http://[ip_address]:9999 Document root is /home/[username]/src/php73 Press Ctrl-C to quit. Finding entry points Branch analysis from position: 0 1 jumps found. (Code = 62) Position 1 = -2 filename: /home/[username]/src/php73/scz_puzzles.php function name: (null)number of ops: 3 compiled vars: none line ------------------------------------------------------------------------------------- 261 0 E > INIT_FCALL 'ooooooo' 1 DO_UCALL 263 2 > RETURN 1 branch: path
程序逻辑分析 针对程序的分析,主要时集中在如下代码部分,参见scz的文章直接调用OPcache生成之some.php.bin中的函数 ,可以调用opcache文件中的函数、进行分析。
filename: /home/[username]/src/php73/scz_puzzles.php function name: ooooooonumber of ops: 20 compiled vars: !0 = $o0000 , !1 = $oooo0 , !2 = $ooo0 line ------------------------------------------------------------------------------------- 240 0 E > INIT_STATIC_METHOD_CALL 'oooo00o' , 'o00o00o' 1 SEND_VAL 'ooo+oooo00o+000+ooo+oooo00o+000+ooo+oooo00o+000+ooo+oooo00o+000' 2 DO_UCALL $3 3 QM_ASSIGN !0 $3 241 4 INIT_STATIC_METHOD_CALL 'oooo00o' , 'o0o0o0o' 5 SEND_VAR !0 6 SEND_VAL 'ooo+oooo00o+0000ooo+ooo+oooo00o+0000ooo+ooo+oooo00o+0000ooo' 7 DO_UCALL $3 8 QM_ASSIGN !1 $3 244 9 INIT_STATIC_METHOD_CALL 'oooo00o' , 'o00000o' 10 SEND_VAR !0 11 SEND_VAR !1 12 DO_UCALL $3 13 QM_ASSIGN !2 $3 247 14 IS_IDENTICAL ~3 !2, 'ooo+oooo00o+0000ooo+ooo+oooo00o+0000ooo+ooo+oooo00o+0000ooo' 15 > JMPZ ~3, ->18 251 16 > ECHO 'Right%21+But+you+need+to+guess+another+puzzles+...%0AWhat%27s+this%3F%0Afd81682965a6a8c1289ed6478ad2740647509a847d7483c3008b647cd7e1270e2a40d618719696983c6ad9550e2ea81e71d322a5cf51b16629551db8c3ef2f499262ec558bb7ca6d%0A' 258 17 > EXIT 255 18 > ECHO 'Wrong%21%0A' 258 19 > EXIT
分析解密 为了不影响解密体验,这里不给出分析解密过程。将上述字节码还原成源代码,并运行即可。 采用题目本身给出的密钥,将hash转换成字符串(pack),即可得到结果。
vld 的坑 此外又一个坑是vld在输出参数时会进行urlencode,所以需要进行解码。 比如下面代码:
<?php $a = "touch /tmp/111 && chmod +x /tmp/111" ; echo ($a ); ?>
在vld中输出结果为:
Finding entry points Branch analysis from position: 0 1 jumps found. (Code = 62 ) Position 1 = -2 filename: /home/[username]/src/php73 /test_vld.phpfunction name: (null ) number of ops: 3 compiled vars: !0 = $a line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0 , 'touch+%2 Ftmp%2 F111 +%26 %26 +chmod+%2 Bx+%2 Ftmp%2 F111 ' 3 1 ECHO !0 5 2 > RETURN 1 branch: # 0 path #1 : 0 , [Mon Oct 4 21 :47 :42 2021 ] [ip_address]:60908 [200 ]: /test_vld.php