<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/6229

            0x00 前言


            作者: R3dF09@騰訊玄武實驗室

            2015 年 3 月 Google Project Zero 發表文章 Exploiting the DRAM rowhammer bug to gain kernel privileges。由于文中提到的缺陷比較難以修復,需要更新 BIOS 來提 高內存刷新的速度,引起了人們的擔憂。然而由于 RowHammer 的運行需要在目 標主機上運行特定的匯編代碼,實施攻擊存在很大的難度。

            本文旨在研究能否通過 Javascript 等腳本語言的動態執行觸發 RowHammer, 如果能夠成功將極大增加 RowHammer 的攻擊性。為了驗證該思路,本文分析了 Java Hotspot、Chrome V8、.NET CoreCLR 以及 Firefox SpiderMonkey 的實現機制并 給出了可行性分析。

            遺憾的是我們在這幾個程序中,沒有找到最優的利用方式。要么不存在相關 的指令,要么指令無法達到 RowHammer 要求,要么需要有額外的操作更改執行 環境才能觸發,缺乏實際的攻擊意義。

            0x01 RowHammer


            本節將簡要回顧 RowHammer 存在的原理,其觸發的機制,已經在利用時將 面臨到的一些挑戰。

            1.1 What`s RowHammer?

            RowHammerDDR3 內存中存在的問題,通過頻繁的訪問內存中的一行(row) 數據,會導致臨近行(row)的數據發生位反轉。如圖 1.1(a)所示,內存是由一系列 內存單元構成的二維數組。如圖 1.1(b)所示每一個內存單元由一個晶體管和一個 電容組成,晶體管與 wordline 相連,電容負責存儲數據。DRAM 的每一行(row) 有自己的 wordline,wordline 需要置高電壓,特定行(row)的數據才能夠訪問。當 某一行的 wordline 置高電壓時,該行的數據就會進入 row-buffer。當 wordline 頻 繁的充放電時,就可能會導致附近 row 的存儲單元中的電容放電,如果在其被刷 新之前,損失過多的電壓就會導致內存中的數據發生變化。

            圖 1.2 所示是一塊內存,其中一個 row 為 64kb(8KB)大小, 32k 個 row 組成一 個 Bank, 8 個 Bank 組成一個 Rank, 該 Rank 為 2G。此處需要注意不同的 Bank 有 專用的 row-buffer,訪問不同 Bank 中的 row 不會導致 wordline 的充放電。

            內存中的電壓是不能長期保存的,需要不停的對其進行刷新,刷新的速度為 64ms,所以必須在 64ms 內完成 RowHammer 操作。

            enter image description here

            enter image description here

            1.2 RowHammer 觸發的方法


            表 1.1 所示為 Google Project Zero 所給出的可以觸發 RowHammer 的代碼段。

            表 1.1

            code1a:
              mov (X), %eax // Read from address X
              mov (Y), %ebx // Read from address Y 
              clflush (X) // Flush cache for address X 
              clflush (Y) // Flush cache for address Y 
              jmp code1a
            

            其中 x, y 地址的選擇非常重要,x, y 必須要在同一個 Bank,不同的 row 中。

            因為不同的 Bank 有專用的 row-buffer。如果 x, y 在同一個 row 中就不會對 wordline 進行頻繁的充放電,也就不會觸發 RowHammer

            上述代碼只是一種有效的測試方法,但并不是惟一的,歸根到底我們所需要 的就是在 64ms 內讓一個 wordline 頻繁的充放電。

            1.3 觸發 RowHammer 指令


            為了頻繁的使 wordline 充放電,必須考慮 CPU 的 Cache, 如果當前地址在Cache 里面就不會訪問內存,也就不會導致 wordline 的充放電情況。

            表 1.2

            指令 作用
            CLFLUSH 將數據從 Cache 中擦除
            PREFETCH 從內存中讀取數據并存放在 Cache 中
            MOVNT* 不經過 Cache 直接操作數據

            表 1.2 中的指令都可以用來頻繁的訪問一個內存地址,并使相應的 wordline 充放電,如果要觸發 RowHammer, 需要上述指令的配合才能完成。

            (注: 這些指令并不是惟一的觸發方法,比如通過分析物理地址和 L3 Cache 的映射關系算法(不同的 CPU 架構實現可能不同),找到映射到同一個 Cache set 的一系列地址,通過重復訪問這一系列的地址即可觸發 RowHammer。)

            0x02 腳本層面觸發 RowHammer


            Google Project Zero 給出的 POC 是直接以匯編的方式來運行,可以用來驗證 內存是否存在安全問題。當前腳本語言大都存在 JIT 引擎,如果能夠通過腳本控 制 JIT 引擎直接觸發 RowHammer,將會具有更大的攻擊意義。為了分析其可行性,本節研究了 Java HotspotChrome V8 等執行引擎的運行機制。

            2.1 Java Hotspot


            HotspotOracle JDK 官方的默認虛擬機,主要用來解釋執行 Java 字節碼,其 源碼位于 Openjdk 下 hotspot 目錄,可以獨立編譯。Java 字節碼是堆棧式指令集, 指令數量少,共有 256 條指令,完成了數據傳送、類型轉換、程序控制、對象操 作、運算和函數調用等功能。Java 字節碼存儲在 class 文件中,作為 Hotspot 虛 擬機的輸入,其在一定程序上是用戶可控的。那么能否通過構造 class 文件,使 得 Hotspot 在執行時完成 RowHammer 呢?

            Java Hotspot 默認對字節碼進行解釋執行,當某方法被頻繁調用,并且達到一定的閾值,即會調用內置的 JIT 編譯器對其進行編譯,在下次執行時直接調用編 譯生成的代碼。

            Java 字節碼解釋器有兩個實現,分別為模版解釋器和 C++解釋器,Hotspot 默 認使用模版解釋器。Java 的 JIT 編譯器有三個實現,分別為客戶端編譯器(C1 編 譯器)、服務器端編譯器(C2 編譯器)以及 Shark 編譯器(基于 LLVM)的編譯 器。

            圖 2.1 所示為 Java 在不同平臺下使用的虛擬機。

            enter image description here

            enter image description here

            2.1.1 模版解釋器觸發 RowHammer?

            a) 模版解釋器工作原理

            模版解釋器是一種比較靠近底層的解釋器實現,每一個字節碼對應一個模版, 所有的模版組合在一起構成一個模板表。每一個模版實質上都是一段匯編代碼, 在虛擬機創建階段進行初始化。在執行 class 文件的時候,遍歷字節碼,每檢測 到一個字節碼就調用相應的匯編代碼塊進行執行,從而完成對于字節碼的解釋執 行。

            為了完成對于字節碼的解釋執行,Hotspot 在初始化時還會生成多種匯編代 碼塊,用來輔助字節碼的解釋,比如函數入口代碼塊,異常處理代碼塊等。查看 Hotspot 中生成的代碼塊和模版可以采用命令 java –XX:+PrintInterpreter 指令來 完成。

            針對各個字節碼的模版中匯編代碼比較龐大,比如字節碼 invokevirtual 對應 的代碼塊共有 352 bytes,字節碼 putstatic512 bytes

            b) 解釋器能否觸發 RowHammer?

            字節碼在解釋執行的時候會產生匯編代碼,那么是否可以通過 class 文件讓解釋器生成 RowHammer 需要的指令呢?

            通過分析,字節碼對應的模版和輔助代碼塊的指令中沒有 prefetch, clflush 以 及 movnt*系列指令,所以直接通過構造字節碼,然后使用模版解釋器來觸發 RowHammer 是不可行的。

            ***||2.1.2 JIT 編譯器觸發 RowHammer?

            a) C1 編譯器工作原理 ?? JIT 編譯器也是一種編譯器,只不過其是在程序動態運行過程中在需要的時候 對代碼進行編譯。其編譯流程與一般編譯器基本相同。

            C1 編譯器是客戶端使用的 JIT 編譯器實現,其主要追求編譯的速度,對于代 碼的優化等要相對保守。

            Hotspot 編譯器默認是異步編譯,有線程 CompilerThread 負責對特定的方法 進行調用,當方法調用次數達到一定閾值時將會調用 JIT 編譯器對方法進行編譯, 該閾值默認為 10000 次,可以通過 –XX:+CompileThreshold 參數來設置閾值。

            enter image description here

            圖 2.2 從代碼級別看,C1 編譯器共包含如下幾個步驟

            表 2.1

            typedef enum {
              _t_compile,
              _t_setup,
              _t_optimizeIR,
              _t_buildIR,
              _t_emit_lir,
              _t_linearScan,
              _t_lirGeneration,
              _t_lir_schedule,
              _t_codeemit,
              _t_codeinstall,
              max_phase_timers
            } TimerName;
            

            C1 編譯器執行流程大致如圖 2.2 所示:

            1) 生成 HIR (build_hir)

            C1 編譯器首先分析 JVM 字節碼流,并將其轉換成控制流圖的形式,控制流 圖的基本塊使用 SSA 的形式來表示。HIR 是一個層級比較高的中間語言表示 形式,離機器相關的代碼還有一定的距離。

            2) 生成 LIR (emit_lir)

            遍歷控制流圖的各個基本塊,以及基本塊中個各個語句,生成相應的 LIR 形 式,LIR 是一個比較接近機器語言的表現形式,但是還不是機器可以理解的 代碼。

            3) 寄存器分配

            LIR 中使用的是虛擬寄存器,在該階段必須為其分配真實可用的寄存器。C1為了保證編譯的速度采用了基于線性掃描的寄存器分配算法

            4) 生成目標代碼

            真正生成平臺相關的機器代碼的過程,在該階段遍歷 LIR 中的所有指令,并 生成指令相關的匯編代碼。主要是使用了 LIR_Assembler 類。

            表 2.2

            #!c++
            LIR_Assembler lir_asm(this); 
            lir_asm.emit_code(hir()->code());
            

            通過遍歷 LIR_List 依次調用,依次調用各個指令相關的 emit_code(如表 2-3), LIR 中的指令都是繼承自 LIR_Op

            表 2.3

            #!c++
            op->emit_code(this);
            

            以 LIR_Op1 為例,其 emit_code 方法為

            表 2.4

            #!c++
            void LIR_Op1::emit_code(LIR_Assembler* masm) { //emit_code 
                masm->emit_op1(this);
            }
            

            假設 LIR_Op1 的操作碼為 LIR_prefetchr

            表 2.5

            #!c++
            case lir_prefetchr:
              prefetchr(op->in_opr());
              break;
            

            最終會調用 prefetchr 函數,該函數為平臺相關的,不同的平臺下實現不同, 以 x86 平臺為例,其實現位于assembler_x86.cpp

            表 2.6

            #!c++
            void Assembler::prefetchr(Address src) {
               assert(VM_Version::supports_3dnow_prefetch(), "must support");
               InstructionMark im(this);
               prefetch_prefix(src);
               emit_byte(0x0D);
               emit_operand(rax, src); // 0, src
            }
            

            最終將會生成相應的機器碼。

            b) 能否觸發 RowHammer

            C1 編譯器是否能夠觸發 RowHammer? 經過分析發現,在 x86 平臺下封裝了 prefetch 相關的指令,確實是有希望控制產生 prefetch 指令。

            從底層向上分析,如果要生成 prefetch 指令,在 LIR 層需要出現 LIR_op1 操 作,且操作碼需要為 lir_prefetchr 或者 lir_prefetchw,進一步向上層分析,要在 LIR 層出現這樣的指令,在從字節碼到 HIR 的過程中必須能夠調用到 GraphBuilder::append_unsafe_prefetch 函 數 。 該 方 法 在 GraphBuilder::try_inline_instrinsics 函 數 中 調 用 , 進 一 步 分 析 只 需 調 用 sun.misc.unsafe 的 prefetch 操作即可觸發。通過深入分析,Hotspot 確實是支持 prefetch 操作,然而在 Java 的運行庫 rt.jar 中,sun.misc.unsafe 并沒有聲明 prefetch 操作,導致無法直接調用,需要更改 rt.jar才能觸發成功。這樣就失去了攻擊的 意義。

            在 Hotspot 中還存在 clflush 這種指令,在 hotspot 的初始化階段,其會生成 一個代碼塊。如下所示:

            表 2.7

            #!c++
            __ bind(flush_line);
            __ clflush(Address(addr, 0)); //addr: address to flush
            ?__ addptr(addr, ICache::line_size);
            __ decrementl(lines); //lines: range to flush 
            __ jcc(Assembler::notZero, flush_line);
            

            該部分代碼在 C1 編譯器編譯完成之后有調用

            表 2.8

            #!c++
            // done
            masm()->flush(); //invoke ICache flush
            

            對當前代碼存儲的區域進行 cache flush

            表 2.9

            #!c++
            void AbstractAssembler::flush() {
                sync();
                ICache::invalidate_range(addr_at(0), offset());
            }
            

            這種方法可以對內存做 cache flush, 主要問題在于代碼存儲的區域在堆中是 隨機分配的,無法直接指定 cache flush 的區域,而且由于涉及到編譯的操作,無 法在短時間內大量產生 clflush 指令。

            c) 其它編譯器實現

            C2 編譯器與 C1 編譯器有一定的相似性又有很大的不同,由于主要在服務器 端使用所以 C2 編譯器更加注重編譯后代碼的執行效率,所有在編譯過程中相對 C1 編譯器做了大量的優化操作,但是在生成匯編代碼的時候兩者使用的是同一 個抽象匯編,所以 C2 編譯器與 C1 編譯器應該大體相同,能夠生成 prefetch 指令, 但是在默認的情形下無法直接使用。

            Shark 編譯器是基于 LLVM 實現的,一般都不會開啟,沒有對該編譯器進行進 一步的分析。

            2.2 Chrome V8


            V8 是 Google 開源的 Javascript 引擎,采用 C++編寫,可獨立運行。V8 會直接 將 JavaScript 代碼編譯成本地機器碼,沒有中間代碼,沒有解釋器。其執行機制 是將 Javascript 代碼轉換成抽象語法樹,然后直接 walk 抽象語法樹,生成相應的 機器碼。

            在 V8 生成機器碼的過程中無法生成 prefetch, clflush, movnt*系列指令。但是 在 V8 執行的過程中可能會引入 prefetch 指令。

            產生 prefetch 的函數為

            表 2.10

            #!c++
            MemMoveFunction CreateMemMoveFunction() {
            

            表 2.11

            #!c++
            __ prefetch(Operand(src, 0), 1); 
            __ cmp(count, kSmallCopySize); 
            __ j(below_equal, &small_size); 
            __ cmp(count, kMediumCopySize); 
            __ j(below_equal, &medium_size); 
            __ cmp(dst, src);
            __ j(above, &backward);
            

            該函數的主要作用是當緩沖區無法滿足指令的存儲時,需要將緩沖區擴大一 倍,在該過程中會調用一次 prefetch 指令,但是調用的此處遠遠不足 RowHammer 觸發的條件。

            2.3 .NET CoreCLIR


            CoreCLR 是.NET 的執行引擎,RyuJIT 是.NET 的 JIT 實現,目前已經開源。作為 Java 的競爭對手,.NET 大量參考了 Java 的實現機制,從字節碼的設計,到編譯 器的實現等,都與 Java 有幾分相似。在 RyuJIT 的指令集定義中只定義了一些常 見的指令(圖 2.3),但是沒有 RowHammer 需要的指令,所以無法直接觸發。但是 在 CoreCLR 的 gc 中存在 prefetch 操作(表 2.12),然而該指令默認是被置為無效的 (表 2.13)。

            enter image description here

            圖 2.3

            表 2.12

            #!c++
            void gc_heap::relocate_survivor_helper (BYTE* plug, BYTE* plug_end) {
                BYTE* x = plug; 
                while (x < plug_end) {
                    size_t s = size (x);
                    BYTE* next_obj = x + Align (s); 
                    Prefetch (next_obj); 
                    relocate_obj_helper (x, s); 
                    assert (s > 0);
                    x = next_obj;
            } }
            

            表 2.13

            #!c++
            //#define PREFETCH
            #ifdef PREFETCH
            __declspec(naked) void __fastcall Prefetch(void* addr) {
               __asm {
                  PREFETCHT0 [ECX]
                  ret
               }; 
            }
            #else //PREFETCH
            inline void Prefetch (void* addr) {
                UNREFERENCED_PARAMETER(addr);
            ?????}
            #endif //PREFETCH
            

            2.4 Firfox SpiderMonkey


            SpiderMonkey 是 Firfox 默認使用的帶有 JIT 的 Javascript 引擎,在 SpiderMonkey中沒有 RowHammer 所需要的指令出現。

            0x03 總結


            本文研究的主要目的是希望通過 JIT 引擎來觸發 RowHammer 的執行,為了 提高腳本語言的執行效率,當前絕大多數腳本引擎都帶有 JIT 編譯器以提高運行 的效率。本文研究了 HotspotV8RyuJITSpiderMonkey,其中并沒有找到比 較好的觸發 RowHammer 的方法,當然依舊有一些 JIT 還沒被研究,不過通過以 上研究證明 JIT 觸發的方式非常困難,原因主要有以下幾點:

            1) RowHammer 的觸發條件比較苛刻,64ms 內觸發成功也就意味著無關指令的 數目必須很少,否者在 64ms 內 wordline 無法充放電足夠的次數。

            2) Cache 相關的指令并不常用,RowHammer 運行需要使用 CLFLUSH, PREFETCH, MOVNT*系列指令,這些指令在實際的使用過程中并不常見,在用戶態進行 Cache 相關操作的情形比較少見。

            3) 站在 JIT 開發人員的角度考慮,為了實現跨平臺,一般會對指令進行抽象, 然后在各個平臺上具體實現。抽象的指令一般都盡可能少,因為每抽象一個 指令就需要再添加大量的代碼。在分析的 JIT 引擎中只有 hotspot 抽象了 prefetch 指令,引擎都盡可能少的去抽象編譯器要用到的指令,想通過腳本 直接生成需要的匯編指令很困難。(特例是如果采用了第三方引擎(比如 AsmJit),引擎會抽象所有的匯編指令,則有更大的可能性觸發,然而當前主 流語言的 JIT 部分大都是獨立開發,而第三方引擎則多是從這些代碼中提取 并逐步完善的)。

            4)在整個分析過程中發現指令出現的原因主要是輔助 JIT 編譯,比如使用 prefetch 提高某些數據存取的速度,使用 CLFLUSH 刷新指令緩沖區等。指令 出現的次數與頻率,遠遠達不到 RowHammer 的要求。

            參考資料


            1. Google Project Zero 博客 http://googleprojectzero.blogspot.com/2015/03/exploiting-dram-rowhammer-bug-to-gain.html

            2. Paper: Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors http://users.ece.cmu.edu/~yoonguk/papers/kim-isca14.pdf

            3. 高級語言虛擬機群組 http://hllvm.group.iteye.com/ ????

            4. 各語言開放的源代碼

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线