進程的內(nèi)存映像

2018-02-24 15:41 更新

進程的內(nèi)存映像

前言

在閱讀《UNIX 環(huán)境高級編程》的第 14 章時,看到一個“打印不同類型的數(shù)據(jù)所存放的位置”的例子,它非常清晰地從程序內(nèi)部反應了“進程的內(nèi)存映像”,通過結(jié)合它與《Gcc 編譯的背后》《緩沖區(qū)溢出與注入分析》的相關(guān)內(nèi)容,可以更好地輔助理解相關(guān)的內(nèi)容。

進程內(nèi)存映像表

首先回顧一下《緩沖區(qū)溢出與注入》中提到的"進程內(nèi)存映像表",并把共享內(nèi)存的大概位置加入該表:

地址 內(nèi)核空間 描述
0xC0000000
(program flie) 程序名 execve 的第一個參數(shù)
(environment) 環(huán)境變量 execve 的第三個參數(shù),main 的第三個參數(shù)
(arguments) 參數(shù) execve 的第二個參數(shù),main 的形參
(stack) 棧 自動變量以及每次函數(shù)調(diào)用時所需保存的信息都
存放在此,包括函數(shù)返回地址、調(diào)用者的
環(huán)境信息等,函數(shù)的參數(shù),局部變量都存放在此
(shared memory) 共享內(nèi)存 共享內(nèi)存的大概位置
...
...
(heap) 堆 主要在這里進行動態(tài)存儲分配,比如 malloc,new 等。
...
.bss (uninitilized data) 沒有初始化的數(shù)據(jù)(全局變量哦)
.data (initilized global data) 已經(jīng)初始化的全局數(shù)據(jù)(全局變量)
.text (Executable Instructions) 通常是可執(zhí)行指令
0x08048000
0x00000000 ...

在程序內(nèi)部打印內(nèi)存分布信息

為了能夠反應上述內(nèi)存分布情況,這里在《UNIX 環(huán)境高級編程》的程序 14-11 的基礎上,添加了一個已經(jīng)初始化的全局變量(存放在已經(jīng)初始化的數(shù)據(jù)段內(nèi)),并打印了它以及 main 函數(shù)(處在代碼正文部分)的位置。

/**
 * showmemory.c -- print the position of different types of data in a program in the memory
 */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE 4000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE (SHM_R | SHM_W)    /* user read/write */

int init_global_variable = 5;    /* initialized global variable */
char array[ARRAY_SIZE];        /* uninitialized data = bss */

int main(void)
{
    int shmid;
    char *ptr, *shmptr;

    printf("main: the address of the main function is %x\n", main);
    printf("data: data segment is from %x\n", &init_global_variable);
    printf("bss: array[] from %x to %x\n", &array[0], &array[ARRAY_SIZE]);
    printf("stack: around %x\n", &shmid);   

    /* shmid is a local variable, which is stored in the stack, hence, you
     * can get the address of the stack via it*/

    if ( (ptr = malloc(MALLOC_SIZE)) == NULL) {
        printf("malloc error!\n");
        exit(-1);
    }
    printf("heap: malloced from %x to %x\n", ptr, ptr+MALLOC_SIZE);

    if ( (shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0) {
        printf("shmget error!\n");
        exit(-1);
    }

    if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1) {
        printf("shmat error!\n");
        exit(-1);
    }
    printf("shared memory: attached from %x to %x\n", shmptr, shmptr+SHM_SIZE);

    if (shmctl(shmid, IPC_RMID, 0) < 0) {
        printf("shmctl error!\n");
        exit(-1);
    }

    exit(0);
}

該程序的運行結(jié)果如下:

$  make showmemory
cc     showmemory.c   -o showmemory
$ ./showmemory
main: the address of the main function is 804846c
data: data segment is from 80498e8
bss: array[] from 8049920 to 804a8c0
stack: around bfe3e224
heap: malloced from 804b008 to 80636a8
shared memory: attached from b7da7000 to b7dbf6a0

上述運行結(jié)果反應了幾個重要部分數(shù)據(jù)的大概分布情況,比如 data 段(那個初始化過的全局變量就位于這里)、bss 段、stack、heap,以及 shared memory 和main(代碼段)的內(nèi)存分布情況。

在程序內(nèi)部獲取完整內(nèi)存分布信息

不過,這些結(jié)果還是沒有精確和完整地反應所有相關(guān)信息,如果要想在程序內(nèi)完整反應這些信息,結(jié)合《Gcc編譯的背后》,就不難想到,我們還可以通過擴展一些已經(jīng)鏈接到可執(zhí)行文件中的外部符號來獲取它們。這些外部符號全部定義在可執(zhí)行文件的符號表中,可以通過 nm/readelf -s/objdump -t 等查看到,例如:

$ nm showmemory
080497e4 d _DYNAMIC
080498b0 d _GLOBAL_OFFSET_TABLE_
080486c8 R _IO_stdin_used
         w _Jv_RegisterClasses
080497d4 d __CTOR_END__
080497d0 d __CTOR_LIST__
080497dc d __DTOR_END__
080497d8 d __DTOR_LIST__
080487cc r __FRAME_END__
080497e0 d __JCR_END__
080497e0 d __JCR_LIST__
080498ec A __bss_start
080498dc D __data_start
08048680 t __do_global_ctors_aux
08048414 t __do_global_dtors_aux
080498e0 D __dso_handle
         w __gmon_start__
0804867a T __i686.get_pc_thunk.bx
080497d0 d __init_array_end
080497d0 d __init_array_start
08048610 T __libc_csu_fini
08048620 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
080498ec A _edata
0804a8c0 A _end
080486a8 T _fini
080486c4 R _fp_hw
08048328 T _init
080483f0 T _start
08049920 B array
08049900 b completed.1
080498dc W data_start
         U exit@@GLIBC_2.0
08048444 t frame_dummy
080498e8 D init_global_variable
0804846c T main
         U malloc@@GLIBC_2.0
080498e4 d p.0
         U printf@@GLIBC_2.0
         U shmat@@GLIBC_2.0
         U shmctl@@GLIBC_2.2
         U shmget@@GLIBC_2.0

第三列的符號在我們的程序中被擴展以后就可以直接引用,這些符號基本上就已經(jīng)完整地覆蓋了相關(guān)的信息了,這樣就可以得到一個更完整的程序,從而完全反應上面提到的內(nèi)存分布表的信息。

/**
 * showmemory.c -- print the position of different types of data in a program in the memory
 */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE 4000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE (SHM_R | SHM_W)        /* user read/write */

                                        /* declare the address relative variables */
extern char _start, __data_start, __bss_start, etext, edata, end;
extern char **environ;

char array[ARRAY_SIZE];         /* uninitialized data = bss */

int main(int argc, char *argv[])
{
    int shmid;
    char *ptr, *shmptr;

    printf("===== memory map =====\n");
    printf(".text:\t0x%x->0x%x (_start, code text)\n", &_start, &etext);
    printf(".data:\t0x%x->0x%x (__data_start, initilized data)\n", &__data_start, &edata);
    printf(".bss: \t0x%x->0x%x (__bss_start, uninitilized data)\n", &__bss_start, &end);

    /* shmid is a local variable, which is stored in the stack, hence, you
     * can get the address of the stack via it*/

    if ( (ptr = malloc(MALLOC_SIZE)) == NULL) {
        printf("malloc error!\n");
        exit(-1);
    }

    printf("heap: \t0x%x->0x%x (address of the malloc space)\n", ptr, ptr+MALLOC_SIZE);

    if ( (shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0) {
        printf("shmget error!\n");
        exit(-1);
    }

    if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1) {
        printf("shmat error!\n");
        exit(-1);
    }
    printf("shm  :\t0x%x->0x%x (address of shared memory)\n", shmptr, shmptr+SHM_SIZE);

    if (shmctl(shmid, IPC_RMID, 0) < 0) {
        printf("shmctl error!\n");
        exit(-1);
    }

    printf("stack:\t <--0x%x--> (address of local variables)\n", &shmid);   
    printf("arg:  \t0x%x (address of arguments)\n", argv);
    printf("env:  \t0x%x (address of environment variables)\n", environ);

    exit(0);
}

運行結(jié)果:

$ make showmemory
$ ./showmemory
===== memory map =====
.text:    0x8048440->0x8048754 (_start, code text)
.data:    0x8049a3c->0x8049a48 (__data_start, initilized data)
.bss:     0x8049a48->0x804aa20 (__bss_start, uninitilized data)
heap:     0x804b008->0x80636a8 (address of the malloc space)
shm  :    0xb7db6000->0xb7dce6a0 (address of shared memory)
stack:     <--0xbff85b64--> (address of local variables)
arg:      0xbff85bf4 (address of arguments)
env:      0xbff85bfc (address of environment variables)

后記

上述程序完整地勾勒出了進程的內(nèi)存分布的各個重要部分,這樣就可以從程序內(nèi)部獲取跟程序相關(guān)的所有數(shù)據(jù)了,一個非常典型的例子是,在程序運行的過程中檢查代碼正文部分是否被惡意篡改。

如果想更深地理解相關(guān)內(nèi)容,那么可以試著利用 readelf,objdump 等來分析 ELF 可執(zhí)行文件格式的結(jié)構(gòu),并利用 gdb 來了解程序運行過程中的內(nèi)存變化情況。

參考資料

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號