Android漫遊記(1)---內存映射鏡像(memory maps)

    Android系統內核基於Linux2.6+內核,因此,其在進程內存管理方面的很多機制和Linux是很相像的。首先,讓我們來看一個典型的Android進程的內存鏡像(App進程和Native本地進程略有差別,但原理是一樣的):


和Linux一樣,Android提供了基於/proc的“僞文件”系統來作爲查看用戶進程內存映像的接口(cat /proc/pid/maps)。可以說,這是Android系統內核層開放給用戶層關於進程內存信息的一扇窗戶。通過它,我們可以查看到當前進程空間的內存映射情況,模塊加載情況以及虛擬地址和內存讀寫執行(rwxp)屬性等。

首先我們來解讀下上面的maps。


以libc.so爲例:

第一列:400dd000------40142000  ,可以看出這是內存中連續的地址空間,分成了3個子空間,分爲400dd000-40142000,40142000-40144000,40144000-40146000。你可能會問了,既然是加載libc.so,爲什麼要加載3次?好問題!

我們繼續看第二列:r-xp  r--p   rw-p。其中r表示只讀,w表示可寫,x表示可執行,p表示私有(s表示共享)。讓我們看一下libc.so的elf的程序頭和段說明部分內容(ELF爲類unix的可執行或共享鏡像的格式,類似於windows PE格式,後續漫遊系列詳細講解),我們可以通過Google提供的Android NDK Toolchains工具鏈的arm-linux-androideabi-readelf來讀取(arm-linux-androideabi-readelf  -a libc.so).



這塊我們不詳細解析,重點關注上圖中我標記的兩塊內容。LOAD表示該段需要加載到內存,Flg標誌表示該段在內存鏡像中的屬性,至此,可以基本回答上面的問題了。內核在加載libc.so的時候,參照ELF程序頭,來講段一一映射到內存。由於libc.so包含了代碼段、數據段等,因此按照不同的屬性映射到不同的位置。

如上面的400dd000-40142000包含了.text  .plt等Section,而40142000-40144000,40144000-40146000則包含了.data  .bss .got等Section。

總結一下:libc.so在被映射到內存的時候,內核是根據elf程序頭來一一映射“組裝的”,不同類型的段被映射到不同區域。


下面,我們來看一下Linux內核提供的內存映射API(mmap)。

       #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

參數:

addr  : 要映射的內存地址參考,內核按照這個地址來動態決定新分配的內存位置

length :大小(bytes)

prot :

       PROT_EXEC  Pages may be executed.//可執行

       PROT_READ  Pages may be read.//可讀

       PROT_WRITE Pages may be written.//可寫

       PROT_NONE  Pages may not be accessed.//不可訪問
flags:

       MAP_SHARED Share this mapping.  Updates to the mapping are visible to
                  other processes that map this file, and are carried
                  through to the underlying file.  The file may not actually
                  be updated until msync(2) or munmap() is called.

       MAP_PRIVATE
                  Create a private copy-on-write mapping.  Updates to the
                  mapping are not visible to other processes mapping the
                  same file, and are not carried through to the underlying
                  file.  It is unspecified whether changes made to the file
                  after the mmap() call are visible in the mapped region.
fd和offset表示要映射的文件句柄和初始偏移,如爲空,相當於分配一塊空的內存塊,mmap返回映射後的內存塊基址。實際上內核就是調用mmap一步一步把elf文件搬到內存的。

寫到這裏,我們開個小差,詳細研究下mmap的prot參數,如上面所說,該標誌表示將映射內存塊的讀寫以及執行屬性,而Linux除了初始映射的時候可以設置內存屬性以外,在加載到內存後,依然可以修改其屬性(需root權限),這就有點意思了,這意味着我們可以在進程執行的時候,動態修改其內存屬性(除了內核vsyscall區域),具體這能幹些啥,你懂的微笑

看API(mprotect

       #include <sys/mman.h>

       int mprotect(void *addr, size_t len, int prot);
參數:

addr:要修改的內存基址(必須頁面對齊,page size的倍數,一般爲4K對齊)

len:大小(bytes)

prot:修改後的rwx屬性。
看到這裏,那些所謂的遊戲”輔助、外掛“都笑了!

我們不妨寫一段代碼看看mproject和mmap能幹些啥!

/*
 *  mmap & mprotect call
 *  Created on: 2014-6
 *  Author: Chris.Z
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>

int main()
{
    void* membase = NULL;
    printf("[+]call mmap to alloc memory,size of 4k btyes\n");
    membase = mmap(NULL,0x1000,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,NULL,NULL);
    if(membase == MAP_FAILED)
    {
        printf("[+]mmap failed with errno:%d\n",errno);
        return 1;
    }
    printf("[+]allocated memory base:0x%x\n",membase);
    if(getchar())
    {
        printf("[+]modify the addr:0x%x prots to rwx\n",membase);
        mprotect(membase,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC);
    }
    if(getchar())
        free(membase);
    return 0;
}

上面的代碼段,我們讓內核在當前進程內存空間分配一片大小爲4K的匿名內存,初始屬性爲”只讀“,然後我們調用mprotect將其屬性改爲”rwx“。看運行結果:



可以看到我們分配後的內存塊的基址爲:0x401da000,查看進程maps,發現確實已分配屬性爲"r--p"的內存塊,結束地址爲0x401db000,大小爲0x1000,正好4K。



輸入回車後,我們看到的結果:



到這裏,我們知道了如何在當前進程動態的分配一塊頁面對齊的內存,同時修改其屬性,當然這裏演示的是對於當前進程的操作,如果要操作一個第三方的進程,還需要一些其他的玩意兒,比如進程附加、注入之類的操作,這個後面慢慢講!

好了,就寫到這裏,Enjoy IT!微笑

轉載請註明出處:生活秀



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章