인디안 보호구역

[FTZ] 메모리 레이아웃 기초 본문

Study/보안 공부하는 인디안

[FTZ] 메모리 레이아웃 기초

Indie-An 2016. 5. 27. 22:16

본 게시물은 위키북스의 '문제풀이로 배우는 시스템 해킹 테크닉 - 해커스쿨 FTZ를 활용한 단계별 해킹 수련법'을 참조하여 만들어졌습니다.

더 자세한 내용은 책을 구입하여 공부하시길 바랍니다.



01 메모리 구조의 이해


버퍼 오버플로우 공격과리버싱 같은 시스템 해킹 기법을 이해하려면 메모리의 레이아웃을 반드시 이해해야 한다.

먼저, 우리가 작성하는 소스코드가 어떻게 메모리에 배치되는지 살펴보자, 아래와 같은 코드가 있다고 해보자.



(예제1. 소스코드의 메모리 구성을 확인하기 위한 예제)



아래의 표는 예제(hello.c)의 내용이 메모리에 어떻게 배치되는가를 보여준다. 실제 소스코드는 표와 같이 메모리에 올라가고, CPU가 이 내용을 읽어 CPU 안에 있는 레지스터로 불러와서 처리한 후 실행된 결과가 우리에게 보여진다.



  물리주소

 메모리 세그먼트

 저장 데이터

 실제 데이터

 0x000000

 Text (=Code)

 실행명령

#include<stdio.h>

int main()

{

  printf("%s\n", string);

  return retVal;

}

 .

 Data

 전역, const, static 변수, 초기화된 변수

 int retVal = 0;

 static int output = 1;

 .

 Bss

 초기화되지 않은 변수

 int outVal;

 .

 Heap(↓)

 동적 메모리

 ptr = (char *)malloc(sizeof(string));

 .

 

 힙과 스택의 여분 공간

 변수가 늘어날 경우 힙과 스택의 시작점 사이에  있는 이 공간에 할당

 0xffffff

 Stack(↑)

 지역변수

 char string[] = "hello";

 char *ptr;

(표1. 메모리 레이아웃)



Windows OS나 Linux OS도 위에 기술된 동일한 처리 과정을 통해 우리에게 보여진다.

표를 보면 스택은 지역변수가 쌓일수록 메모리의 낮은 주소 방향으로 데이터가 쌓이는 특이한 구조라는 점을 기억해 둬야 한다. 이는 힙과 스택의 여분 공간에 해당하는 메모리 영역을 최대한 효율적으로 사용하기 위함이다.


예제(hello.c)를 컴파일해서 실행하면 "hello"라는 문자열이 출력된다.





02 메모리 레이아웃을 이해하기 위한 구조 분석


우리는 항상 C 코드를 작성한 후 실행 결과를 보기 때문에 실제 데이터가 처리될 때의 구조도 우리가 작성한 C 코드와 똑같을 것이라는 착각에 빠지지만 현실은 그렇지 않다. 우리가 작성한 코드는 C 언어의 문법만 준수하면 되지만 실행 파일은 해당 실행 파일이 실행될 플랫폼의 환경에 맞춰 최적화된 형태로 재구성된다. 이 두 과정을 프로그램 언어로 비교하자면 전자는 C 언어가 될 테고 후자는 어셈블리 언어가 될 것이다.


아래의 코드를 통해 우리가 착각하고 있을 법한 개념을 먼저 설명하겠다.



(예제2. 인자가 전달되는 순서를 확인하기 위한 예제)



코드를 보면 main() 함수가 실행되면서 function() 함수에 3개의 인자를 전달하고, 함수의 실행이 끝나면 main() 함수가 종료된다.

여기서 main() 함수에서 function() 함수에 전달하는 인자의 순서는 어떻게 될까? 이때 일반적인 사람이라면 코드에 나온 대로

a=1 → b=2 → c=3의 순서로 전달되리라 생각할 것이다. 하지만 실제로 프로그램을 디버깅해보면 생각한 것과 반대되는 순서로 스택에 변수값이 쌓이는 것을 볼 수 있다.


그럼 실제로 파일을 디버깅해서 이를 확인해 보자. 먼저 파일을 디버깅하려면 실행 파일이 있어야 하므로 아래와 같이 소스코드를 컴파일해서 실행 파일을 만든다.


structure.c 파일은 main()과 function()의 두 개의 함수만 들어있는 간단한 구조다. 여기서 함수의 인자 전달 순서를 확인하기에는 function() 함수가 적당하므로 function() 함수를 디스어셈블해 보면 소스코드가 메모리에 어떻게 배치되는지 직접 확인할 수 있다.





여기서 function() 함수를 호출하는 부분은 회색으로 표시했다. 디스어셈블하면서 표시되는 나머지 부분에 대해서는 이후에 다루고, 인자의 전달 순서에 해당하는 부분만 뽑아서 설명하겠다. 우선 앞에서 회색으로 표시된 부분만 살펴보면, 우리의 추측과는 달리 function() 함수에 제공한 인자값이 c=3 → b=2 → a=1의 순서로 전달되는 것을 볼 수 있다. 이처럼 사소하고 단순해 보이는 인자값의 스택 배치 순서가장 기본적이고도 핵심적인 원리이므로 이를 반드시 기억해야 한다.


Comments