自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何在任意進(jìn)程中修改內(nèi)存保護(hù)屬性

存儲 存儲軟件
最近,我們在進(jìn)行一項安全研究時,需要在任意進(jìn)程中修改內(nèi)存空間的保護(hù)標(biāo)志。在解決這些問題的過程中,我們學(xué)到了一些新的東西,主要是關(guān)于Linux機(jī)制和內(nèi)核開發(fā)的。本文將介紹我們所采取的三種方法以及每次尋求更好解決方案的原因。
最近,我們在進(jìn)行一項安全研究時,需要在任意進(jìn)程中修改內(nèi)存空間的保護(hù)標(biāo)志。起初,我們發(fā)現(xiàn)這項任務(wù)看起來很簡單,但在實際操作中,卻發(fā)現(xiàn)困難重重,還好這些都不是什么大問題。在解決這些問題的過程中,我們還學(xué)到了一些新的東西,主要是關(guān)于Linux機(jī)制和內(nèi)核開發(fā)的。在以下的詳解中,我們會介紹我們所采取的三種方法以及每次尋求更好解決方案的原因。
 
背景介紹
 
在現(xiàn)代操作系統(tǒng)中,每個進(jìn)程都有自己的虛擬地址空間(從虛擬地址到物理地址的映射)。此虛擬地址空間由內(nèi)存頁面(某些固定大小的連續(xù)內(nèi)存塊)組成,且每個頁面都有保護(hù)標(biāo)志,這些保護(hù)標(biāo)志決定了允許對該頁面的訪問類型(讀取、寫入和執(zhí)行)。不過,這種機(jī)制依賴于架構(gòu)頁表(architecture page table)。不過要注意的是,在x64的架構(gòu)中,你不能只進(jìn)行頁面寫入,即使你是特意從操作系統(tǒng)請求的,也都同時具有頁面寫入和可讀的功能。
 
在Windows中,你可以使用API函數(shù)VirtualProtect或VirtualProtectEx修改內(nèi)存空間的保護(hù)。VirtualProtectEx使我們的修改任務(wù)變得非常簡單:因為它的***個參數(shù)hProcess是“要修改其內(nèi)存保護(hù)的進(jìn)程的句柄”。
 
不過,在Linux中,修改過程就沒有這么簡單了,因為修改內(nèi)存保護(hù)的API是系統(tǒng)調(diào)用mprotect或pkey_mprotect的結(jié)果,并且這兩個函數(shù)始終在當(dāng)前進(jìn)程的地址空間上運行?,F(xiàn)在讓我們想辦法解決一下如何在x64架構(gòu)上的Linux中解決修改的問題,不過前提條件是,我們具有修改設(shè)備的root權(quán)限。

[[251304]]

 
方法一:代碼注入
 
如果mprotect總是在當(dāng)前進(jìn)程中運行,我們需要讓目標(biāo)進(jìn)程從它自己的上下文中調(diào)用它。這時就要用到代碼注入了,該方法可以通過許多不同的方式實現(xiàn)。我們可以選擇使用ptrace機(jī)制實現(xiàn)它,該機(jī)制允許一個進(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)存空間(通過檢查/ proc / PID / maps),并在這個空間編寫操作碼syscall(十六進(jìn)制:0f05);
 
3.根據(jù)調(diào)用約定來修改寄存器,首先,將rax修改為mprotect的系統(tǒng)調(diào)用號(即10);然后,前三個參數(shù)(即起始地址、長度和所需的保護(hù))分別存儲在rdi、rsi和rdx中;***,將rip修改為步驟2中使用的地址;
 
4.繼續(xù)這個過程,直到系統(tǒng)調(diào)用返回(ptrace允許你跟蹤系統(tǒng)調(diào)用的進(jìn)入和退出);
 
5.恢復(fù)被修改的內(nèi)存和寄存器,從進(jìn)程中將其分離并恢復(fù)正常執(zhí)行;
 
這種方法是我們的采用的***個也是最直觀的方法,并且非常有效。不過在我們發(fā)現(xiàn)了Linux中的另一種完全破壞機(jī)制:利用seccomp進(jìn)行破壞之后,該方法就不是我們的***選擇了?;旧?,它是Linux內(nèi)核中的一個安全工具,允許進(jìn)程輸入某種形式的“監(jiān)獄”,除了read,write,_exit和sigreturn之外,它不能進(jìn)行任何系統(tǒng)調(diào)用。還有一個選項,可以指定任意的系統(tǒng)調(diào)用及針對它們的過濾參數(shù)。
 
因此,如果進(jìn)程啟用了seccomp模式并且我們嘗試將一個對mprotect的調(diào)用注入其中,那么內(nèi)核將終止進(jìn)程,因為該進(jìn)程是不允許使用此系統(tǒng)調(diào)用的。因此,要對這些進(jìn)程進(jìn)行調(diào)用,就要采用方法二。
 
方法二:在內(nèi)核模塊中模擬mprotect系統(tǒng)調(diào)用
 
seccomp(全稱securecomputing mode)是linuxkernel從2.6.23版本開始所支持的一種安全機(jī)制。
 
在Linux系統(tǒng)里,大量的系統(tǒng)調(diào)用直接暴露給用戶態(tài)程序。但是,并不是所有的系統(tǒng)調(diào)用都被需要,而且不安全的代碼濫用系統(tǒng)調(diào)用會對系統(tǒng)造成安全威脅。通過seccomp,我們限制程序使用某些系統(tǒng)調(diào)用,這樣可以減少系統(tǒng)的暴露面,同時是程序進(jìn)入一種“安全”的狀態(tài)。
 
由于Linux中存在另一種完全破壞機(jī)制:利用seccomp進(jìn)行破壞,因此這個方法肯定要在內(nèi)核模式中進(jìn)行。在Linux內(nèi)核中,每個線程(包括用戶線程和內(nèi)核線程)都由一個名為task_struct的結(jié)構(gòu)表示,并且當(dāng)前線程(任務(wù))可以通過pointer current訪問。內(nèi)核中mprotect的內(nèi)部實現(xiàn)使用了pointer current,因此我們的***個想法是,只要將mprotect的代碼復(fù)制粘貼到內(nèi)核模塊中,并將每次出現(xiàn)的current替換為指向目標(biāo)線程task_struct的指針,不就可以了嗎?
 
接下來的事情你可能已經(jīng)猜到了,就是復(fù)制C代碼,不過復(fù)制過程并不是你想的那么簡單,因為其中存在大量使用我們無法訪問的未導(dǎo)出的函數(shù)、變量和宏。某些函數(shù)說明會在標(biāo)頭文件中導(dǎo)出,但是它們的實際地址不是由內(nèi)核導(dǎo)出的。如果內(nèi)核是用linux內(nèi)核符號表kallsyms編譯的,那么通過文件/ proc / kallsysm導(dǎo)出所有內(nèi)部符號,這個特定的問題就可以解決。因為kallsyms在進(jìn)行源碼調(diào)試時具有相當(dāng)重要的作用,它可以描述所有不處在堆棧上的內(nèi)核符號。linux內(nèi)核在編譯的過程中,將內(nèi)核中所有的符號(所有的內(nèi)核函數(shù)以及已經(jīng)裝載的模塊)及符號的地址以及符號的類型信息都保存在了/proc/kallsyms文件中。
 
盡管存在這個特定問題,我們?nèi)匀辉噲D實現(xiàn)mprotect調(diào)用。為此,我們特意編寫一個內(nèi)核模塊,利用該模塊獲取目標(biāo)PID和參數(shù)以進(jìn)行mprotect,并模仿其調(diào)用行為。首先,我們需要獲取所需的內(nèi)存映射對象,用它表示線程的地址空間:
  1. /* Find the task by the pid */ 
  2.     pid_struct = find_get_pid(params.pid); 
  3.     if (!pid_struct) 
  4.         return -ESRCH; 
  5.  
  6.     task = get_pid_task(pid_struct, PIDTYPE_PID); 
  7.     if (!task) { 
  8.         ret = -ESRCH; 
  9.         goto out
  10.     } 
  11.  
  12.     /* Get the mm of the task */ 
  13.     mm = get_task_mm(task); 
  14.     if (!mm) { 
  15.         ret = -ESRCH; 
  16.         goto out
  17.     } 
  18.  
  19.     … 
  20.     … 
  21.  
  22. out
  23.     if (mm) mmput(mm); 
  24.     if (task) put_task_struct(task); 
  25.     if (pid_struct) put_pid(pid_struct); 
現(xiàn)在我們已經(jīng)獲得了內(nèi)存映射對象,這大大方便了以后的操作。 Linux內(nèi)核實現(xiàn)了一個抽象層來管理內(nèi)存空間,每個空間由結(jié)構(gòu)vm_area_struct表示。為了找到正確的內(nèi)存空間,我們使用函數(shù)find_vma,該函數(shù)會根據(jù)所需地址搜索內(nèi)存映射。
 
vm_area_struct包含字段vm_flags,它以獨立于架構(gòu)的方式來表示內(nèi)存空間的保護(hù)標(biāo)志,vm_page_prot也以獨立于架構(gòu)的方式來表示內(nèi)存空間的保護(hù)標(biāo)志。單獨修改這些字段并不會真正影響頁表(但會影響/proc/PID/maps的輸出,我們已經(jīng)嘗試過了),詳情請點擊這里。
 
在對內(nèi)核代碼進(jìn)行了一些閱讀和深入研究之后,我們發(fā)現(xiàn)要真正攻破內(nèi)存空間的保護(hù),最重要的工作是以下3方面:
 
1.將字段vm_flags修改為所需的保護(hù);
 
2.調(diào)用函數(shù)vma_set_page_prot_func,再根據(jù)vm_flags字段來更新字段vm_page_prot;
 
3. 調(diào)用change_protection_func函數(shù)來實際修改頁表中的保護(hù)位;
 
雖然以上的那段代碼很有效,但其中也存在著很多問題。首先,我們只實現(xiàn)了mprotect的基本部分,但原始函數(shù)的基本功能卻比我們能開發(fā)的要多得多,例如,通過保護(hù)標(biāo)志分離和連接內(nèi)存空間。其次,我們使用了兩個內(nèi)核函數(shù)(vma_set_page_prot_func和change_protection_func),這些函數(shù)不是由內(nèi)核導(dǎo)出的。此時,我們可以使用kallsyms來調(diào)用它們,但是這很容易出現(xiàn)問題,因為將來我們可能會修改它們的名稱,或者將內(nèi)存空間的整個內(nèi)部實現(xiàn)進(jìn)行修改。不過,我們想要一個更通用的解決方案,即不考慮內(nèi)部結(jié)構(gòu)的方案,此時,就有了方法三。

方法三:使用目標(biāo)進(jìn)程的內(nèi)存映射
 
方法三與***種方法非常相似,即都要目標(biāo)進(jìn)程的上下文中執(zhí)行代碼。雖然,這兩個方法都可以在我們自己的線程中執(zhí)行代碼,但在方法三中,我們使用的是目標(biāo)進(jìn)程的“內(nèi)存上下文”,這意味著,我們要使用內(nèi)存中的地址空間。
 
我們通過幾個API函數(shù)就可以在內(nèi)核模式下修改地址空間,其中就用到了use_mm。正如use_mm的介紹中明確指出的那樣“此例程僅會被用于從內(nèi)核線程上下文中進(jìn)行調(diào)用”。由于這些線程是在內(nèi)核中創(chuàng)建的,不需要任何用戶地址空間,因此可以修改它們的地址空間(地址空間內(nèi)的內(nèi)核區(qū)域在每個任務(wù)中都以相同的方式映射)。
 
在內(nèi)核線程中運行代碼的一種簡單方法就是通過內(nèi)核的運行隊列接口(queue interface),它允許你使用特定例程和特定參數(shù)來進(jìn)行進(jìn)程調(diào)用。我們的工作例程也非常簡單,它會獲取所需進(jìn)程的內(nèi)存映射對象和mprotect的參數(shù),并執(zhí)行以下操作(do_mprotect_pkey是內(nèi)核中實現(xiàn)mprotect和pkey_mprotect系統(tǒng)調(diào)用的內(nèi)部函數(shù)):
  1. use_mm(suprotect_work->mm); 
  2. suprotect_work->ret_value = do_mprotect_pkey(suprotect_work->start, 
  3.                                              suprotect_work->len, 
  4.                                              suprotect_work->prot, -1); 
  5. unuse_mm(suprotect_work->mm); 
當(dāng)我們的內(nèi)核模塊在某個進(jìn)程(通過一個特殊的IOCTL)獲得修改保護(hù)的請求時,該請求首先會找到所需的內(nèi)存映射對象(正如我們在前面的方法中所解釋的那樣),然后再使用正確的參數(shù)來調(diào)用進(jìn)程。
 
不過這個解決方案仍有一個小問題,即函數(shù)do_mprotect_pkey_func不會由內(nèi)核導(dǎo)出,需要使用kallsyms獲取。與***個解決方案不同,這個解決方案中的內(nèi)部函數(shù)不太容易被修改,因為該函數(shù)與系統(tǒng)調(diào)用pkey_mprotect有關(guān),而且我們也不用處理內(nèi)部結(jié)構(gòu),因此我們只能將其稱為“小問題”。
 
我們希望你在這篇文章中找到一些有趣的信息和技巧,學(xué)會如何在任意進(jìn)程中修改內(nèi)存保護(hù)屬性。如果你有興趣,可以在github中找到這個概念驗證內(nèi)核模塊的源代碼。
責(zé)任編輯:武曉燕 來源: 4hou
相關(guān)推薦

2019-01-03 16:04:13

內(nèi)存Linux PoC

2018-07-27 05:08:58

2019-12-16 10:43:38

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

2019-11-06 15:58:54

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

2010-08-05 16:08:12

輕松掌握DB2 9.5

2021-08-30 06:59:07

x86處理器內(nèi)存

2023-03-05 16:40:07

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

2023-01-27 09:17:02

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

2021-07-16 08:00:00

開發(fā)VSCode編輯器

2016-07-29 11:21:16

Ubuntulinux程序

2018-05-31 11:58:06

Linux進(jìn)程Early OOM

2023-01-30 14:27:14

Linux進(jìn)程

2023-01-30 00:05:02

操作系統(tǒng)虛擬化安全

2020-05-19 11:54:04

腳本語言Linux命令

2019-01-07 10:24:41

2018-08-23 10:50:08

Windows 10Windows隱私

2021-01-13 07:33:41

API數(shù)據(jù)安全

2015-10-22 13:07:29

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

2022-07-05 17:49:34

元宇宙安全隱私保護(hù)

2018-06-05 08:51:04

Linux結(jié)束進(jìn)程中止程序
點贊
收藏

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