본문 바로가기
hacking/pwnable

[Dream hack] Background: Linux Memory Layout

by ilp 2024. 4. 26.
반응형

강의 내용

  1. 서론
  2. 세그먼트
  3. 코드 세그먼트
  4. 데이터 세그먼트
  5. BSS 세그먼트
  6. 스택 세그먼트
  7. 힙 세그먼트

서론

  • 메모리 오염(Memory Corruption)
    • cpu의 동작과 메모리 사이에 밀접한 연관이 있음을 의미한다.
      만약 공격자가 메모리를  조작할 수 있다면 조작된 메모리 값에 의해 CPU도 잘못된 동작을 할 수 있다.
    • Stack Buffer Overlow, Format String Bug, Use After Free, Double Free Bug
      를 배우고 리눅스 메모리 구조(Memory Layout)에 대해 배운다.

세그먼트

  • 세그먼트(Segment)
    • 리눅스에서 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분한다.
    • 적재되는 데이터의 용도별로 메모리의 구획은 나눈 것,
      (코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 그리고 스텍 세그먼트)
  • 메모리를 용도별로 나누었을때 장점
    • 각 용도에 맞게 적절한 권한을 부여할 수 있다.
    • 읽기, 쓰기, 실행 이있다. CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있다.

  • 예시
    • 데이터 세그먼트에는 프로그램이 실행되면서 사용하는 데이터가 적재된다.
    • CPU는 이곳의 데이터를 읽을 수 있어야 하며, 그래서 읽기 권한이 부여된다. 단 실행 대상이 아니끼 떄문에 실행 권한은 부여되지 않는다.

코드 세그먼트

  • 코드 세그먼트(Code Segment)
    • 실행 가능한 기계 코드가 위치하는 영역 (텍스트 세그먼트(Text Segment)라고도 불린다.)
    • 프로그램이 동작하기 위해 코드를 실행할 수 있어야 하기 떄문에, 읽기, 쓰기 권한이 부여된다.
      반면에 쓰기 권한이 있으면 공격자가 악성코드를 삽입하기 쉽기 때문에 , 쓰기 권한을 제거한다.
int main() { return 31337; }
//정수 31337을 반환하는 main함수가 컴파일 되면 554889e5b8697a00005dc3라는 기계코드로 변환된다.
//이 기계 코드가 코드 세그먼트에 위치한다.

데이터 세그먼트 

  • 데이터 세그먼트(Data Segment)
    • 커파일 시점에 값이 정해진 전여 변수 및 전역 상수들이 위치한다.
    • CPU가 데이터를 읽을 수 있어야 하므로, 읽기 권한이 부여된다.
  • data 세그먼트
    • 쓰기가 가능한 세그먼트
    • 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치한다.
  • rodata(read-only-data) 세그먼트
    • 쓰기가 불가능한 세그먼트
    • 값이 변하면 안되는 데이터들, 전역으로 선언된 상수
int data_num = 31337;                       // data
char data_rwstr[] = "writable_data";        // data
const char data_rostr[] = "readonly_data";  // rodata
char *str_ptr = "readonly";  // str_ptr은 data, 문자열은 rodata
// str_ptr은 전역변수, 문자열은 상수 문자열로 취급됨

int main() { ... }

BSS 세그먼트

  • BSS 세그먼트(BSS Segment, Block Started By Symbol Segment)
    • 컴파일 시점에 값이 정해지지 않은 전역 변수가 위차한다.
      (선언만 하고 초기화 하지 않은 전역변수가 포함)
    • 프로그램이 시작될때, 모두 0으로 초기화 된다.
      (c코드를 작성할 떄, 초기화 되지 않은 전역변수값은 0이된다.)
    • 읽기, 쓰기 권한이 부여된다.
int bss_data; // BSS 세그먼트에 위치

int main() {
  printf("%d\n", bss_data);  // 0
  return 0;
}

 


스택 세그먼트

  • 스택 세그먼트(Stack Segment)
    • 프로세스의 스택이 위치하는 영역이다.
    • 함수의 인자지역변수와 같은 임시 변수들이 실행중에 저장된다.
  • 스택 프레임(Stack Frame)
    • 함수가 호출될때 생성되고, 반환될떄 해제된다.
    • 프로그래므의 전체 실행 흐름은 사용자의 입력을 비롯한 여러 요인에 영향을 받는다.

👇아래 코드에서 유저가 입력한 choice에 따라 call_true()가 호출될 수도, call_false()가 호출될 수도 있다.

void func() {
  int choice = 0;

  scanf("%d", &choice);

  if (choice)
    call_true();
  else
    call_false();

  return 0;
}
// 지역변수 choice가 스태겡 저장되게 됩니다.
  • 추가설명
    • 프로세스가 얼마 만큼의 스택 프레임을 사용하게 될지를 미리 계산하는 것은 일반적으로 불가능하다.
      따라서 운영체제는 작은 크기의 스택 세그먼트를 먼저 할당해주고, 부족해 질 떄마다 이를 확장해준다.
    • 스택에서 '아래로 자란다'란다 라는 표현을 사용하느데, 스텍이 확장될 때, 기존 주소보다 낮은 주소로 확장되기 떄문이다.
    • CPU가 자유롭게 값을 일고 쓸 수 있어야 하므로, 읽기와 쓰기 권한이 부여된다.

힙 세그먼트

  • 힙 세그먼트(Heap Segment)
    • 힙 데이터가 위치하는 세그먼트 이다.
      (실행 중에 동적으로 할당될 수 있다., 스택 세그먼트와 반대 방향으로 자란다.)
    • malloc(), calloc()등을 호출해서 할당받는 메모리가 위치한다.(읽기와 쓰기 권한이 부여된다.)

👇heap_data_ptr에 malloc()으로 동적 할당한 영역의 주소를 대입하고, 이 영역에 값을 쓴다.

int main() {
  int *heap_data_ptr =
      malloc(sizeof(*heap_data_ptr));  // 동적 할당한 힙 영역의 주소를 가리킴
  *heap_data_ptr = 31337;              // 힙 영역에 값을 씀
  printf("%d\n", *heap_data_ptr);  // 힙 영역의 값을 사용함
  return 0;
}
// heap_data_ptr은 지역변수이므로 스택에 위치하며, malloc으로 할당받은 힙 세그먼트의 주소를 가리킨다.

💡힙과 스택 세그먼트가 자라는 방향이 반대인 이유?

  • 두 세그먼트가 동일한 방향으로 자라며, 연속된 메모리 주소에 각각 할당된다고 가정해본다. 이 경우, 기존에 힙 세그먼트를 모두 사용하면, 이를 확장하는 과정에서 스택 세그먼트와 충돌하게 된다.
  • 이를 쉽게 해결하기 위해 리눅스는 스택을 메모리의 끝에 위치시키고, 힙과 스택을 반대로 자라게한다. 이렇게 하면 힙과 스택은 메모리를 최대한 자유롭게 사용할 수 있으며, 충돌 문제로 부터도 비교적 자유롭게 된다.

결론

세그먼트 역할 일반적인 권한 사용예
코드 세그먼트 실행 가능한 코드가 저장된 영역 읽기, 실행 main()등의 함수 코드
데이터 세그먼트 초기화된 전역 변수 또는 상수가 위치하는 영역 일릭, 쓰기 또는 읽기 초기화된 전역 변수, 전역 상수
BSS 세그먼트 초기화 되지 않은 데이터 위차하는 영역 읽기, 쓰기 최기화 되지 않은 전역변수
스택 세그먼트  임시 변수가 저장되는 영역 읽기, 쓰기 지역변수, 함수의 인자
힙 세그먼트 실행중에 동적으로 사용되는 영역 읽기, 쓰기  malloc(), calloc()등으로 할당 받은 메모리

반응형