几天前,为了备注,2019年的6月17号吧,一个Linux/FreeBSD系统的漏洞爆出,就是CVE-2019-11477,Netflix的公告为:
https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001.md
Redhat的链接为:
https://access.redhat.com/security/vulnerabilities/tcpsack

在当日得到了消息,然后呼哈大笑,忽然想起奇书名著《黑客大曝光》的调调,知道这这么回事,简单的一个POC说明,却没有EXP,呵呵了。

没意思。

其实这个公告只是一个POC说明,证明了 代码那么写确实是有问题的! 具体如何来触发问题,却至今还未知。(文末有好玩的packetdrill脚本哦…)

POC的证明如下:

  1. gso_segs是一个u16类型的数字,其最大值为65535;
  2. gso_segs的计算方法为lengthmss\dfrac{length}{mss}msslength​;
  3. lengthlengthlength由所有skb的片段总长构成,总片段最大17个,每一个片段最长32KB;
  4. mss最小值为48,减去TCP选项头40字节,raw data的最小值为8;
  5. 根据gso_segs的计算方法,17×32×10248\dfrac{17\times32\times1024}{8}817×32×1024​会溢出。

确实是会溢出,然而想要制造这么个溢出,却是另一回事。POC和EXP是两回事,很多人往往混淆。

先说结论, CVE-2019-11477漏洞的危害并没有那么严重,不必惊慌。


在阅读下面的内容之前,还是希望把上面我发的链接仔细梳理梳理,此外还需要把Linux系统协议栈TCP关于SACK的处理流程仔细梳理一番,不然下面的内容可能会不知所云,就更别说有多么好玩了。


看了几个关于CVE-2019-11477的POC/EXP报文流程的尝试,几乎总是纠结于 一次性send出32k非线性scatter/gather IO(分散聚集IO)的数据,重复17次或者更多,然后就被堵在 如何一次性发出32k这么大的数据 这个难题上了。 诚然,这个问题是必须解决的,即每次必须发送什么样的数据,但是这不是触发漏洞的关键因素,这只是一个要求。

实际上,触发SACK Panic漏洞的关键根本就不在 “一次性发多少数据” 这里,而是在 “发送队列中有多少数据可供合并!” 这里的数据是以字节为单位的,而不是包。

所以,我把 一次性发送32Kb的scatter/gather IO(分散聚集IO)数据,重复至少17次 这个要求放在本文的最后去满足,仅仅提示一点,这个漏洞的触发确实和scatter/gather IO(分散聚集IO)有关,不过这里,暂且绕过它,先说发送队列层面的原理。即假设没有scatter/gather IO这回事。

这个CVE-2019-11477漏洞的关键是 SACK段的合并操作 ,最终使得被合并SACK段的总大小超过了阈值:

Multiple such SKB in the list are merged together into one to efficiently process different SACK blocks. It involves moving data from one SKB to another in the list. During this movement of data, the SKB structure can reach its maximum limit of 17 fragments and ‘tcp_gso_segs’ parameter can overflow and hit the BUG_ON() call below resulting in the said kernel panic issue.

注意,sack段被合并(merge)的目标是 “to efficiently process different SACK blocks” 这是为了抑制另外一种DDoS,即伪造畸变的SACK序列,让发送端的CPU消耗于发送队列的链表操作,排序,遍历等。在4.15内核,TCP的发送队列被重构成了红黑树,进一步抑制了这种DDoS攻击。

然而,这种善良的本意却无心插柳了一个漏洞,即CVE-2019-11477。


CVE-2019-11477的攻击EXP要点主要有以下三点:

  1. 发送队列中保持17×32×102417\times32\times102417×32×1024字节的scatter/gather(即分散/聚集非线性IO)数据,这是合并溢出的前提。注意,切不可指望服务器使用常规的线性IO数据情况下完成攻击。
  2. 被攻击侧发送TCP报文中要携带40字节的option,这才能使得raw data的mss成为8,这是实施除法的前提。
  3. 需要超时重传,从而完成两个目标,即 “给ICMP Need Frag以间隙” 以及 “tcp_fragment实施除法” 导致溢出。

其中,第一个要点已经有办法解决,难的是第二个。如何让被攻击侧携带40个字节的选项呢?

我们能列举出的选项,时间戳,SACK…如何才能 驱使 被攻击的发送端携带40字节之多的选项呢?用 诱导 这个词比较合适。

我们知道,TCP选项最大40字节,SACK选项最多容纳4个段,满4个段的SACK就能占掉(4×2)×4+2=34(4\times2)\times4+2=34(4×2)×4+2=34字节的选项空间,我们还知道,TCP时间戳选项一共10个字节,那么除却时间戳,还需要30字节就够了,我建议以下的TCP选项组合:
时间戳+1个SACK段+1个MD5选项=10字节+20字节+10字节=40字节

然而这需要被攻击者内核支持TCP MD5选项。我的手改版是现编译的,特意支持的,但是互联网线上的内核是否大规模支持MD5选项,不得而知。

此外,还可以诱导被攻击侧组合下面的TCP选项:
时间戳+3个SACK段+4个padding NOP=10字节+26字节+4字节=40字节
不过我没有试这个,反而舍近求远用了MD5…并且为了触发漏洞,免除了校验。(不过,事后我进行了重测,我用packetdrill模拟出一个8字节raw data的例子,可以在本文的最后例行皮鞋章节前面找到。)

其实使用 时间戳+3个SACK+4个Padding NOP 更加容易些。

内核在拼TCP Option时,按照其格式每一类Option都是 TLV 的结构,其中 Type 有1个字节, L 有1个字节,为了对齐,内核会把头2个字节的 TL 给padding上2个NOP,也就是Padding上2个字节,正好凑成4字节对齐,以时间戳为例:

*ptr++ = htonl((TCPOPT_NOP << 24) |(TCPOPT_NOP << 16) |(TCPOPT_TIMESTAMP << 8) |  // TypeTCPOLEN_TIMESTAMP);        // Length
...
*ptr++ = htonl((TCPOPT_NOP  << 24) |(TCPOPT_NOP  << 16) |(TCPOPT_SACK <<  8) |      // Type(TCPOLEN_SACK_BASE + (opts->num_sack_blocks *TCPOLEN_SACK_PERBLOCK))); // Lengthfor (this_sack = 0; this_sack < opts->num_sack_blocks;++this_sack) {*ptr++ = htonl(sp[this_sack].start_seq);*ptr++ = htonl(sp[this_sack].end_seq);
}

接下来,诱导被攻击侧发送1个SACK选项也有办法,比如攻击者可以主动给被攻击侧发送带有一个或者多个空洞的数据序列,诱使被攻击侧SACK空洞之后的数据。

但其实,非常难。

本文将给出一例EXP报文的触发逻辑。但我是改了内核的,手改了内核的初始拥塞窗口,加入了TCP MD5选项的支持,且增加了超时容忍时间。

如果使用原生的内核,想要实施攻击就更加不易了。

在本例中,若想实施攻击,首先有个前置条件,假设当前的mss为1400,则必须让被攻击的发送端的发送队列里必须至少有(17+1)×32×10241400)(\dfrac{17+1)\times32\times1024}{1400})(140017+1)×32×1024​) 个数据包,这里的(17+1)(17+1)(17+1)指的是 发送队列里面已经发送尚未确认的数据包数量必须至少18个,17个用来合并,1个为UNA空洞。

我们算一下(17+1)×32×10241400\dfrac{(17+1)\times32\times1024}{1400}1400(17+1)×32×1024​大概需要多大的拥塞窗口,以包计数,也就是400~500400~500400~500左右的拥塞窗口。

这并不难,攻击者可以拉取被攻击方的一个大文件, 不断伪造正常的ACK确认报文,诱导被攻击的发送端拥塞窗口不断张开,如果怕ACK丢失,可以每个ACK发两遍 ,只有发送端的拥塞窗口涨得足够大,其发送队列中才能容纳至少(17+1)×32×1024(17+1)\times32\times1024(17+1)×32×1024这么多字节的数据包。

同时攻击者伪造通告非常的接收窗口使的被攻击的发送端的发送窗口不断增加:

snd_wnd=min(songestion_wnd, receive_wnd);

两个措施,便可诱导被攻击侧发送更多的 尚未确认的包 ,以被攻击者伪造包序列以利用漏洞。

如果我们一开始就构建mss为48而不是1400的握手包去连接被攻击者的话,我们就需要诱导它的拥塞窗口达到(17+1)×32×10248\dfrac{(17+1)\times32\times1024}{8}8(17+1)×32×1024​这么大,这就困难了。

那么既然漏洞在mss为48时才会触发,现在为了快速增长拥塞窗口,攻击者的mss协商成了1400,那么如何让其在攻击前置条件(即发送队列里已经堆积了总大小为17×32×102417\times32\times102417×32×1024字节的包)满足后,如果让mss变成48而实施攻击呢?

答案是伪造ICMP Need Frag序列,迫使被攻击者自己重置mss。

好了,在满足了前置条件后,此时被攻击的发送端的发送队列情况是:

接下来,一个合理的触发操作序列为:

  1. 攻击者伪造sack序列, <sack una+mss~end, rwnd 0>
    被攻击的发送端收到该sack序列后,会将这些 连续的被sack的段 合并成为一个大的GSO段,一共17×32×102417\times32\times102417×32×1024字节,且阻止被攻击的发送端发送任何数据。
    rwnd=0 这个很重要,为了让序列起作用,则必须阻止被攻击侧的任何新包被发送,即憋住它。要注意的是,rwnd=0是憋不住重传包的,因为rwnd基于的是滑动窗口矢量,而不是类似cwnd的可用计数标量。憋住新包是为了尽量减少发包动作,以更快超时。只有超时后的指数退避间隙,下面第2步的ICMP Need Frag报文才能趁隙插入,用以改变连接的mss。
    插一句。为什么会合并?很简单,为了处理发送队列更高效。不管怎么说,发送队列要么为链表(O(n)O(n)O(n)操作),要么为红黑树(4.15以后,O(ln⁡n)O(\ln n)O(lnn)操作),元素合并会减少数量,提高效率。
    此时,被攻击的发送端的发送队列情况是:
  2. 伪造 ICMP Need Fragment [mtu=48+协议头长]
    迫使TCP连接的mss减少到48,留下8字节给raw data。进而raw data的mss成为8。
    这个包若想插入,必须在等到超时的间隙。
  3. 继续等待超时RTO,攻击者只需等待。
    被攻击的发送端超时后会再次重置所有的发送队列skb为LOST,包括那个已经被合并的大小为17×32×102417\times32\times102417×32×1024的包。( 真的会吗?你可能发现并不会…所以呢,这里还藏着一个细节trick,即你还要伪造一个SACK renege报文 ,细节参考tcp_clean_rtx_queue函数以及tcp_check_sack_reneging函数)
    此时,被攻击的发送端的发送队列情况是:

    sack reneging报文的细节如下:
    1. 以包为单位,伪造憋住una不动,但却sack后面一个空洞之后的1个包,即sack una+2;
    2. 伪造促使17个满frag片段合并的sack段,参见上文,不再赘述。
    3. 以包为单位,伪造将una推进到una+1的报文,此举将促使被攻击侧在超时之后认定接收端sack reneging ,从而将SACKed段也标记为LOST,从而为下一次合并触发漏洞制造条件。参见tcp_enter_loss:

        void tcp_enter_loss(struct sock *sk){..skb = tcp_write_queue_head(sk);is_reneg = skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED);...mark_lost = (!(TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED) ||is_reneg);if (mark_lost)tcp_sum_lost(tp, skb);if (mark_lost) {TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_ACKED;TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;tp->lost_out += tcp_skb_pcount(skb);tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;}...
    }```
    
  4. 被攻击端超时重传
    由于sack reneging已经被认定,超时后合并的SACKed段被认定为LOST,会被重传。
    当重传到被合并的超大skb时,由于mss已经变小,且拥塞窗口此时还比较小,会调用tcp_fragment将大的skb进行分割,分为大小分别为8(即raw data为8的skb)以及(17×32×1024−8)(17\times32\times1024-8)(17×32×1024−8),首先重传第一个大小为8的包,大小为(17×32×1024−8)(17\times32\times1024-8)(17×32×1024−8)的包留在后面等待下次重传。
    tcp_fragment函数中,gso_segs字段就会溢出。由于mss已经变小,会重新计算gso_segs,算法为: gso_segs=lengthmssgso\_segs=\dfrac{length}{mss}gso_segs=msslength​ 其值等于 (17×32×1024−8)8=69631\dfrac{(17\times32\times1024-8)}{8}=696318(17×32×1024−8)​=69631,超过了u16的最大值65535,回绕成4095(记住这个值)。
    此时,被攻击的发送端的发送队列情况是:
  5. 攻击者收到重传包后,再次伪造sack序列 <sack una+1个包~end-8, rwnd 32768>
    被攻击的发送端收到该sack序列后会尝试将超时重传时分开的两个大小分别为8(即raw data为8的skb)以及(17×32×1024−8−8)(17\times32\times1024-8-8)(17×32×1024−8−8)的包重新合并为一个,因为此前这两个包均被标记为LOST了,然后收到这里伪造的sack序列后,两个包均被SACKed,且均是非线性分散/聚集IO数据,符合合并条件。此时打开了接收窗口。因为通告接收窗口憋住期间,攻击已经一气呵成。
    之所以要(17×32×1024−8−8)(17\times32\times1024-8-8)(17×32×1024−8−8)连续减2个8,是因为我们第二次伪造sack序列时,不能全部覆盖被合并的段,要留空,这样才能用int型去计算pcount=lenmsspcount=\dfrac{len}{mss}pcount=msslen​,参见tcp_shift_skb_data函数的in_sack判定,只有留8字节空,其值为假,才会计算pcount=lenmsspcount=\dfrac{len}{mss}pcount=msslen​,否则直接取gso_segs了。
    合并过程中,后面(17×32×1024−8−8)(17\times32\times1024-8-8)(17×32×1024−8−8)字节大小的包要和前面8字节大小的包合并,合并的函数如下:
static struct sk_buff *tcp_shift_skb_data(struct sock *sk, struct sk_buff *skb,struct tcp_sacktag_state *state,u32 start_seq, u32 end_seq,bool dup_sack)
{…len = end_seq - TCP_SKB_CB(skb)->seq; // len就是后面将要被合并到前面那个8字节小包的大包的长度,即int型值 (17*32*1024-8-8)=557040pcount = len / mss; // pcount就是即将被合并的大包的以mss为单位的段数,即int型的(17*32*1024-8-8)/8=69630if (!skb_shift(prev, skb, len)) // 大包顺利合入第一个小包goto fallback;// tcp_shifted_skb 中pconut为大包段数69630,skb的gso_segs为在超时重传中tcp_fragmeng中被重置的溢出值4095。// 4095<69631!命中BUG_ON,gg!if (!tcp_shifted_skb(sk, skb, state, pcount, len, mss, dup_sack))goto out;

插播一段tcp_fragment的逻辑。

在GSO启用的情况下,当重传一个长度len大于mss的包时,重传逻辑会将其分为两部分:

  1. 一个长度和mss大小相同的包;
  2. 一个长度为len-mss的包。

分割逻辑如下:

// __tcp_retransmit_skb 在发现当前mss已经小于skb的len时,将会调用tcp_fragment。
/* Initialize TSO segments for a packet. */
static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb,unsigned int mss_now)
{struct skb_shared_info *shinfo = skb_shinfo(skb);/* Make sure we own this skb before messing gso_size/gso_segs */WARN_ON_ONCE(skb_cloned(skb));if (skb->len <= mss_now || !sk_can_gso(sk) ||skb->ip_summed == CHECKSUM_NONE) {/* Avoid the costly divide in the normal* non-TSO case.*/shinfo->gso_segs = 1;shinfo->gso_size = 0;shinfo->gso_type = 0;} else {// 这里的除法将会是:(17*32*1024-8)/8,使得u16的gso_segs溢出!shinfo->gso_segs = DIV_ROUND_UP(skb->len, mss_now);shinfo->gso_size = mss_now;shinfo->gso_type = sk->sk_gso_type;}
}
int tcp_fragment(struct sock *sk, struct sk_buff *skb, u32 len,unsigned int mss_now)
{…/* Fix up tso_factor for both original and new SKB.  */// skb的raw data大小为raw data的mss,也就是8tcp_set_skb_tso_segs(sk, skb, mss_now);// buff的大小为合并后的原始skb的大小17*32768字节减去8.tcp_set_skb_tso_segs(sk, buff, mss_now);…
}

在描述完一个EXP触发逻辑后,这里再给出一个。

这个是我昨夜值班时想了的,并没有亲自试,所以也只是个想法。该EXP触发逻辑是概率性的大摆拳,没有上一个那么精心Trick。

如果不使用超时重传呢?

  1. 同前文的触发逻辑第1步。
  2. 伪造窗口通告 <rwnd 2000>
  3. 被攻击侧重传合并后的GSO大包。
    由于rwnd此时只有1000,故而只重传UNA,不涉及合并后的包。
  4. 恰好该GSO大包被塞入了SCH队列。
    这一步是概率性的,一旦GSO大包被塞入队列,tcp_retransmit_skb将会逐栈返回,TCP连接解锁。
  5. 解锁期间,伪造ICMP Need Frag报文,强制被攻击侧raw data的mss为8。
    注意,这一步设置mss为48,因此依然依赖凑40字节的TCP选项。
  6. 伪造rwnd打开,重传继续进行。
    此时会重传合并后的GSO大包,按照重传逻辑,由于合并后的GSO大包长度为17×32×102417\times32\times102417×32×1024字节,远大于mss,因此会执行tcp_fragment(请务必熟读该函数!),此时gso_segs字段会溢出。
  7. 继续按照前文的EXP步骤,伪造SACK段,促使一次新的合并,gg。

注意,这个步骤中没有采用超时重传的间隙,而是采用Linux发包流程中的队列隔断了同步流程,获取概率性的TCP锁空档,进而改变mss。

当然了,这个我并没有测试,根本没有时间。所以,这只是一个想法。


恕不能提供源码和fixed attacked kernel,因为很多人的目的不是学习,而是干坏事。


一天以后,我重新测试了 时间戳+3个SACK+4个Padding NOP 的情况,packetdrill脚本如下:

0   socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 48,sackOK, TS val 100 ecr 0,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>+0 < . 1:1(0) ack 1 win 257
+0  accept(3, ..., ...) = 4+0  write(4, ..., 200) = 200
// 按照mss=48,发送200字节。每个包的时间戳选项为10字节,T(ype)L(ength)被Padding 2个字节Nop,所以raw data为48-10-2=36字节,以初始cwnd按照下列序列发出:
// 1:37 37:73 73:109 109:145 145:181 181:201// 1. 攻击者sack除una包之外的一大片,促成merge动作,合并skb
// 2. 攻击者同时发送空洞数据,促使被攻击者填充3个sack段,凑成40字节的options
// 3. UNA后面留2个洞,一个为了sack,另一个为了在RTO后增加cwnd,分割重传后面的合并后的包,从而调用tcp_fragment
// 4. 哦,对了,这里没有sack reneging的伪造...
+0 < . 1:11(10) ack 1 win 0 <sack 73:201,nop,nop>
+0 < . 21:31(10) ack 1 win 0 <sack 73:201,nop,nop>
+0 < . 41:51(10) ack 1 win 0 <sack 73:201,nop,nop>
+0 < . 61:71(10) ack 1 win 0 <sack 73:201,nop,nop>// 超时期间本可以插入ICMP了…哦不,因为本例子一开始就协商了48字节的mss,所以不需要ICMP了// 超时后再次以sack触发合并
+0.1 < . 61:71(10) ack 1 win 257 <sack 73:201,nop,nop>
+0.1 < . 61:71(10) ack 1 win 257 <sack 73:201,nop,nop>
+0.1 < . 61:71(10) ack 1 win 257 <sack 73:201,nop,nop>// 以新的向后滑动的UNA触发cwnd增加,触发合并后的skb被分割重传,触发tcp_fragmeng中溢出
+0.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>
+0.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>
+0.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>// 超时期间,迫使所有的skb被重置为LOST// 重新用sack促使合并,触发BUG_ON,从而gg
+1.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>
+1.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>
+1.1 < . 61:71(10) ack 37 win 257 <sack 73:201,nop,nop>// Receiver ACKs all data.
+5 < . 1:1(0) ack 201 win 257

抓包如下:

可见,万事俱备,只欠cwnd了。这意味着,这个pdrill脚本前面长一点,多写几个write:

…
write(4, ..., 200) = 200
+0 < . 1:1(0) ack 201 win 257
write(4, ..., 200) = 200
+0 < . 1:1(0) ack 401 win 257
write(4, ..., 200) = 200
+0 < . 1:1(0) ack 601 win 257
…

cwnd就会张开到需要的值了,这样我们根本就不需要伪造ICMP Need Frag报文了,说白了,伪造ICMP就是为了让cwnd内的字节容量涨得快一些,一开始我们设置了比较大的mss,比如1400,然后为了触发除法的u16溢出,才伪造ICMP更新mss的,既然我们能忍耐一开始mss就很小,自然就不必伪造ICMP咯。

已经提示到这里了,如果你真的去尝试,会发现仍然不成功!

唉,怎么会这样呢?如果你systemtap跟代码,会发现SACKed段大概率根本就没有被合并,Why?

scatter/gather IO的要求来了,最后一点,临门一脚,就是它了。

因为你没用scatter/gather IO啊,因此skb_can_shift就会返回false,何谈合并?!只有使用了scatter/gather IO的数据发送,即paged non-linear style send,才会促使skb数据的合并。

即scatter/gather IO每次发送PAGE_SIZESKB_FRAG_PAGE_ORDERKBPAGE\_SIZE^{SKB\_FRAG\_PAGE\_ORDER}KBPAGE_SIZESKB_FRAG_PAGE_ORDERKB这么多的数据,保持在send queue中至少18个,才能满足被攻击的要求!

这个scatter/gather IO在高性能下载服务器上非常普遍,这也是ZeroCopy的基础。

因此,你说这个漏洞严重不严重呢?

不多说了。


皮鞋?湿了,不会胖。


经理可以扣篮,但不经常,也不绝对

经理穿着皮鞋能扣篮,但不经常,也不绝对。
正如时间无限的前提下,总会有一立方的空气分子朝着一个方向运动的概率,虽然概率很低。

所以,经理一定有概率可以穿着皮鞋扣篮。虽然这并不意味着经理扣篮就一定能成功。

经理穿皮鞋扣篮可以用泊松分布来建模(无论跳多少次,高度几乎总是一个固定值),把经理的弹跳高度15公分填入兰姆达,然后让k等于扣篮所需的弹跳高度比如100公分,只要概率不等于0,就是有可能。越大越经常,越绝对。如果很小,就是但不经常,也不绝对。

CVE-2019-11477漏洞详解详玩相关推荐

  1. php 内存池,内存详解: 详解PHP内存池中的存储层_php

    php的内存管理器是分层(hierarchical)的.这个管理器共有三层:存储层(storage).堆(heap)层和 emalloc/efree 层.存储层通过 malloc().mmap() 等 ...

  2. linux中iptables配置文件及命令详解详解

    linux中iptables配置文件及命令详解详解 2018年05月04日 14:23:41 hxpjava1 阅读数 956 linux中iptables配置文件及命令详解详解 iptables配置 ...

  3. 如何判断立即数的合法性?(详解详析)

    如何判断立即数的合法性?(详解详析) <ARM体系结构与编程>一书中对立即数有这样的描述:每个立即数由一个8位的常数循环右移偶数位得到. 一个32位的常数,只有能够通过上面构造方法得到的才 ...

  4. 服务器的全部作用,王者荣耀有哪些服务器_王者荣耀全部服务器功能详解_玩游戏网...

    王者荣耀中有多种服务器,除了玩家们最常见的正式服和体验服,还有抢先服.中转服.比赛服.内测服,每个服务器的功能都是不一样的,我们平时常见的一些爆料内容其实都是这些不常见的服务器中流传出来的,小编这里带 ...

  5. 饥荒服务器不显示管理员,饥荒联机版管理员怎么添加_饥荒联机版管理员介绍与添加方法详解_玩游戏网...

    <饥荒>联机版里面的管理员这个概念大家了解吗?我之前也不清楚管理员相关内容,下面笔者就为大家带来了饥荒联机版管理员介绍与添加方法详解,小伙伴们还不了解联机版管理员的下面跟我一起来看看吧. ...

  6. java 修饰符 详解,详解Java修饰符

    Java语言提供了很多修饰符,主要分为以下两类: 访问修饰符 非访问修饰符 修饰符用来定义类.方法或者变量,通常放在语句的最前端.我们通过下面的例子来说明: 访问控制修饰符 Java中,可以使用访问控 ...

  7. php carbon详解,详解PHP使用日期时间处理器Carbon人性化显示时间

    详解PHP使用日期时间处理器Carbon人性化显示时间 本文介绍了PHP使用日期时间处理器Carbon人性化显示时间,分享给大家,具体如下: 可以通过 Composer 很方便的来安装 Carbon ...

  8. Maven 安装和使用详解详解(windows 环境)

    目录 1.maven 安装步骤 (1)从 Apache 官网下载 maven 压缩包 (2)解压 maven 压缩包,即可使用 (3)配置 maven 环境变量,方便全局使用 (3)检测是否安装配置成 ...

  9. android dialog 详解,详解android dialog自定义实例

    现在很多应用程序的提示对话框都很有个性的,如果你使用的对话框样子还是传统的对话框,是不是有点LOST了呢?今天爱站技术频道小编就给大家讲讲详解android dialog自定义实例. 第一个需求:简单 ...

  10. CVE-2019-11477漏洞详解详玩(删)

    几天前,为了备注,2019年的6月17号吧,一个Linux/FreeBSD系统的漏洞爆出,就是CVE-2019-11477,Netflix的公告为: https://github.com/Netfli ...

最新文章

  1. iOS之Storyboard导航大揭秘(1)
  2. Java 并发/多线程教程(四)-并发模型
  3. BZOJ 1208 宠物饲养所 Splay
  4. 并查集/poj1182 noi2001食物链eat
  5. git 常用操作命令(Common operation)
  6. Java集合类及常用数据结构
  7. 【JDK1.8】JDK1.8集合源码阅读——Set汇总
  8. c语言答案选择题,C语言选择题(附答案)
  9. oss读取txt文件
  10. C语言圆周率天书简化,c语言天书__圆周率的计算及分析
  11. 0001-【linux系统】-用于生物信息分析该如何安装ubuntu系统?
  12. ACDSee 15/ACDSee Pro 6简体中文版破解注册机
  13. 计算机桌面搜狗输入图标不见了怎么办,电脑上搜狗输入法状态栏突然不见了怎么办 如何找到电脑中所失去的输入法...
  14. 为什么计算机打不开优盘,电脑显示u盘但打不开 为什么u盘连电脑有显示但打不开...
  15. LOMO+XQDA(2015CVPR)
  16. linux服务运维管理,Linux服务器运维管理(第2版)
  17. 路径算法:遗传算法 (Genetic Algorithm, GA)
  18. 腾讯AI Lab姚建华博士入选2022 AIMBE会士
  19. WebGL简易教程——目录
  20. Java PDF数字签名(一) - 添加数字签名

热门文章

  1. 敏捷.高效通过ACP.随笔
  2. python操作autocad_利用python控制Autocad:pyautocad方式
  3. 爱奇艺落子IP源头 全产业链开放协同助攻原创内容
  4. OLAP场景之利器ClickHouse
  5. sketch如何做设计稿交互_sketch 交互原型制作方法
  6. 透彻解读mysql的可重复读、幻读及实现原理
  7. SAP Hana 数据库游标使用方法示例
  8. 企业高薪招人,近5成岗位月薪过万
  9. greenDAO简单使用经验
  10. oracle oem登录xdb,XDB sys_nc_oid$递归调用的案例一则