혜랑's STORY

[2021 겨울 시스템 Pre : 달고나 문서 정리(1~3)] 본문

2021 SISS 21기 활동/겨울방학 System

[2021 겨울 시스템 Pre : 달고나 문서 정리(1~3)]

hyerang0125 2021. 1. 3. 20:44

#1 목적

 이 문서는 시스템에서 원하는 명령을 실행시키기 위해 사용되는 Buffer Overflow 공격에 대한 원리와 지식들을 설명한다. 즉, Buffer Overflow 공격이 어떻게 이루어지는지, 이러한 공격이 가능하게 되는 그 원리와 컴퓨터 시스템의 기본 구조에 대해서 설명하고 있다.

 

#2 8086 Memory Architecture

 

<그림 1. 8086 basic memory structure>

 

 8086 시스템의 기본적인 메모리 구조는 위 그림과 같다. 시스템이 초기화 되기 시작하면 시스템은 커널을 메모리에 적재시키고 가용 메모리 영역을 확인한다. 시스템은 운영에 필요한 기본적인 명령어 집합을 커널에서 찾기 때문에 커널 영역은 반드시 저 위치에 있어야 한다. 

32bit 시스템에서는 CPU가 한꺼번에  처리할 수 있는 데이터가 32bit 단위이므로, 메모리 영역에 주소를 할당할 수 있는 범위가 0 ~ 2^32-1 이다. 64bit 시스템의 CPU는 64bit씩 처리할 수 있으므로 0 ~ 2^64-1 범위를 가진다.

 

 하나의 프로세스 즉, 하나의 프로그램이 실행되기 위한 메모리의 구조는 다음과 같다. 

 

<그림 2. segmented memory model >

 

 운영체제는 하나의 프로세스를 실행시키면 이 프로세스를 segment라는 단위로 묶어서 가용 메모리 영역에 저장시킨다. 위 그림과 같이 오늘날의 시스템은 멀티 테스킹(multi-tasking)이 가능하므로 메모리에는 여러개의 프로세스가 저장되어 병렬적으로 작업을 수행한다. 

 하나의 segment는 위 그림의 오른편에 그려진 구조를 가지고 있다. 각각을 code segment, data segement, stack segement라고 한다. 시스템에는 최대 16,383개의 segment가 생성될 수 있고 그 크기와 타입은 다양하게 생성된다. 하나의 segment는 최대 2^32byte의 크기를 가진다.

 1) code segment : 시스템이 알아들을 수 있는 명령어(instruction : 기계어 코드로써 컴파일러가 만들어낸 코드)가 들어있다. instruction들이 명령을 수행하기 위해 특정 위치에 있는 명령을 지정해 주어야 하지만 segment는 자신이 어느 위치에 저장될지 컴파일 과정에서 알 수 없다. 따라서 logical address를 사용한다. 이는 실제 메모리 상의 주소(physical address)와 매핑되어 있고, segment는 segment selector에 의해서 자신의 시작 위치(offset)를 찾을 수 있고 자신의 시작 위치로부터의 위치(logical address)에 있는 명령을 수행할 지를 결정한다.

 즉, 실제메모리 주소(physical address) = offset + logical address

 2) data segment : 프로그램이 실행시에 사용되는 데이터(전역 변수들)가 들어간다. data segment는 각각 현재 모듈의 data structure, 상위 레벨로부터 받아들이는 데이터 모듈, 동적 생성 데이터, 다른 프로그램과 공유하는 공유 데이터 부분으로 나뉜다.

 3) stack segment : 현재 수행되고 있는 handler, task, program이 저장되는 데이터 영역이다. 우리가 사용하는 버퍼가 바로 이 stack segment에 자리잡고 있으며, 프로그램이 사용하는 multiple 스텍을 생성할 수 있고 각 스텍들간의 switch가 가능하다. 

 스텍은 처음 생성될 때 그 필요한 크기만큼 만들어지고 프로세스의 명령에 의해 데이터를 저장해 나가는 과정을 거치는데 이것을 stack pointer(SP)라고 하는 레지스터가 스텍의 맨 꼭대기를 가리키고 있다. 스텍에 데이터를 저장하고 읽어 들이는 과정은 PUSH와 POP instruction에 의해서 수행된다.

 

#3 8086 CPU 레지스터 구조

CPU가 프로세스를 실행하기 위해서는 프로세스를 CPU에 적재시켜야 한다. 그 저장 공간을 레지스터(register)라고 한다. 일반적인 시스템의 프로그램 레지스터의 구조는 아래 그림과 같다.

 

<그림 3. 일반적인 시스템의 프로그램 레지스터 구성 >

 

 레지스터는 목적에 따라 범용 레지스터(General-Purpose register), 세그먼트 레지스터(segment register), 플래그 레지스터(Program status and control register), 인스트럭션 포인터(instruction pointer)로 구성된다.

1) 범용 레지스터 : 논리 연산, 수리 연산에 사용되는 피연산자, 주소를 계상하는데 사용되는 피연산자, 그리고 메모리 포인터가 저장되는 레지스터

 

< 그림 4. 범용 레지스터 >

 

EAX피연산자와 연산 결과의 저장소
EBXDS segment안의 데이터를 가리키는 포인터
ECX문자열 처리나 루프를 위한 카운터
EDXI/O 포인터
ESI
문자열 처리에서 source를 가리킴
EDI
문자열 처리에서 destination을 가리킴 
ESPSS 레지스터가 가리키는 stack segment의 맨 꼭대기를 가리키는 포인터
EBPSS 레지스터가 가리키는 스텍상의 한 데이터를 가리키는 포인터

 

2) 세그먼트 레지스터 : code segment, data segment, stack segment를 가리키는 주소가 들어있는 레지스터

 

<그림 5. 세그먼트 레지스터 >
< 그림 6. 세그먼트 레지스터가 가리키는 세그먼트들 >

 

3) 플래그 레지스터 : 프로그램의 현재 상태나 조건 등을 검사하는데 사용되는 플래그들이 있는 레지스터

 

< 그림 7. 플래그 레지스터의 구성 >

 

4) 인스트럭션 포인터 : 다음에 수행해야 하는 명령이 있는 메모리상의 주소가 들어가 있는 레지스터

 

 8086 시스템의 메모리 및 CPU 레지스터의 구조를 알아야 하는 이유는 buffer overflow 공격을 하는데 있어 적절한 padding 사용과 return address의 정확한 위치를 찾고 필요한 assembly 코드를 추출하고 이해하기 위해서이다.