偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入Linux | 如何在任意進(jìn)程中修改內(nèi)存保護(hù)(含PoC)

存儲 存儲軟件
在現(xiàn)代操作系統(tǒng)中,每個進(jìn)程都有自己的虛擬地址空間(從虛擬地址映射到物理地址)。此虛擬地址空間由內(nèi)存頁(某些固定大小的連續(xù)內(nèi)存塊)組成,每個頁都有保護(hù)標(biāo)志,用于確定允許此頁面訪問的類型(讀取,寫入和執(zhí)行)。

 前言

最近,我們遇到一個非常具體的問題:改變?nèi)我膺M(jìn)程的內(nèi)存區(qū)域的保護(hù)標(biāo)志。這項任務(wù)看似微不足道,但是我們著實遇到了一些麻煩,在此過程中也學(xué)到了關(guān)于Linux機(jī)制和內(nèi)核開發(fā)相關(guān)的東西。以下是一些簡要概述,其中包括了當(dāng)時采取的三種方案,每次也在尋求更好解決方案。

[[254657]]

 

概述

在現(xiàn)代操作系統(tǒng)中,每個進(jìn)程都有自己的虛擬地址空間(從虛擬地址映射到物理地址)。此虛擬地址空間由內(nèi)存頁(某些固定大小的連續(xù)內(nèi)存塊)組成,每個頁都有保護(hù)標(biāo)志,用于確定允許此頁面訪問的類型(讀取,寫入和執(zhí)行)。這種機(jī)制依賴于架構(gòu)頁表(有趣的是,在x64架構(gòu)中,你不能使頁面只寫(write-only),就算你特意從操作系統(tǒng)請求,它也總是可讀的)。

在Windows中,你可以使用VirtualProtect或VirtualProtectEx這兩個API更改內(nèi)存區(qū)域的保護(hù)。后者讓我們的任務(wù)變得非常簡單:它的***個參數(shù)hProcess是“要改變內(nèi)存保護(hù)的進(jìn)程的句柄”(參見MSDN.aspx))。

另一方面,在Linux中,我們并不那么幸運(yùn):更改內(nèi)存保護(hù)的API是系統(tǒng)調(diào)用mprotect或pkey_mprotect,并且兩者始終在當(dāng)前進(jìn)程的地址空間上運(yùn)行。 我們現(xiàn)在回顧一下在x64架構(gòu)上的Linux中解決此任務(wù)的方法(我們假設(shè)是root權(quán)限)。

而在Linux中,我們就沒那么幸運(yùn)了,更改內(nèi)存保護(hù)的API是系統(tǒng)調(diào)用(mprotect或pkey_mprotect),并且兩者始終在當(dāng)前進(jìn)程的地址空間上運(yùn)行。所以現(xiàn)在我們來回顧一下在Linux x64架構(gòu)上解決此問題的方法(假設(shè)是root權(quán)限)。

方案一:代碼注入

如果mprotect總是作用于當(dāng)前進(jìn)程,那么我們就需要讓目標(biāo)進(jìn)程從它自己的上下文中調(diào)用它。這稱為代碼注入,可以通過許多不同的方式實現(xiàn)。我們選擇使用ptrace機(jī)制實現(xiàn)它,其允許一個進(jìn)程“觀察并控制另一個進(jìn)程的執(zhí)行”(參見手冊),包括更改目標(biāo)進(jìn)程的內(nèi)存的能力。此機(jī)制用于調(diào)試器(如gdb)和跟蹤程序(如strace)。使用ptrace注入代碼所需的步驟如下:

1. 通過ptrace附加到目標(biāo)進(jìn)程。如果進(jìn)程中有多個線程,那就終止所有其他線程

2. 找到可執(zhí)行內(nèi)存區(qū)域(通過檢查/proc/PID/maps)并在那里寫操作碼(hex:0f 05)

3.根據(jù)調(diào)用約定修改寄存器:首先將rax更改為mprotect的系統(tǒng)調(diào)用號(即10)。然后三個參數(shù)(起始地址,長度和所需的保護(hù))分別存儲在rdi,rsi和rdx中。***,將rip更改為步驟2中使用的地址

4. 恢復(fù)進(jìn)程直到系統(tǒng)調(diào)用返回(ptrace允許你跟蹤系統(tǒng)調(diào)用的進(jìn)入和退出)

5. 恢復(fù)被覆蓋的內(nèi)存和寄存器,從進(jìn)程中分離并恢復(fù)正常執(zhí)行

這種方法是***個也是最直觀的方法,但是我們之后發(fā)現(xiàn)Linux中的另一種叫seccomp的機(jī)制會工作得更好。它是Linux內(nèi)核中的一個安全工具,允許進(jìn)程自己進(jìn)入某種封閉狀態(tài),除了read,write,_exit和sigreturn之外,它不能調(diào)用任何系統(tǒng)調(diào)用。不過也可以選擇任意系統(tǒng)調(diào)用及其參數(shù)來僅僅過濾指定的系統(tǒng)調(diào)用。

因此,如果進(jìn)程啟用了seccomp模式并且我們嘗試將mprotect調(diào)用到其中,那么內(nèi)核將終止進(jìn)程,因為不允許此系統(tǒng)調(diào)用。所以我們要尋求更好的解決方案……

方案二:模仿內(nèi)核模塊中的mprotect

由于seccomp,用戶態(tài)中每個解決方案都不可行,因此下一個方法肯定存在于內(nèi)核態(tài)中。在Linux內(nèi)核中,每個線程(用戶線程和內(nèi)核線程)都由名為task_struct的結(jié)構(gòu)表示,并且當(dāng)前線程(任務(wù))可通過指針訪問。內(nèi)核中mprotect的內(nèi)部實現(xiàn)使用指針current,所以我們首先想到的是將mprotect的代碼復(fù)制粘貼到我們的內(nèi)核模塊,并用指向目標(biāo)線程的task_struct的指針替換每次出現(xiàn)的current。

可能你已經(jīng)猜到了,復(fù)制C代碼并不是那么簡單,其中有大量我們無法訪問的,未導(dǎo)出的函數(shù),變量和宏。某些函數(shù)聲明在頭文件中導(dǎo)出,但內(nèi)核不會導(dǎo)出它們的實際地址。如果內(nèi)核是由kallsyms支持編譯的,那么這個特定的問題就可以解決,然后通過文件/proc/kallsysm導(dǎo)出所有內(nèi)部符號。

盡管存在這些問題,我們?nèi)砸詍protect的本質(zhì)進(jìn)行嘗試,甚至僅用于教育目的。因此,我們開始編寫一個內(nèi)核模塊,它獲取mprotect目標(biāo)PID和參數(shù),并模仿其行為。首先,我們需要獲取所需的內(nèi)存映射對象,它表示線程的地址空間:

 

現(xiàn)在我們有了內(nèi)存映射對象,就需要深入挖掘。Linux內(nèi)核實現(xiàn)了一個抽象層來管理內(nèi)存區(qū)域,每個區(qū)域由結(jié)構(gòu)vm_area_struct表示。為了找到正確的內(nèi)存區(qū)域,我們使用函數(shù)find_vma,它通過所需的地址搜索內(nèi)存映射。

vm_area_struct包含字段vm_flags,其以與結(jié)構(gòu)無關(guān)的方式表示存儲器區(qū)域的保護(hù)標(biāo)志,vm_page_prot以體系結(jié)構(gòu)相關(guān)的方式表示。單獨(dú)更改這些字段不會真正地影響頁表(但會影響proc/PID/maps的輸出,我們已經(jīng)嘗試過)。 你可以點擊這里獲取更多內(nèi)容。

在深入研究內(nèi)核代碼之后,我們發(fā)現(xiàn)了真正改變內(nèi)存區(qū)域保護(hù)所需的最基本工作:

1. 將字段vm_flags更改為所需的保護(hù)

2. 調(diào)用函數(shù)vma_set_page_prot_func來根據(jù)vm_flags字段更新vm_page_prot

3. 調(diào)用函數(shù)change_protection_func更新頁表中的保護(hù)位。

這段代碼雖然有效,但它有很多問題,首先,我們只實現(xiàn)了mprotect的基本部分,但原始函數(shù)比我們做的要多得多(例如通過保護(hù)標(biāo)志分割和連接內(nèi)存區(qū)域)。其次,我們使用兩個內(nèi)核函數(shù),這些函數(shù)不是由內(nèi)核導(dǎo)出的(vma_set_page_prot_func和change_protection_func)。我們可以使用kallsyms來調(diào)用它們,但是這很容易出問題(將來可能會更改它們的名稱,或者會改變內(nèi)存區(qū)域的整個內(nèi)部實現(xiàn))。所以我們想要一個更通用的解決方案,不考慮內(nèi)部結(jié)構(gòu)。

方案三:使用目標(biāo)進(jìn)程的內(nèi)存映射

這種方法與***種方法非常相似,因為我們希望在目標(biāo)進(jìn)程的上下文中執(zhí)行代碼。但在這里,我們會用自己的線程中執(zhí)行代碼,同時使用目標(biāo)進(jìn)程的“內(nèi)存上下文”,這意味著:我們會使用其地址空間。

通過幾個API可以在內(nèi)核態(tài)下更改地址空間,我們使用了use_mm。如文檔明確指出的那樣,“此例程僅用于從內(nèi)核線程上下文中調(diào)用”。這些是在內(nèi)核中創(chuàng)建的線程,不需要任何用戶地址空間,因此可以更改其地址空間(地址空間內(nèi)的內(nèi)核區(qū)域在每個任務(wù)中以相同的方式映射)。

在內(nèi)核線程中運(yùn)行代碼有一種簡單方法,就是內(nèi)核的工作隊列接口,它允許你使用特定例程和特定參數(shù)來安排工作。我們的例程獲取所需進(jìn)程的內(nèi)存映射對象和mprotect的參數(shù),并執(zhí)行以下操作(do_mprotect_pkey是內(nèi)核中實現(xiàn)mprotect和pkey_mprotect系統(tǒng)調(diào)用的內(nèi)部函數(shù)):

 

當(dāng)我們的內(nèi)核模塊在某個進(jìn)程(通過一個特殊的IOCTL)獲得更改保護(hù)的請求時,它首先找到所需的內(nèi)存映射對象,然后使用正確的參數(shù)來調(diào)度工作。這個方案仍有一個小問題:函數(shù)do_mprotect_pkey_func不由內(nèi)核導(dǎo)出,需要使用kallsyms獲取。與前一個解決方案不同,這個內(nèi)部函數(shù)不太容易發(fā)生變化,因為它與系統(tǒng)調(diào)用pkey_mprotect有關(guān),而且我們無需處理內(nèi)部結(jié)構(gòu)。

如果你有興趣,可以在github中找到這個PoC內(nèi)核模塊的源代碼。

責(zé)任編輯:武曉燕 來源: FreeBuf
相關(guān)推薦

2018-12-03 11:05:20

內(nèi)存代碼Linux

2019-12-16 10:43:38

Linux內(nèi)存消耗進(jìn)程

2019-11-06 15:58:54

Linux內(nèi)存消耗進(jìn)程

2018-07-27 05:08:58

2021-08-30 06:59:07

x86處理器內(nèi)存

2023-03-05 16:40:07

linux進(jìn)程內(nèi)存

2023-01-30 14:27:14

Linux進(jìn)程

2018-05-31 11:58:06

Linux進(jìn)程Early OOM

2010-08-05 16:08:12

輕松掌握DB2 9.5

2015-10-22 13:07:29

USB設(shè)備權(quán)限Linux

2023-01-27 09:17:02

操作系統(tǒng)虛擬化內(nèi)存

2018-06-05 08:51:04

Linux結(jié)束進(jìn)程中止程序

2019-12-16 09:10:38

Linux中央處理器進(jìn)程

2019-12-16 11:00:04

LinuxCPU進(jìn)程

2022-05-27 11:59:22

Linux內(nèi)存CPU

2021-07-16 08:00:00

開發(fā)VSCode編輯器

2013-06-20 10:25:56

2018-10-08 08:20:25

Linux進(jìn)程端口號

2009-12-09 10:07:19

Linux靜態(tài)路由

2011-01-26 13:26:32

Linux進(jìn)程
點贊
收藏

51CTO技術(shù)棧公眾號