Reversing

0201 메모리 구조, 스택, 스택 프레임

오호츠크해 기단 2023. 3. 28. 23:22
728x90

문서화 과제

1. 메모리 구조

위 사진은 컴퓨터의 기본 구조를 나타낸 사진이다. 메모리 구조를 알아보기 전 간단하게 짚고 넘어갈 것이다.
컴퓨터는 CPU(Processor) + Memory(메모리) + HDD(하드디스크)로 구성되어있다. PE 포맷으로 구성된 실행파일을 클릭하면 운영체제에 있던 로더가 PE 헤더에 있는 정보를 분석해 PE 바디에 있는 코드와 데이터를 메모리에 배치하면서 프로그램이 실행된다.
 
*PE 포맷(Portable Executable)
윈도우 운영 체제에서 사용되는 실행 파일, DLL, object 코드, FON 폰트 파일 등을 위한 파일 형식

프로그램이 실행되면 로더에 의해서 디스크에서 주기억장치인 메모리(RAM: Random Access Memory)로 프로그램을 가져온다. 프로그램은 메모리 상에서 크게 5가지 영역으로 나뉘어 스택 형태로 구현된다.
 
1. Code 영역
: 프로그램의 코드가 저장되어 있는 영역
 
2. Data 영역
: 전역변수 global, 정적변수 static, 배열 array, 구조체 structure 등이 들어가는 영역
초기화 된 데이터는 이곳에 저장된다.
 
3. BSS(Block Stated Symbol) 영역
: 초기화 되지 않은 데이터가 저장되는 영역
 
4. Heap 영역
: 동적 메모리 할당에 사용되는 영역
가변 크기로 위에서부터 채워진다.
 
4. Stack 영역
: 함수를 호출할 때 사용되는 매개 변수, 지역 변수가 저장되는 영역
여러 개의 스택 프레임이 존재하며, 밑에서부터 채워진다.
 
*HEAP과 STACK은 서로 반대로 채워 나가기 때문에 서로의 영역을 침범할 수 있고, 이를 악용해 공격도 가능하다.

  • HEAP Overflow: HEAP이 STACK 영역을 침범하는 경우
  • STACK Overflow: STACK이 HEAP 영역을 침범하는 경우

 

2. 스택

메모리의 한 부분으로 먼저 들어가서 마지막에 나오는 FILO/LIFO(First In Last Out/Last In First Out) 구조의 메모리 공간이다.

  1. 함수 내의 로컬 변수 임시 저장
  2. 함수 호출 시 파라미터(인자) 전달
  3. 리턴 주소(return address) 저장
  • 스택은 "POP"과 "PUSH"의 동작만을 지원한다.

   -  POP : 데이터를 스택에서 꺼내는 동작을 하는 명령어
   -  PUSH : 데이터를 스택에 집어넣는 명령어

  • 스택은 한 방향으로만 데이터가 쌓인다.

PUSH 명령을 통해 데이터가 stack에 추가되면 ESP 레지스터(스택 포인터)는 스택의 top (낮은 주소)을 향해 움직이고,  POP 명령을 통해 데이터가 stack에서 제거되면 ESP 레지스터는 스택의 bottom(높은 주소)를 향해 움직인다. PUSH를하면  스택 주가 4바이트(32bit)만큼 감소하면서 스택으로 데이터가 들어가고, POP을하면 반대로 주소가 4바이트만큼 증가하면서 스택에서 데이터가 나온다.
이것을 총에 비유하면 이해하기가 쉽다.

탄창에 총알을 넣으면 아래 방향으로만 총알이 쌓인다. >> 스택은 한 방향으로만 데이터가 쌓임.
총알을 넣으면(PUSH) 탄의 높이가 ncm만큼 감소하면서 총알이 탄창에 들어간다. >> PUSH
총알을 꺼내면(POP) 탄의 높이가 ncm만큼 증가하면서 총알이 나온다. >> POP

이와 같이 스택의 진행방향이 설계된 이유는 스택 구조 위에 있는 데이터가 시스템을 운영하는 데 있어서 중요한 역할을 하기 때문이다.
 
 
*ESP : 스택의 위치는 스택포인터인 ESP에 저장되어 있다. PUSH, POP시 ESP의 값이 4바이트 만큼씩 커졌다 작아졌다하는 것을 확인 할 수 있다.
 
 

3. 스택 프레임

: 서브루틴(함수)이 가지는 자신만의 스택 영역
쉽게 말하자면 어떤 함수가 호출 되었을 때 그 함수가 가지는 공간 구조라고 보면 된다. 서브 루틴 내에서 사용하는 데이터(파라미터, 지역 변수 등)가 저장된다.


함수가 호출될 때(서브루틴이 시작될 때) 스택 프레임이 생성되고, 함수가 동작을 종료하고 종료 주소로 돌아갈 때 스택 프레임은 소멸한다.
 
서브루틴을 호출할 경우
1. 필요 인자들을 먼저 스택에 입력.
2. 서브루틴 실행 직전 윈도우(OS)는 다시 돌아올 복귀 주소를 스택에 입력.
3. 스택 프레임 시작.
4. 먼저 이전 루틴이 사용했던 EBP 레지스터 내용을 백업.
5. EBP가 백업된 스택 주소를 서브루틴의 EBP에 다시 집어 넣는다.
6. 이렇게 설정된 EBP는 스텍프레임에서 데이터 참조를 위한 기준 주소인 프레임 포인터로 사용됨.
 
서브루틴을 호출할 때 필요한 인자들을 스택에 입력하고, 실행 바로 직전에 운영체제에서는 리턴 주소(복귀 주소)를 스택에 집어넣는다.
 
*EBP(base pointer)
: 레지스터(프레임 포인터)를 사용해 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근한다.
EBP는 스택 프레임의 시작점을 가리킨다. ESP는 프로그램 실행 중 계속 그 값이 달라지기 때문에 EBP에 함수의 시작(스택프레임의 시작점)의 ESP 값을 EBP에 저장하고 이를 기준으로 하여 해당 함수의 변수, 파라미터, 리턴 주소에 접근할 수 있도록 한다.
 
[스택 프레임의 어셈블리 코드 구조]

PUSH EBP
MOV EBP, ESP
......
......
......
MOV ESP, EBP
POP EBP
RETN

PUSH EBP:  함수 시작 (EBP 사용하기 전에 초기 값을 스택에 저장)
MOV EBP, ESP:  현재의 ESP를 EBP에 저장
 
···    :  새로운 함수의 내용
 
MOV ESP, EBP:  ESP를 함수 시작했을 때의 값으로 복원
POP EBP :  리턴되기 전에  저장해놨던 원래 EBP 값으로 복원
RETN:  함수 종료