如何快速讀取Mac進程的內存映射

2021-02-14 嘶吼專業版

首先需要2個變量的地址:ruby_version(用來布局結構)和ruby_current_thread(用來進行堆棧跟蹤)。

由於它們就在我正在查看的Ruby二進位文件的符號表中,所以通常獲取這兩個變量的地址並不難,但是由於ASLR的存在,二進位文件會隨機加載到內存中。所以我需要做到以下幾點:

1. 在符號表中找到ruby_version的地址;

2. 找出Ruby二進位文件在內存中加載的位置(來自進程的內存映射);

3. 減去__mh_execute_header符號的值。

在Linux上,你可以通過查看/ proc / PID / maps文件來獲得內存映射,它們的組成方式是這樣的:地址範圍,權限(例如r-xp),大小, inode編號以及一個映射到該文件的文件名。

00400000-00401000 r-xp 00000000 00:14 13644        /usr/bin/ruby1.9.1

00600000-00601000 r--p 00000000 00:14 13644        /usr/bin/ruby1.9.1

00601000-00602000 rw-p 00001000 00:14 13644        /usr/bin/ruby1.9.1

0060b000-00887000 rw-p 00000000 00:00 0              [heap]

7f1d44648000-7f1d4464a000 r-xp 00000000 00:14 14411  /usr/lib/ruby/1.9.1/x86_64-linux/enc/trans/transdb.so

7f1d4464a000-7f1d4484a000 ---p 00002000 00:14 14411  /usr/lib/ruby/1.9.1/x86_64-linux/enc/trans/transdb.so

7f1d4484a000-7f1d4484b000 r--p 00002000 00:14 14411  /usr/lib/ruby/1.9.1/x86_64-linux/enc/trans/transdb.so

7f1d4484b000-7f1d4484c000 rw-p 00003000 00:14 14411  /usr/lib/ruby/1.9.1/x86_64-linux/enc/trans/transdb.so

而在所有版本的Mac上,雖然內存映射的結構基本上是相同的,但是獲取它們會相當困難!起初我試圖在Mac上使用適用於Linux的方法(就是上文提到的/ proc文件),但花了3天時間都沒有得到。為此,我進行了以下2種嘗試:

嘗試1——使用vmmap

VMMap是一個免費的工具,可以用來分析應用程式使用虛擬和物理內存的情況。除了內存使用圖形來表示,VMMap也顯示摘要信息和詳細進程的內存映射。強大的過濾和刷新功能可以確定進程的內存使用情況和應用功能內存成本的來源。

不過,由於某種原因vmmap速度很慢,獲取一個進程的內存映射需要2秒,這可能是因為vmmap在獲取內存映射之前暫停了這個過程。

我不想直接使用vmma的原因,除了不滿意vmmap的速度以外,另外一個原因就是如果vmmap真的是在獲取內存映射之前暫停了這個過程,那這就意味著內存映射的分析過程被幹擾了。

嘗試2——在Rust中重新使用vmmap

Rust是針對多核體系提出的語言,並且吸收一些其他動態語言的重要特性,比如不需要管理內存,比如不會出現Null指針等等。由於不想直接使用vmmap,所以我考慮在Rust中重新實現它的功能(至少是我需要的部分)。我在Rust中寫了一個簡單的vmmap複製功能,代碼就在main.rs。

為了做到這一點,我使用了mach crate,它具有Rust綁定功能,可以調用一些Mac內核函數。我還了解到,在Mac / BSD上有一個「埠(port)」的概念:

『埠』是一個受保護的消息隊列,用於在任務之間進行通信,任務擁有發送權限並接收每個埠的權限

以下是如何從一個程序獲得一個內存映射的方法,不過這個函數的接口有一點奇怪,就是你給它一個埠ID和一個地址,它會給你該地址之後的第一個內存映射。基本上這個函數只是將mach_vm_region函數從Mach微內核中封裝起來(所有Mach函數的頭文件都在/usr/include/mach/*.h中)。

我已經注釋了一些代碼,它使用https://github.com/andrewdavidmackenzie/libproc-rs  crate作為regionfilename函數(它給出了與內存映射關聯的庫的文件名)。我就不得不在github master上使用該版本的crate,因為其發布的版本有一個Use-After-Free(UAF)漏洞。

fn mach_vm_region(target_task: mach_port_name_t, mut address: mach_vm_address_t) -> Option<Region> {

    let mut count = mem::size_of::<vm_region_basic_info_data_64_t>() as mach_msg_type_number_t;

    let mut object_name: mach_port_t = 0;

    // we need to create new `size` and `info` structs for the function we call to read the data

    // into

    let mut size = unsafe { mem::zeroed::<mach_vm_size_t>() };

    let mut info = unsafe { mem::zeroed::<vm_region_basic_info_data_t>() };

    let result = unsafe {

        // Call the underlying Mach function

        mach::vm::mach_vm_region(

            target_task as vm_task_entry_t,

            &mut address,

            &mut size,

            VM_REGION_BASIC_INFO,

            &mut info as *mut vm_region_basic_info_data_t as vm_region_info_t,

            &mut count,

            &mut object_name,

        )

    };

    if result != KERN_SUCCESS {

        return None;

    }

    // this uses 

    let filename = match regionfilename(41000, address) {

        Ok(x) => Some(x),

        _ => None,

    };

    Some(Region {

        size: size,

        info: info,

        address: address,

        count: count,

        filename: filename,

    })

}

以上是我編寫的Rust程序,比vmmap要快很多!整個內存映射的時間要在80毫秒左右完成,比vmmap快大約15倍。

不過到現在為止,我的Rust vmmap複製還存在一個主要問題,就是它實際上只是給我一些現在我的進程中的內存映射。而對於任何動態連結庫(包括一個Ruby庫以及我需要的地址和文件名),它們被存儲在一個叫做「dyld_shared_cache」的地方。

在下面的連結中有一堆關於這個「dyld」的代碼,我打算試著利用一下。

用於從Mac進程讀取內存映射的有用資源

以下是我在Mac上閱讀內存映射時發現的4個最有用的資源:

1.來自OS X內部的vmmap.c原始碼;

2.「使用Mach-O二進位文件和dyld」,用於在Mac進程中查找共享庫的地址;

3.來自Chromium的dynamic_images.cc,用它讀取Mac進程中的信息共享庫;

4.psutil的OS X C代碼,ppsutil是一個跨平臺庫,能夠輕鬆實現獲取系統運行的進程和系統利用率(CPU,內存,磁碟,網絡等)信息,主要應用於系統監控,分析和限制系統資源及進程的管理,它實現了同等命令行工具提供的功能,所以它的原始碼有助於了解Mac內部的結構

相關焦點

  • 一條進程的棧區、堆區、數據區和代碼區在內存中的映射
    注意:1)堆向高內存地址生長;2)棧向低內存地址生長;3)堆和棧相向而生,堆和棧之間有個臨界點,稱為stkbrk。1、一條進程在內存中的映射假設現在有一個程序,它的函數調用順序如下:main(...)當一個程序被作業系統調入內存運行,其對應的進程在內存中的映射如下圖所示:注意:l隨著函數調用層數的增加,函數棧幀是一塊塊地向內存低地址方向延伸的;l隨著進程中函數調用層數的減少(即各函數調用的返回),棧幀會一塊塊地被遺棄而向內存的高址方向回縮;l各函數的棧幀大小隨著函數的性質的不同而不等
  • 系統內存/進程內存知識掃盲
    共享內存:就是多個進程間共同地使用同一段物理內存空間,它是通過將同一段物理內存映射到不同進程的虛擬空間來實現的,是進程間通信中最簡單的方式之一。應用程式可用內存/系統物理內存>80%時,表示系統內存資源非常充足,不影響系統性能,應用程式可用內存/系統物理內存<20%時,表示系統內存資源緊缺,需要增加系統內存,20%<應用程式可用內存/系統物理內存<80%時,表示系統內存資源基本能滿足應用需求,暫時不影響系統性能。在知道了系統如果存在內存資源瓶頸後,如何進一步定位到底是哪個進程導致的內存問題呢?
  • IO埠映射和IO內存映射(詳解S3C24XX_GPIO驅動)
    物理地址中很大一部分是留給內存條中的內存的,但也常被映射到其他存儲器上(如顯存、BIOS等)。在程序指令中的虛擬地址經過段映射和頁面映射後,就生成了物理地址,這個物理地址被放到CPU的地址線上。 Linux中,進程的4GB(虛擬)內存分為用戶空間、內核空間。用戶空間分布為0~3GB(即PAGE_OFFSET,在0X86中它等於0xC0),剩下的1G為內核空間。程式設計師只能使用虛擬地址。系統中每個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其他進程是不可見的。
  • ARM體系結構下面內存和i/o映射區別
    port);· 讀寫一串字節void insb(unsigned port, void *addr, unsigned long count);void outsb(unsigned port, void *addr, unsigned long count);· insb()從埠port開始讀count個字節埠,並將讀取結果寫入
  • 系統軟體工程師必備技能-進程內存的working set size(WSS)測量...
    有經驗的讀者可能會注意到Linux top命令的輸出中,有兩列分別是VIRT與RES,其中VIRT是進程使用的虛擬內存地址空間大小,而RES則是實際使用的物理內存(如果考慮共享映射等,則需要用到smaps proc文件的PSS或者top命令的SHARE,不過這兩者對於理解WSS無益,故不在此展開)。
  • 內存初始化代碼分析(三):創建系統內存地址映射
    而廣大的系統內存區域仍然在黑暗之中,等待我們去拯救(進行地址映射)。最後,我們思考這樣一個問題:是否memory type類型的數組代表了整個的系統內存的地址空間呢?當然不是,有些驅動可能會保留一段系統內存區域為自己使用,同時也不希望OS管理這段內存(或者說對OS不可見),而是自己創建該段內存的地址映射。
  • 通過洩漏KVA SHADOW映射破壞Windows KASLR(下)
    Windows中的PML4隨機分配如前所述,PML4表是64位內存模型中的最高分頁級別,在Windows中,僅通過使用最低的0x100條目(256)來映射用戶內存,而使用最高的0x100條目來映射內核內存,此表用於分隔用戶和內核內存
  • Android 內存管理詳解
    三、內存的申請與回收四、內存限制五、不同App切換時的內存管理Android Runtime(ART)和Dalvik虛擬機使用 分頁 和 內存映射 來管理內存。這意味著應用程式修改的任何內存(無論是通過分配新對象通過映射頁面)都將保留在RAM中,並且不能被分頁。應用程式釋放內存的唯一方法是釋放應用程式持有的對象引用,即使垃圾收集器回收(GC)回收內存 。比如:如果系統想要在其他地方使用該內存,則可以將任何未經修改的映射到mmap中文件(例如代碼)分頁出RAM。本頁面介紹了Android如何管理應用程式進程和內存分配。
  • 淺析JVM內存模型:虛擬機如何實現多線程而導致的並發問題
    下面我們將從JVM內存模型的角度來分析虛擬機如何實現多線程、多線程之間由於共享和競爭數據而導致的並發問題及解 決思路。一般CPU都會從內存取數據到 寄存器,然後進行處理,但由於內存的處理速度遠遠低於CPU,導致CPU在處理指令時往往花費很多時間在等待內 存做準備工作,於是在寄存器和主內存間添加了CPU緩存,CPU緩存比較小,但訪問速度比主內存快得多,用它來 作為內存與處理器之間的緩衝:將運算需要使用到的數據複製到緩存中,讓運算能快速進行,當運算結束後再從緩 存同步到內存之中,這樣處理器就不用等待緩慢的內存讀寫了
  • 獨家|Linux進程內存用量分析之堆內存篇
    本文將介紹幾種內存洩漏檢測工具,並通過實際例子介紹一種分析堆內存佔用量的工具和方法,幫助定位內存膨脹問題。背景進程的內存管理是每一個開發者必須要考慮的問題,對於C++程序進程來說,出現問題很多情況下都與內存掛鈎。進程崩潰問題通常可以使用gdb等調試工具輕鬆排查並解決。
  • 實例講解基於Volatility的內存分析技術Part 1
    它是通過尋找已分配內存的段(通過查看VAD樹數據結構),並檢查它們是否有未映射到磁碟上任何文件的可執行代碼的線索來實現這一點的。「VAD節點還引用了一些其他的內核結構體,這些結構體可能對調查人員非常有用……因此,如果該內存區被用於已映射的文件(如加載的DLL),則可以引用相應的_FILE_OBJECT結構體,並提取文件的名稱。
  • 科技:如何在Mac上打開任務管理器
    導語:如何在Mac上打開任務管理器。MacOS具有與Windows任務管理器類似的功能,使用Windows的人知道打開任務管理器的用處,它顯示正在運行的進程,那些可以忽略的進程,那些在RAM上狼吞虎咽的進程,那些對網絡訪問過多的進程,可以終止幾個進程或啟動新的進程。
  • AweCleaner for Mac(mac清理系統工具) v4.8激活版
    07 來源: 有脾氣的小女子 舉報   AweCleaner for Mac是一款強大的mac
  • 陳延偉:任督二脈之內存管理總結筆記
    那麼MMU是如何實現物理地址到虛擬地址的映射的呢,請看下圖:對於一個虛擬地址,0x12345670,地址的高20位表示頁表的物理地址,也就是0x12345(對應圖中的p),通過這個地址找到頁表所在位置,讀取該位置中的數據,這個數據指向物理地址的索引。
  • MacPilot for Mac(系統優化軟體)
    MacPilot for mac是Mac os平臺上的一款非常不錯的Mac系統優化軟體,MacPilot for mac是套完整的Mac工具,有簡單易用的界面,運行腳本作為一個先進的系統優化,使隱藏的選項和功能,並清除您的電腦歷史記錄。
  • 一文探討堆外內存的監控與回收
    復現問題為了復現線上的問題,我們使用一個程序,不斷開啟線程使用堆內內存作為緩衝區進行文件的讀取操作,並監控該進程的堆外內存使用情況。總結一下就是:堆外內存的回收繼續深究一下一個話題,也是我的微信交流群中曾經有人提出過的一個疑問,到底該如何回收 DirectByteBuffer?既然可以監控堆外內存,那驗證堆外內存的回收就變得很容易實現了。
  • 內存:你跑慢點行不行?CPU:跑慢點你養我嗎?內存:我不管!
    那麼我們可以總結下基址寄存器:存儲數據內存的起始位置變址寄存器:存儲應用程式的長度。每當進程引用內存以獲取指令或讀取、寫入數據時,CPU 都會自動將基址值添加到進程生成的地址中,然後再將其發送到內存總線上。
  • 嵌入式Linux內存管理的一些知識點總結
    例子解析:一個變量定義在一個進程裡面,在程序中使用&操作符獲取的地址(估計是偏移地址),將怎麼映射呢?(或者它是什麼地址,在進程中的位置如何)1. 回答:因為,程序使用的都是虛擬地址,所以程式設計師拿到的變量地址是虛擬地址(它是經過編譯器處理過的,並由系統指映射線性地址,分配物理內存的)。2.
  • 郝健: Linux內存管理學習筆記-第2節課
    malloc、vmalloc與kmalloc的唯一區別是malloc、vmalloc申請內存後需要修改頁表,而kmalloc申請內存時由於已經做了開機線性映射,所以不需要修改頁表。寄存器是通過ioremap向vmalloc映射區去映射的,一旦調用ioremap,Linux就從vmalloc映射區找一個空閒的虛擬地址空間,然後去修改進程的頁表,把這個虛擬地址往這個寄存器的物理地址去指。
  • 全面解讀作業系統中的內存管理,你懂幾點?
    什麼是虛擬內存? 虛擬內存如何映射到物理內存 什麼是分頁內存管理? 什麼是缺頁中斷? 最簡單的辦法就是把每個進程的地址空間分別映射到物理內存的不同部分。這樣就可以保證不同進程使用的是獨立的地址空間。