<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    堆內存管理

    在堆溢出章節我們的glibc版本為2.23,這是ubuntu 16.04的默認版本,也是pwn題最常用的。

    堆是用于存放除了棧里的東西之外所有其他東西的內存區域,有動態內存分配器負責維護。分配器將堆視為一組不同大小的塊(block)的集合來維護,每個塊就是一個連續的虛擬內存器片(chunk)。當使用 malloc()free() 時就是在操作堆中的內存。對于堆來說,釋放工作由程序員控制,容易產生內存泄露。

    堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

    如果每次申請內存時都直接使用系統調用,會嚴重影響程序的性能。通常情況下,運行庫先向操作系統“批發”一塊較大的堆空間,然后“零售”給程序使用。當全部“售完”之后或者剩余空間不能滿足程序的需求時,再根據情況向操作系統“進貨”。

    需要注意的是,在內存分配與使用的過程中,Linux 有這樣的一個基本內存管理思想,只有當真正訪問一個地址的時候,系統才會建立虛擬頁面與物理頁面的映射關系。 所以雖然操作系統已經給程序分配了很大的一塊內存,但是這塊內存其實只是虛擬內存。只有當用戶使用到相應的內存時,系統才會真正分配物理頁面給用戶使用。

    malloc

    首先看源碼說明

    /*
      malloc(size_t n)
      Returns a pointer to a newly allocated chunk of at least n bytes, or null
      if no space is available. Additionally, on failure, errno is
      set to ENOMEM on ANSI C systems.
      If n is zero, malloc returns a minumum-sized chunk. (The minimum
      size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit
      systems.)  On most systems, size_t is an unsigned type, so calls
      with negative arguments are interpreted as requests for huge amounts
      of space, which will often fail. The maximum supported value of n
      differs across systems, but is in all cases less than the maximum
      representable value of a size_t.
    */

    總結一下就是malloc返回對應大小字節的指針,當n==0時,返回當前系統允許的最小內存塊。當n為負數時,系統并不會認為是負數,所以就會申請很大的內存塊。但通常來說系統并沒有這么多的空閑內存,所以絕大多數情況會失敗。

    free

    看一下源碼

    /*
          free(void* p)
          Releases the chunk of memory pointed to by p, that had been previously
          allocated using malloc or a related routine such as realloc.
          It has no effect if p is null. It can have arbitrary (i.e., bad!)
          effects if p has already been freed.
          Unless disabled (using mallopt), freeing very large spaces will
          when possible, automatically trigger operations that give
          back unused memory to the system, thus reducing program footprint.
        */

    free會釋放由指針p所指向的內存塊,同樣該函數也會有一些異常處理。當p指針為空時,函數不執行任何操作,當已經被釋放過一次后,再次釋放就會有一些玄學的現象,這就是double free。除了被禁用 (mallopt) 的情況下,當釋放很大的內存空間時,程序會將這些內存空間還給系統,以便于減小程序所使用的內存空間。

    進程堆管理

    Linux 提供了兩種堆空間分配的方式,一個是 brk() 系統調用,另一個是 mmap() 系統調用。可以使用 man brkman mmap 查看。

    brk() 的聲明如下:

    #include <unistd.h>
    
    int brk(void *addr);
    
    void *sbrk(intptr_t increment);

    參數 *addr 是進程數據段的結束地址,brk() 通過改變該地址來改變數據段的大小,當結束地址向高地址移動,進程內存空間增大,當結束地址向低地址移動,進程內存空間減小。brk()調用成功時返回 0,失敗時返回 -1。 sbrk()brk() 類似,但是參數 increment 表示增量,即增加或減少的空間大小,調用成功時返回增加后減小前數據段的結束地址,失敗時返回 -1。

    在上圖中我們看到 brk 指示堆結束地址,start_brk 指示堆開始地址。BSS segment 和 heap 之間有一段 Random brk offset,這是由于 ASLR 的作用,如果關閉了 ASLR,則 Random brk offset 為 0,堆結束地址和數據段開始地址重合。

    例子:源碼

    #include <stdio.h>
    #include <unistd.h>
    void main() {
            void *curr_brk, *tmp_brk, *pre_brk;
    
            printf("當前進程 PID:%d\n", getpid());
    
            tmp_brk = curr_brk = sbrk(0);
            printf("初始化后的結束地址:%p\n", curr_brk);
            getchar();
    
            brk(curr_brk+4096);
            curr_brk = sbrk(0);
            printf("brk 之后的結束地址:%p\n", curr_brk);
            getchar();
    
            pre_brk = sbrk(4096);
            curr_brk = sbrk(0);
            printf("sbrk 返回值(即之前的結束地址):%p\n", pre_brk);
            printf("sbrk 之后的結束地址:%p\n", curr_brk);
            getchar();
    
            brk(tmp_brk);
            curr_brk = sbrk(0);
            printf("恢復到初始化時的結束地址:%p\n", curr_brk);
            getchar();
    }

    開啟兩個終端,一個用于執行程序,另一個用于觀察內存地址。首先我們看關閉了 ASLR 的情況。第一步初始化:

    # echo 0 > /proc/sys/kernel/randomize_va_space
    $ ./a.out
    當前進程 PID27759
    初始化后的結束地址:0x56579000
    # cat /proc/27759/maps
    ...
    56557000-56558000 rw-p 00001000 08:01 28587506                           /home/a.out
    56558000-56579000 rw-p 00000000 00:00 0                                  [heap]
    ...

    數據段結束地址和堆開始地址同為 0x56558000,堆結束地址為 0x56579000

    第二步使用 brk() 增加堆空間:

    $ ./a.out
    當前進程 PID27759
    初始化后的結束地址:0x56579000
    
    brk 之后的結束地址:0x5657a000
    # cat /proc/27759/maps
    ...
    56557000-56558000 rw-p 00001000 08:01 28587506                           /home/a.out
    56558000-5657a000 rw-p 00000000 00:00 0                                  [heap]
    ...

    堆開始地址不變,結束地址增加為 0x5657a000

    第三步使用 sbrk() 增加堆空間:

    $ ./a.out
    當前進程 PID27759
    初始化后的結束地址:0x56579000
    
    brk 之后的結束地址:0x5657a000
    
    sbrk 返回值(即之前的結束地址):0x5657a000
    sbrk 之后的結束地址:0x5657b000
    # cat /proc/27759/maps
    ...
    56557000-56558000 rw-p 00001000 08:01 28587506                           /home/a.out
    56558000-5657b000 rw-p 00000000 00:00 0                                  [heap]
    ...

    第四步減小堆空間:

    $ ./a.out
    當前進程 PID27759
    初始化后的結束地址:0x56579000
    
    brk 之后的結束地址:0x5657a000
    
    sbrk 返回值(即之前的結束地址):0x5657a000
    sbrk 之后的結束地址:0x5657b000
    
    恢復到初始化時的結束地址:0x56579000
    # cat /proc/27759/maps
    ...
    56557000-56558000 rw-p 00001000 08:01 28587506                           /home/a.out
    56558000-56579000 rw-p 00000000 00:00 0                                  [heap]
    ...

    再來看一下開啟了 ASLR 的情況:

    # echo 2 > /proc/sys/kernel/randomize_va_space
    $ ./a.out
    當前進程 PID28025
    初始化后的結束地址:0x578ad000
    # cat /proc/28025/maps
    ...
    5663f000-56640000 rw-p 00001000 08:01 28587506                           /home/a.out
    5788c000-578ad000 rw-p 00000000 00:00 0                                  [heap]
    ...

    可以看到這時數據段的結束地址 0x56640000 不等于堆的開始地址 0x5788c000

    mmap() 的聲明如下:

    #include <sys/mman.h>
    
    void *mmap(void *addr, size_t len, int prot, int flags,
        int fildes, off_t off);

    mmap() 函數用于創建新的虛擬內存區域,并將對象映射到這些區域中,當它不將地址空間映射到某個文件時,我們稱這塊空間為匿名(Anonymous)空間,匿名空間可以用來作為堆空間。mmap() 函數要求內核創建一個從地址 addr 開始的新虛擬內存區域,并將文件描述符 fildes 指定的對象的一個連續的片(chunk)映射到這個新區域。連續的對象片大小為 len 字節,從距文件開始處偏移量為 off 字節的地方開始。prot 描述虛擬內存區域的訪問權限位,flags 描述被映射對象類型的位組成。

    munmap() 則用于刪除虛擬內存區域:

    #include <sys/mman.h>
    
    int munmap(void *addr, size_t len);

    例子:源碼

    #include <stdio.h>
    #include <sys/mman.h>
    #include <unistd.h>
    void main() {
        void *curr_brk;
    
        printf("當前進程 PID:%d\n", getpid());
        printf("初始化后\n");
        getchar();
    
        char *addr;
        addr = mmap(NULL, (size_t)4096, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        printf("mmap 完成\n");
        getchar();
    
        munmap(addr, (size_t)4096);
        printf("munmap 完成\n");
        getchar();
    }

    第一步初始化:

    $ ./a.out
    當前進程 PID28652
    初始化后
    # cat /proc/28652/maps
    ...
    f76b2000-f76b5000 rw-p 00000000 00:00 0
    f76ef000-f76f1000 rw-p 00000000 00:00 0
    ...

    第二步 mmap:

    ]$ ./a.out
    當前進程 PID28652
    初始化后
    mmap 完成
    # cat /proc/28652/maps
    ...
    f76b2000-f76b5000 rw-p 00000000 00:00 0
    f76ee000-f76f1000 rw-p 00000000 00:00 0
    ...

    第三步 munmap:

    $ ./a.out
    當前進程 PID28652
    初始化后
    mmap 完成
    munmap 完成
    # cat /proc/28652/maps
    ...
    f76b2000-f76b5000 rw-p 00000000 00:00 0
    f76ef000-f76f1000 rw-p 00000000 00:00 0
    ...

    可以看到第二行第一列地址從 f76ef000->f76ee000->f76ef000 變化。0xf76ee000-0xf76ef000=0x1000=4096

    通常情況下,我們不會直接使用 brk()mmap() 來分配堆空間,C 標準庫提供了一個叫做 malloc 的分配器,程序通過調用 malloc() 函數來從堆中分配塊,聲明如下:

    #include <stdlib.h>
    
    void *malloc(size_t size);
    void free(void *ptr);
    void *calloc(size_t nmemb, size_t size);
    void *realloc(void *ptr, size_t size);

    示例:

    #include<stdio.h>
    #include<malloc.h>
    void foo(int n) {
        int *p;
        p = (int *)malloc(n * sizeof(int));
    
        for (int i=0; i<n; i++) {
            p[i] = i;
            printf("%d ", p[i]);
        }
        printf("\n");
    
        free(p);
    }
    
    void main() {
        int n;
        scanf("%d", &n);
    
        foo(n);
    }

    運行結果:

    $ ./malloc
    4
    0 1 2 3
    $ ./malloc
    8
    0 1 2 3 4 5 6 7
    $ ./malloc
    16
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

    使用 gdb 查看反匯編代碼:

    gdb-peda$ disassemble foo
    Dump of assembler code for function foo:
       0x0000066d <+0>:     push   ebp
       0x0000066e <+1>:     mov    ebp,esp
       0x00000670 <+3>:     push   ebx
       0x00000671 <+4>:     sub    esp,0x14
       0x00000674 <+7>:     call   0x570 <__x86.get_pc_thunk.bx>
       0x00000679 <+12>:    add    ebx,0x1987
       0x0000067f <+18>:    mov    eax,DWORD PTR [ebp+0x8]
       0x00000682 <+21>:    shl    eax,0x2
       0x00000685 <+24>:    sub    esp,0xc
       0x00000688 <+27>:    push   eax
       0x00000689 <+28>:    call   0x4e0 <malloc@plt>
       0x0000068e <+33>:    add    esp,0x10
       0x00000691 <+36>:    mov    DWORD PTR [ebp-0xc],eax
       0x00000694 <+39>:    mov    DWORD PTR [ebp-0x10],0x0
       0x0000069b <+46>:    jmp    0x6d9 <foo+108>
       0x0000069d <+48>:    mov    eax,DWORD PTR [ebp-0x10]
       0x000006a0 <+51>:    lea    edx,[eax*4+0x0]
       0x000006a7 <+58>:    mov    eax,DWORD PTR [ebp-0xc]
       0x000006aa <+61>:    add    edx,eax
       0x000006ac <+63>:    mov    eax,DWORD PTR [ebp-0x10]
       0x000006af <+66>:    mov    DWORD PTR [edx],eax
       0x000006b1 <+68>:    mov    eax,DWORD PTR [ebp-0x10]
       0x000006b4 <+71>:    lea    edx,[eax*4+0x0]
       0x000006bb <+78>:    mov    eax,DWORD PTR [ebp-0xc]
       0x000006be <+81>:    add    eax,edx
       0x000006c0 <+83>:    mov    eax,DWORD PTR [eax]
       0x000006c2 <+85>:    sub    esp,0x8
       0x000006c5 <+88>:    push   eax
       0x000006c6 <+89>:    lea    eax,[ebx-0x17e0]
       0x000006cc <+95>:    push   eax
       0x000006cd <+96>:    call   0x4b0 <printf@plt>
       0x000006d2 <+101>:   add    esp,0x10
       0x000006d5 <+104>:   add    DWORD PTR [ebp-0x10],0x1
       0x000006d9 <+108>:   mov    eax,DWORD PTR [ebp-0x10]
       0x000006dc <+111>:   cmp    eax,DWORD PTR [ebp+0x8]
       0x000006df <+114>:   jl     0x69d <foo+48>
       0x000006e1 <+116>:   sub    esp,0xc
       0x000006e4 <+119>:   push   0xa
       0x000006e6 <+121>:   call   0x500 <putchar@plt>
       0x000006eb <+126>:   add    esp,0x10
       0x000006ee <+129>:   sub    esp,0xc
       0x000006f1 <+132>:   push   DWORD PTR [ebp-0xc]
       0x000006f4 <+135>:   call   0x4c0 <free@plt>
       0x000006f9 <+140>:   add    esp,0x10
       0x000006fc <+143>:   nop
       0x000006fd <+144>:   mov    ebx,DWORD PTR [ebp-0x4]
       0x00000700 <+147>:   leave  
       0x00000701 <+148>:   ret
    End of assembler dump.

    參考

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类