Heap Arena

c0wb3ll ㅣ 2021. 1. 24. 22:27

Reference

https://rninche01.tistory.com/entry/heap2-glibc-malloc1-feat-Arena?category=838537

https://wogh8732.tistory.com/180

https://s0ngsari.tistory.com/entry/BlackHat-Draft-mallocstate-structure

https://www.lazenca.net/pages/viewpage.action?pageId=51970061

http://core-analyzer.sourceforge.net/index_files/Page335.html

Arena 란?

각각의 스레드가 서로 간섭하지 않고 서로 다른 메모리 영역에서 액세스 할 수 있도록 도와주는 힙 영역이다.

단일 스레드 프로세스인 경우 Arena가 한개를 가지게 되고 멀티 스레드 프로세스인 경우에는 여러개의 Arena를 가지며 서로 다른 Arena안에 존재하는 각각의 스레드는 정지하지 않고 힙 작업을 수행할 수 있다.

하지만 모든 스레드에 Arena를 할당해주면 자원 고갈이 심해질 것이므로 각각의 시스템과 코어의 개수에 따라 Arena의 개수가 정해져 있다.

  • 32bit 시스템인 경우 long type 크기가 4byte이기 때문에 코어 * 4 만큼의 Arena를 가짐
  • 64bit 시스템인 경우 long type 크기가 8byte이기 때문에 코어 * 8 만큼의 Arena를 가짐

Arena를 모두 사용하고 있어 더 이상 증가시킬 수 없다면 여러 스레드가 하나의 Arena에서 공유하며 힙 작업을 수행한다. 이 말은, 각각의 Arena안에서 여러개의 스레드가 존재할 수 있으며 뮤텍스를 사용하여 액세스를 제어한다.

  • 뮤텍스?
    • 뮤텍스는 공유자원에 접근하기 위한 방식인데 간단한 비유로 화장실이 하나밖에 없는 상황에 비유할 수 있다.
      • 화장실이 1개이며 화장실 열쇠를 가진 사람1이 사용하고 있다.
        • 공유자원을 오브젝트를 가진 프로세스 혹은 쓰레드가 사용하고 있다.
      • 사람1이 화장실을 나와 화장실 열쇠를 대기줄 맨 앞에 서있는 사람에게 건네준다.
        • 다음 프로세스 혹은 쓰레드가 공유자원에 접근한다.

정리하면 새로운 스레드가 생성되면 사용되지 않는 Arena를 찾아 할당하며, 더 이상 Arena를 할당할 수 없다면 여러 스레드가 하나의 Arena에서 작업을 수행하게 된다.

Arena 란? 요약

  • Arena는 멀티 스레드 환경을 지원하기 위해 도입된 개념
  • 멀티 스레드 프로세스인 경우 하나 이상의 Arena를 가짐
  • 서로 다른 Arena는 서로 간섭을 받지 않고 작업을 수행 가능
  • 자원 고갈을 방지하기 위해 시스템 환경과 코어 개수에 따라 Arena의 개수가 제한됨
  • 하나의 Arena에서 여러 스레드가 존재할 수 있으며 뮤텍스를 이용해 충돌 방지

Arena의 종류

Arena의 종류는 크게 Main ArenaMain Arena를 제외한 Arena로 나뉜다.

Main Arena

메인 쓰레드로서 생성되었기 때문에 Main Arena라고 부른다.

단일 스레드용 프로그램을 위해 존재하며 malloc()과 같은 힙 작업을 요구하는 코드를 실행하지 않아도 기본적으로 132KB의 크기의 Initial Heap을 가진다.

Main Arena가 감당할 수 있을 만큼의 동적 할당 요구가 들어오면 sbrk()를 통해 heap segment를 확장한다.

  • sbrk(), brk()?
    • brk()는 프로그램 break location을 확장시킴으로서 메모리를 확보한다.
      • brk()의 성공시 return값은 0, 실패시 return 값은 -1이된다.
    • sbkr()는 내부적으로 brk system call을 사용한다. 따라서 brk()와 마찬가지로 break location을 확장시킴으로 메모리를 확보한다.
      • brk()와의 차이점은 brk()의 return 값은 성공 시 0, 실패 시 -1 이었지만, sbkr()의 return 값은 성공 시 할당받은 메모리 영역의 끝 주소 값, 실패 시 -1 을 리턴한다.
    • malloc() 사용시 system library에서 sbrk()를 이용해 메모리를 넉넉히 받아온 다음 후에 malloc()이 들어올 때마다 미리 받아온 공간에서 잘라서 할당(이거시... Top Chunk가 아닐까...?)해준다.

너무 큰 사이즈의 동적 할당 요구가 들어오면 mmap()을 통해 새로운 힙 메모리를 할당해준다.

  • mmap()?
    • 새로운 메모리를 할당하고 호출한 프로세스에서 해당 메모리를 사용한다.

Main Arena는 하나의 힙만을 가질 수 있으며 heap_info 구조체를 가질 수 없다. 이 때, 하나의 힙은 여러 개의 chunk로 나누어지며 각 chunk는 각각의 header를 갖는다.

Main Arena를 제외한 Arena

새로운 스레드가 생성되어 힙 작업을 수행하고자 할 때 다른 스레드를 기다리는 것을 줄이기 위해 새로운 Arena를 생성하게 된다. sbrk()를 사용하는 Main Arena와는 다르게 mmap()을 이용해 새로운 힙 메모리를 할당받으며 mprotect()를 사용하여 확장한다.

또한, Main Arena를 제외한 Arena들은 Main_Arena와는 다르게 여러 개의 서브 힙과 heap_info 구조체를 가질 수 있다.

실습

환경

  • OS

    • Docker linux 16.04 (glibc-2.23)
  • Debugger

    • gef, Pwndbg(heap 분석용)
      • peda는 catch syscall brk, mmap에서 에러가 나서 못 사용했다... ㅠ
  • 소스코드 원본

    • https://rninche01.tistory.com/entry/heap2-glibc-malloc1-feat-Arena?category=838537

    • 실습코드

      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <pthread.h>
      
      #define whoami "c0wb3ll"
      
      void* thread_function(void* num){
        pid_t pid;
        pthread_t tid;
      
        pid = getpid();
        tid = pthread_self();
      
        int *p = (int*)malloc(sizeof(int)*1024);
        *p = whoami;
      
        printf("Thread #%d\n", (int*)num);
        printf("Thread data : %s\n", whoami);
      
        free(p);
      }
      
      int main(){
        int *p;
        int status;
        int t_id[3];
        pthread_t t[3];
      
        p = (int*)malloc(sizeof(int)*1024);
      
        *p = whoami;
      
        for(int i=0; i<3; i++) t_id[i] = pthread_create(&t[i], NULL, thread_function, (void*)i);
        for(int i=0; i<3; i++) pthread_join(t[i], (void**)&status);
      
        free(p);
        return 0;
      }
      https://user-images.githubusercontent.com/48816875/105630560-4ec70e80-5e8d-11eb-958e-ec9e0dea3bee.png
    • main함수의 시작부분에 bp를 걸어놓고 바로 힙부분을 파보자.

    • Main_Arena는 malloc() 등 힙 작업을 하지 않아도 존재한다.

    • Main_Arena를 살펴보면 next값이 Main_Arena를 가리키고 있는데 이는 아직 다른 Arena가 할당되지 않았기 때문이다.

      https://user-images.githubusercontent.com/48816875/105630612-88981500-5e8d-11eb-887f-805328241e3b.png
    • 사진을 보면 heap영역이 아직 할당되지 않았다는걸 알 수 있다.

    • 또한 Main_Arena는 libc-2.23.so영역 안에 포함되어 있는 것을 확인할 수 있다.

      https://user-images.githubusercontent.com/48816875/105630661-cc8b1a00-5e8d-11eb-9938-452961b8d4b3.png
    • 메인 쓰레드의 힙 영역은 brk를 사용하여 메모리를 확장하고 그렇지 않은 쓰레드는 mmap을 이용하여 확장하므로 두 함수에 bp를 걸어주자.

      https://user-images.githubusercontent.com/48816875/105630740-3a374600-5e8e-11eb-8c85-d4929f5d70da.png
    • 다음과 같이 brk()를 사용하여 메인 쓰레드의 heap영역을 할당하는 것을 확인할 수 있다.

      https://user-images.githubusercontent.com/48816875/105630916-014ba100-5e8f-11eb-9cb3-230aa240b515.png
    • Main_Arena를 보면 mem_size가 0x21000으로 확장된 것을 볼 수 있다.(top chunk 최대 크기?)

      https://user-images.githubusercontent.com/48816875/105631015-9058b900-5e8f-11eb-94d8-d4c4f635b56f.png
    • 그 후 또 쭉 진행하다보면 thread가 생성되고 힙 영역을 확장할 때 mmap()을 사용하여 확장하는 것을 확인할 수 있다.

      https://user-images.githubusercontent.com/48816875/105630990-5edfed80-5e8f-11eb-9490-5c601319f687.png
    • mmap bp를 쭉 하다보면 다음과 같이 쓰레드들이 사용되고 있는 것을 확인할 수 있다.

      https://user-images.githubusercontent.com/48816875/105631175-81bed180-5e90-11eb-8f18-4c075ca634ab.png
    • 스레드 별로 힙 영역(빨간 박스)과 스택 영역(노란 박스)이 할당된 것도 확인할 수 있다.

      https://user-images.githubusercontent.com/48816875/105631282-188b8e00-5e91-11eb-8c33-001f06c18b75.png
    • 각 스레드 별로 별개의 Arena를 가지고 있는 것도 확인할 수 있다.

    • Arena끼리 Linked List로 연결된 것을 확인할 수 있다.

      • Main_Arena부터 next값을 쭉 따라가면 다시 Main_Arena로 돌아옴
      https://user-images.githubusercontent.com/48816875/105631477-fcd4b780-5e91-11eb-9f30-f374881c44e8.png
    • Main_Arena에는 heap_info 구조체가 존재하지 않는다.

      https://user-images.githubusercontent.com/48816875/105631582-7c628680-5e92-11eb-94f8-cf3ffb7325e8.png
    • Main_Arena를 제외한 Arena들은 heap_info(빨간 박스)와 malloc_state(노란 박스) 구조를 가진다.

      https://user-images.githubusercontent.com/48816875/105631690-0874ae00-5e93-11eb-9903-188b2fbea276.png
    • main의 leave 부분에 bp를 걸고 실행한 결과이다. 모든 영역이 free되었음에도 남아있는 것을 확인할 수 있다.