Exploiting 「BadIRET」 vulnerability (CVE-2014-9322, Linux kernel privilege escalation)

insight-labs · 2015/02/06 14:24php

from:http://labs.bromium.com/2015/02/02/exploiting-badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/html

POC( 感謝Mickey提供的連接):node

rdot.org/forum/showt…linux

Shawn:對於這個漏洞,本文的結論是SMEP雖然被繞過了,但SMAP是依然奏效的,這裏只想提一下相似PaX/Grsecurity的UDEREF特性和SMAP相似,只是屬於純軟件 實現,大概2006年左右這個特性就已經有了並且被一些anarchy普遍使用。git

0x00 Intro


CVE-2014-9322的描述以下:github

linux內核代碼文件arch/x86/kernel/entry_64.S在3.17.5以前的版本都沒有正確的處理跟SS(堆棧區)段寄存器相關的錯誤,這可讓本地用戶經過觸發一個IRET指令從錯誤的地址空間去訪問GS基地址來提權。
複製代碼

這個漏洞於2014年11月23日被社區修復2,至今我並無見到公開的利用代碼和詳細的討論。這篇文章我會嘗試去解釋這個漏洞的本質以及利用的過程。不幸的 是,我沒法徹底引用Intel白皮書3的全部內容,若是有讀者不熟悉一些術語能夠直接查Intel白皮書。全部的實驗都是在Fedora 20 64-bit發行版上完成的,內核是3.11.10-301,全部的討論基於64位進行。shell

簡單結論概要:bash

1. 經過測試,這個漏洞能夠徹底穩定的被利用。
2. SMEP[4]不能阻止任意代碼執行;SMAP[5]能夠阻止任意代碼執行。
複製代碼

0x01 Digression: kernel, usermode, iret


enter image description here

0x02 漏洞


在一些狀況下,linux內核經過iret指令返回用戶空間時會產生一個異常。異常處理程序把執行路徑返回到了bad_iret函數,她作了:數據結構

#!bash
 /* So pretend we completed the iret and took the #GPF in user mode.*/
 pushq $0
 SWAPGS
 jmp general_protection
複製代碼

正如這行評論所解釋,接下來的代碼流應該和通常保護異常(General Protection)在用戶空間發生時(轉跳到#GP處理程序)徹底相同。這種異常處理狀況大可能是由iret指令引起的,e.g. #GP。less

問題在於#SS異常。若是有漏洞的內核(好比3.17.5)也有"espfix"功能(從3.16引入的特性),以後bad_iret函數會在只讀的棧上執行"push"指令,這會致使頁錯誤(page fault)而會直接引發兩個錯誤。我不考慮這種場景;從如今開始,咱們只關注在3.16之前的沒有"espfix"的內核。

這個漏洞根源於#SS的異常處理程序沒有符合「pretend-it-was-#GP-in-userspace」[6]的規劃,與#GP處理程序相比,#SS異常處理會多作一次swapgs指令。若是你對swapgs不瞭解,請不要跳過下面的章節。

0x03 偏題:swapgs指令


當內存經過gs段進行訪問時,像這樣:

#!bash
mov %gs:LOGICAL_ADDRESS, %eax
複製代碼

實際會發生如下幾步:

1. BASE_ADDRESS值從段寄存器的隱藏部分取出
2. 內存中的線性地址LOGICAL_ADDRESS+BASE_ADDRESS被dereferenced(Shawn:char *p; *p就是deref)。
複製代碼

基地址是從GDT(或者LDT)繼承過來的。不管如何,有一些狀況是GS段基地址被修改的動做不須要GDT的參與。

引用自Intel白皮書:

「SWAPGS把當前GS基寄存器值和在MSR地址C0000102H(IA32_KERNEL_GS_BASE)所包含的值進行交換。SWAPGS指令是一個爲系統軟件設計的特權指令。(....)內核可使用GS前綴在正常的內存引用去訪問[per-cpu]內核數據結構。」

Linux內核爲每一個CPU在啓動時分配一個固定大小的結構體來存放關鍵數據。以後爲每一個CPU加載IA32_KERNEL_GS_BASE到相應的結構地址上,所以,一般的狀況,好比系統調用的處理程序是:

1. swapgs(如今是GS指向內核空間)
2. 經過內存指令和gs前綴訪問per-cpu內核數據結構
3. swapgs(撤銷以前的swapgs,GS指向用戶空間)
4. 返回用戶空間
複製代碼

0x04 觸發漏洞


如今很明顯能夠看到這個漏洞簡直就是墳墓,由於多了一個swapgs指令在有漏洞代碼路徑裏,內核會嘗試從可能被用戶操控的錯誤GS基地址訪問重要的數據結構。

當iret指令產生了一個#SS異常?有趣的是,Intel白皮書在這方面介紹不徹底(Shawn:是陰謀論的話又會想到BIG BROTHER?);描述iret指令時,Intel白皮書這 麼講:

64位模式的異常:
#SS(0)
若是一個嘗試從棧上pop一個值違反了SS限制。
若是一個嘗試從棧上pop一個值引發了non-canonical地址(Shawn: 64-bit下只容許訪問canonical地址)的引用。
複製代碼

沒有一個條件能被強制在內核空間裏發生。不管如何,Intel白皮書裏的iret僞代碼展現了另一種狀況:when the segment defined by the return frame is not present:

IF stack segment is not present
THEN #SS(SS selector); FI;
複製代碼

因此在用戶空間,咱們須要設置ss寄存器爲某個值來表示不存在。這不是很直接:

咱們不能僅僅使用:

mov $nonpresent_segment_selector, %eax
mov %ax, %ss
複製代碼

第二條指令會引起#GP。經過調試器(任何ptrace)設置ss寄存器是不容許的;相似的,sys_sigreturn系統調用不會在64位系統上設置這個寄存器(可能32位能工做)。解決方案是:

1. 線程A:經過sys_modify_ldt系統調用在LDT裏建立一個定製段X
2. 線程B:s:=X_selector
3. 線程A:經過sys_modify_ldt使X無效
4. 線程B:等待硬件中斷
複製代碼

爲何須要在一個進程裏使用兩個線程的緣由是從系統調用(包括sys_modify_ldt)返回是經過硬編碼了#ss值的sysret指令。若是咱們使X在相同的線程中無效就等同於"ss:=X 指令「,ss寄存器會處於未完成設置的狀態。運行以上代碼會致使內核panic。按照更有意義的作法,咱們將須要控制用戶空間的gs基地址;她能夠經過系統調用arch_prctl(ARCH_SET_GS)被設置。

0x05 Achieving write primitive


若是運行以上代碼,#SS處理程序會正常的返回bad_iret(意思是沒有觸及到內存的GS基地址),以後轉跳到#GP異常處理程序,執行一段時間後就調用到了這個函數:

#!cpp
289 dotraplinkage void
290 do_general_protection(struct pt_regs *regs, long error_code)
291 {
292         struct task_struct *tsk;
...
306         tsk = current;
307         if (!user_mode(regs)) {
                ... it is not reached
317         }
318 
319         tsk->thread.error_code = error_code;
320         tsk->thread.trap_nr = X86_TRAP_GP;
321 
322         if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) &&
323                         printk_ratelimit()) {
324                 pr_info("%s[%d] general protection ip:%lx sp:%lx
error:%lx",
325                         tsk->comm, task_pid_nr(tsk),
326                         regs->ip, regs->sp, error_code);
327                 print_vma_addr(" in ", regs->ip);
328                 pr_cont("\n");
329         }
330 
331         force_sig_info(SIGSEGV, SEND_SIG_PRIV, tsk);
332 exit:
333         exception_exit(prev_state);
334 }
複製代碼

C代碼不太明顯,但從gs前綴讀取到現有宏的值賦給了tsk。第306行是:

#!bash
0xffffffff8164b79d :    mov    %gs:0xc780,%rbx
複製代碼

這很變得有意思起來了。咱們控制了current指針,她指向用於描述整個Linux進程的數據結構。

319         tsk->thread.error_code = error_code;
320         tsk->thread.trap_nr = X86_TRAP_GP;
複製代碼

寫入(從task_struct開始的固定偏移)咱們控制的地址。注意值自己不能被控制(分別是0和0xd常量),但這不該該成爲一個問題。遊戲結束?

不會,咱們想覆蓋一些在X上的重要數據結構。若是咱們按照如下的步驟:

1. 準備在FAKE_PERCPU的用戶空間內存,設置gs基地址給她
2. 讓地址FAKE_PERCPU+0xc780存着指針FAKE_CURRENT_WITH_OFFSET,以知足FAKE_CURRENT_WITH_OFFSET= X – offsetof(struct task_struct,thread.error_code)
3. 觸發漏洞
複製代碼

以後do_general_protection會寫入X。但很快就會嘗試再次訪問current task_current的其餘成員,e.g.unhandled_signal()函數從task_struct指針解引用。咱們沒有依賴X來控制,最終會在內核產生一個頁錯誤。咱們怎麼避免這個問題?選項有:

  1. 什麼都不作。Linux內核不像Windows,Linux內核是徹底容許當一個不是預期的頁錯誤在內核出現,若是可能的話,內核會殺死當前進程以後嘗試繼續運行(Windows會藍屏)。這種機制對於大量內核數據污染就無能爲力了。個人猜想是在當前進程被殺死後,swapgs不平衡的保持下來,這會致使其餘進程上下文的更多頁錯誤。

  2. 使用「tsk->thread.error_code = error_code」覆蓋爲頁錯誤處理程序的IDT入口。以後頁錯誤發生(被unhandled_signal()觸發)。這個技術曾經在一些偶然的環境中成功過。但在這裏不會成功,由於有2個緣由:

    • Linux讓IDT只讀
    • 就算IDT可寫,咱們也不能控制覆蓋的值 -- 0或者0xd。SMEP/SMAP也會是問題。
  3. 咱們能夠嘗試產生一個競爭。「tsk->thread.error_code = error_code」會促進代碼執行,好比容許經過系統調用控制的代碼指針P。以後咱們能夠在CPU 0上觸發漏洞,在同一時間段CPU 1能夠循環執行一些系統調用。這個思路能夠在CPU 0被破壞前讓經過CPU 1得到代碼執行,好比hook頁錯誤處理程序,這樣CPU 0不會影響更多的地方,我嘗試了這種方法屢次,但都失敗了。可能不一樣的漏洞在時間線上的不一樣所致。

  4. Throw a towel on 「tsk->thread.error_code = error_code」 write.

雖然有些噁心,咱們會嘗試最後一個選項。咱們會讓current指向用戶空間,設置這個指針能夠經過讀的deref到咱們能控制的內存。天然的,咱們觀察接下來的代碼,找找更多的寫deref。

0x06. Achieving write primitive continued, aka life after do_general_protection

下一個機會是do_general_protection()所調用的函數:

#!cpp
int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
        unsigned long int flags;
        int ret, blocked, ignored;
        struct k_sigaction *action;

        spin_lock_irqsave(&t->sighand->siglock, flags);
        action = &t->sighand->action[sig-1];
        ignored = action->sa.sa_handler == SIG_IGN;
        blocked = sigismember(&t->blocked, sig);   
        if (blocked || ignored) {
                action->sa.sa_handler = SIG_DFL;
                if (blocked) {
                        sigdelset(&t->blocked, sig);
                        recalc_sigpending_and_wake(t);
                }
        }
        if (action->sa.sa_handler == SIG_DFL)
                t->signal->flags &= ~SIGNAL_UNKILLABLE;
        ret = specific_send_sig_info(sig, info, t);
        spin_unlock_irqrestore(&t->sighand->siglock, flags);

        return ret;
}
複製代碼

task_struct的成員sighand是一個指針,咱們能夠設置任意值。

action = &t->sighand->action[sig-1];
action->sa.sa_handler = SIG_DFL;
複製代碼

咱們沒法控制寫的值,SIG_DFL是常量的0。這裏最終能工做了,雖然有些扭曲。假設咱們想覆蓋內核地址X。爲此咱們準備僞造的task_struct,因此X等於t->sighand->action[sig-1].sa.sa_handler的地址。上面還有一行要注意:

#!cpp
spin_lock_irqsave(&t->sighand->siglock, flags);
複製代碼

t->sighand->siglock在t->sighand->action[sig-1].sa.sa_handler的常量偏移上,內核會調用spin_local_irqsave在某些地址上,X+SPINLOCK的內容沒法控制。這會發生什麼呢?兩種可能性:

  1. X+SPINLOCK所在的內存地址看起來像沒有鎖的spinlock。spin_lock_irqsave會當即完成。最後,spin_unlock_irqrestore會撤銷spin_lock_irqsave的寫操做。

2.X+SPINLOCK所在的內存地址看起來像上鎖的spinlock。若是咱們不介入的話,spin_lock_irqsave會無線循環等待spinlock。有些擔憂,要繞過這個障礙咱們得須要其餘假設 ---|| X+SPINLOCK所在內存地址的內容。這是可接受的,咱們能夠在後面看到在內核.data區域裏設置X。

* 首先,準備FAKE_CURRENT,讓t->sighand->siglock指向用戶空間上鎖的區域,SPINLOCK_USERMODE
* force_sig_info()會掛在spin_lock_irqsave裏
* 這時,另一個用戶空間的線程在另一個CPU上運行,而且改變了t->sighand,因此t->sighand->action[sig-1.sa.sa_hander成了咱們的覆蓋目標,以後解鎖SPINLOCK_USERMODE
* spin_lock_irqsave會返回
* force_sig_info()會從新載入t->sighand,執行指望的寫操做
複製代碼

鼓勵細心的讀者追問爲何不能使用第2種方案,即X+SPINLOCK在初始時是沒有鎖的。這並非所有 ---|| 咱們須要準備一些FAKE_CURRENT的字段來讓儘可能少的代碼執行。我不會再透露更多細節 ---|| 這篇BLOG已經夠長了....下一步會發生什麼?force_sig_info()和do_general_protection()返回。接下來iret指令會再次產生#SS異常處理(由於仍然是用戶空間ss的值在棧上引用了一個nonpresent段),但這一次,#SS處理程序裏的額外swapgs指令會返回並取消以前不正確的swapgs。 do_general_protection()會調用和操做真正的task_struct,而不是僞造的FAKE_CURRENT。最終,current會發出SIGSEGV信號,其餘進程會被調度來執行。這個系統仍然是穩定的。

enter image description here

0x07 插曲:SMEP


SMEP是Intel處理器從第3代Core(Shawn:酷睿)時加入的硬件特性。若是控制寄存器CR4裏的SMEP位被設置的話,當RING0(Shawn:標準Linux內核是RING0,在XEN下是例外,RING0是Hypervisor)嘗試執行的代碼來自標記爲用戶空間的內存頁,CPU就會生成一個錯誤(Shawn:就是拒絕)。若是可能的話,Linux內核會默認開啓SMEP。

0x08 實現代碼執行


以前的章節講述了一種如何以0在內核內存中覆蓋8個連續字節的方法。若是SMEP開啓的狀況下如何實現代碼執行呢?

直接覆蓋一個內核代碼的指針是不行的。咱們能夠清零top bytes( Shawn: MSB)- 但以後的地址會在用戶空間,因此SMEP會阻止這個指針的deref。

換一種方式,咱們能夠清零幾個low bytes( Shawn: LSB),可是以後能利用這個指針的機率也很低。

咱們須要一個內核指針P指向結構X包含了代碼指針。咱們能夠覆蓋P的top bytes讓她成爲一個用戶空間的地址,這樣P->code_pointer_in_x()調用會跳轉到一個咱們能選擇的地址。我不肯定最好選擇哪一個攻擊對象。從個人經驗來看,我選擇內核proc_root變量,這是一個結構體:

#!cpp
struct proc_dir_entry {
            ...
        const struct inode_operations *proc_iops;
        const struct file_operations *proc_fops;
        struct proc_dir_entry *next, *parent, *subdir;
        ...
        u8 namelen;
        char name[];
};
複製代碼

這個結構體是一個proc文件系統的入口(proc_root是/proc做爲proc文件系統的根目錄)。當一個文件名路徑開始在/proc裏查詢時,subdir指針(從proc_root.subdir開始)會跟進,直到名字被找到。以後proc_iops的指針會被調用:

#!cpp
struct inode_operations {
        struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
        void * (*follow_link) (struct dentry *, struct nameidata *);
        ...many more...
        int (*update_time)(struct inode *, struct timespec *, int);
        ...
} ____cacheline_aligned;
複製代碼

proc_root駐紮在內核代碼段裏,這意味着漏洞利用須要知道她的地址。這個信息能夠從/proc/kallsyms符號表獲得;固然,不少加固過的內核不容許普通用戶讀取這個文件。但若是內核是一個已知的build(標準的GNU/Linux發行版),這個地址能夠輕鬆得到;和一堆偏移同樣須要構建FAKE_CURRENT。

咱們會覆蓋proc_root.subdir,讓她成爲一個指向一個在用戶空間能被控制的結構體proc_dir_entry。有點困難在於咱們不能覆蓋整個指針。別忘了咱們的寫操做是「覆蓋8個0」。若是咱們讓proc_root.subdir變成0,咱們不會去映射她,由於Linux內核不容許用戶空間映射到地址0上(更確切的說發是,任何低於/proc/sys/vm/mmap_min_addr的地址,默認值通常是4k)。(Shawn:想一想哪些0ld good hacking days,天天都有一堆NULL pointer deref是多麼幸福活着無挑戰的時光啊;-))。這意味着咱們須要:

1. 映射16MB的內存到地址4096
2. 使用相似proc_dir_entry的方式來填充,把inode_operations字段指向用戶空
間的地址FAKE_IOPS,name字段爲字符串"A"。
3. 配置漏洞利用去覆蓋proc_root.subdir的top 5 bytes。
複製代碼

以後,除非proc_root.subdir最低的3 bytes是0,咱們能夠肯定在觸發force_sig_info()覆蓋後,proc_root.subdir會指向被控制的用戶空間內存。當咱們的進程調用open("/proc/A",...)時,FAKE_IOPS的指針會被調用。她們應該指向哪裏呢?若是你認爲答案是「指向咱們的shellcode「,請再讀一遍上面的分析。

咱們須要讓FAKE_IOPS指針指向一個stack pivot1序列。這再次假設了具體內核運行的版本狀況。一般的"xchg %esp, %eax; ret"代碼序列(2個字節,94 c3是在測試內核的地址0xffffffff8119f1ed)很好的能夠用於64位內核的ROP。就算沒能控制%rax,這個xchg指令操做32位的寄存器也能清掉%rsp的高32位而讓%rsp着陸在用戶空間的內存裏。在最糟糕的狀況下,咱們能夠分配低4GB的虛擬內存而後填充ROP鏈條。

在當前測試的內核(Fedora 20)有兩種方法去deref在FAKE_IOPS的指針:

1. %rax:=FAKE_IOPS; call *SOME_OFFSET(%rax)
2. %rax:=FAKE_IOPS; %rax:=SOME_OFFSET(%rax); call *%rax 
複製代碼

第1種狀況裏,在%rsp和%rax交換值後,她會等於FAKE_IOPS。咱們須要ROP鏈條駐紮在FAKE_IOPS的起始位置,這須要相似「add $A_LOT, %rsp; ret」的指令,而後在繼續。

第2種狀況裏,%rsp會分配低32位的調用目標,即0x8119f1ed。咱們須要準備在這個地址上的ROP鏈條。

計算一下%rax值有二者之一的已知值在特定的時間指向stack pivot序列,咱們不須要ROP鏈條填充整個4GB內存,只須要上面的兩個地址便可。第2種狀況的ROP鏈條自身很簡潔:

#!bash
unsigned long *stack=0x8119f1ed;
*stack++=0xffffffff81307bcdULL;  // pop rdi, ret
*stack++=0x407e0;                //cr4 with smep bit cleared
*stack++=0xffffffff8104c394ULL;  // mov rdi, cr4; pop %rbp; ret
*stack++=0xaabbccdd;             // placeholder for rbp
*stack++=actual_shellcode_in_usermode_pages;
複製代碼

0x09 插曲:SMAP


SMAP是Intel從第5代Core處理器推出的一個硬件特性。若是CR4控制寄存器的SMAP位被設置的話,CPU會拒絕用戶空間的頁被RING0訪問(Shawn:我的理解,SMAP和SMEP最大的不一樣主要是SMEP針對代碼段,而SMAP針對數據段)。Linux內核一般會默認開啓SMAP。一個測試的內核模塊(Core-M 5Y10a CPU)嘗試訪問用戶空間而後crash了:

#!bash
[  314.099024] running with cr4=0x3407e0
[  389.885318] BUG: unable to handle kernel paging request at 00007f9d87670000
[  389.885455] IP: [ffffffffa0832029] test_write_proc+0x29/0x50 [smaptest]
[  389.885577] PGD 427cf067 PUD 42b22067 PMD 41ef3067 PTE 80000000408f9867
[  389.887253] Code: 48 8b 33 48 c7 c7 3f 30 83 a0 31 c0 e8 21 c1 f0 e0 44 89 e0 48 8b 
複製代碼

正如咱們看到的,用戶空間的頁是正常的,但訪問也報了頁錯誤。Windows系統不太支持SMAP;Windows 10技術預覽版build 9926的cr4=0x1506f8(SMEP啓動,SMAP關閉);對比Linux內核(一樣的測試硬件)你能夠看到cr4的bit 21是沒有設置的。這不奇怪,在Linux中,訪問用戶空間是經過調用copy_from_user(),copy_to_user()和相似函數顯式執行的,因此執行這些操做時臨時關閉SMAP是可行的。在Windows上,內核代碼直接訪問用戶空間代碼,只是包裝了一層訪問異常處理程序,因此要讓SMAP工做正常須要調整全部的驅動,這是一項困難的工做。

0x0A SMAP to the rescue!


上面的漏洞利用方法依賴於在用戶空間裏準備特定的數據結構,而後強制內核認爲她們是可信的內核數據。這種方法對於開啓SMAP特性的內核不奏效 ---|| CPU會拒絕從用戶空間讀取惡意數據。咱們能作的是構造全部須要用的數據結構,而後拷貝她們到內核。好比:

#!cpp
write(pipe_filedescriptor, evil_data, ...
複製代碼

以後evil_data會被拷貝到一個內核管道緩衝區裏。咱們可能須要猜想她的地址; some sort of heap spraying, combined with the fact that there is no spoon^W effective kernel ASLR[9], could work, although it is likely to be less reliable than exploitation without SMAP.

總之,還有最後一個障礙 ---|| 不要忘了咱們須要設置用戶空間的gs base去指向咱們的漏洞利用的數據結構。在上面的場景(沒有SMAP),咱們使用arch_prctl(ARCH_SET_GS)系統調用,她是這樣在內核裏實現的:

#!bash
long do_arch_prctl(struct task_struct *task, int code, unsigned long addr)
{ 
         int ret = 0; 
         int doit = task == current;
         int cpu;

         switch (code) { 
         case ARCH_SET_GS:
                 if (addr >= TASK_SIZE_OF(task))
                         return -EPERM; 
                 ... honour the request otherwise
複製代碼

休斯頓,咱們有一個麻煩 ---|| 咱們不能使用這個API去設置gs base用戶空間以上的內存!

最近的CPU有wrgsbase指令能夠直接設置gs base,這是一個非特權級指令,但須要經過內核設置CR4控制寄存器中的FSGSBASE bit( no 16)來開啓。Linux並無設置這個位,所以用戶空間不能使用這條指令。

在64位系統上,非系統級的GDT和LDT條目依然是8個字節長,base field是最大4GB-1,因此根本沒有機會設置一個基地址的段在內核空間裏。因此,除非我漏掉了能在內核裏設置用戶態gs base的其餘方法,否則SMAP能保護CVE-2014-9322針對64位Linux內核任意代碼執行的漏洞利用。

1 CVE-2014-9322 http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9322

2 Upstream fix http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6f442be2fb22be02cafa606f1769fa1e6f894441

3 Intel Software Developer’s Manuals, http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

[4] SMEP http://vulnfactory.org/blog/2011/06/05/smep-what-is-it-and-how-to-beat-it-on-linux/

[5] SMAP http://lwn.net/Articles/517475

[6] "pretend-it-was-#GP-in-userspace" https://lists.debian.org/debian-kernel/2014/12/msg00083.html

[7] Stack Pivoting https://trailofbits.files.wordpress.com/2010/04/practical-rop.pdf

[8] TSX improves timing attacks against KASLR http://labs.bromium.com/2014/10/27/tsx-improves-timing-attacks-against-kaslr/

相關文章
相關標籤/搜索