부트로더에 대한 간단한 정리 [2]

2018. 6. 21. 04:420x04 pwnable/Linux Kernel Basic Study

728x90

0번 트랙, 0번 섹터를 부트섹터라고도 한다. 

부트섹터 메모리 배치에 대해 알아보았다. 


Q : 메모리 배치는 왜 하는건데?

A : 내부 코드들, 데이터들, 코드와 데이터 사이에 중복이 없도록 하기 위함이다.

Thinking...

중복 되면 마치 DOS(Derial Of Service)중 TearDrop과 같은 일이 벌어지지 않을까? 


이 부분들은 주소가 많이 나와서 좀 더 명확하게 이해할 필요가 있다고 느낀다.


BIOS는 부트섹터를 메모리에 로드한다.


부트섹터는 나머지 두 섹터들을 메모리에 로드하기 전에 메모리 재배치를 수행한다. 


부트섹터는 이러한 변수를 선언하게 된다.

SETUPLEN = 4   # Setup 프로그램 섹터 수

BOOTSEG = 0x07C0 # 부트섹터 위치

INITSEG = 0x9000 # 부트코드를 이 주소로 옮기는 용도로 쓰일 것 임 

SETUPSEG = 0x9020 # 이 주소부터 셋업이 시작 됨 

SYSSEG = 0x1000 # 시스템이 로드되는 주소 (65536) 

ENDSEG = SYSSEG + SYSSIZE # 로드 끝내야 하는 지점인데 SYSSIZE는 어느정도인지 아직 잘 모르겠다 


눈에 익혀야 하는 사진 한 장을 책에서 가져왔다.

리얼모드에서의 메모리 배치 방법에 대해 요약되어 있다.


그림 이해가 잘 되지 않았는데 저자님이 친절하게 코드를 작성해주셨다. 


우선, 부트섹터를 복사하는 로직이 담겨 있는 코드이다.


mov ax, #BOOTSEG

mov ds, ax

mov ax, #INITSEG

mov es, ax

mov cx, #256

sub si,  si

sub di, di

rep 

movw # 2바이트 (WORD) 만큼 명령 반복 수행 == CX가 0이 될 때 까지 

(DS:SI  -> ES:DI) 

summary:

DS(0x07C0) 값과 SI(0x0000)가 복사할 소스 어드레스 : 0x07C00

ES(0x9000) 값과 DI(0x0000)가 목적 어드레스인 0x90000을 가리킨다. 


이 행위로 인해 부트섹터는 0x07C00 -> 0x90000으로 이동하게 된다.

즉, 메모리가 복사 되는 것이다. 


256워드(512바이트) 만큼 복사가 진행된다는 것은 1 Sector를 뜻하기도 한다.


cf) C언어의 memcpy함수는 rep movsb로 주로 쓰이게 된다.


!! strcpy와 memcpy는 엄연히 다르다. !!


<복습>

strcpy는 길이 지정을 하지 않지만, source문자열이 반드시 \0(null)로 끝남

-> BOF에 취약하게 됨 -> 정해진 버퍼의 크기가 없기 때문 


memcpy는 데이터 형에 관계없이 임의의 영역을 지정한 byte 수만큼 복사 

-> 이 친구도 BOF에 취약함 

-> size_t가 unsigned int인데 memcpy에 사용하는 3번째 인자 값이 signed 일 경우 실수로 -1을 넣었다고 가정 ->  memcpy함수는 이 -1을 signed로 받아들이지 않고 unsigned로 받아들이게 됨 -> 고로 2,147,483,648보다 큰 수가 되어버림 -> 의도 하지 않은 메모리 크기가 복사 되어 버림 


strcpy 원형 : 

char *strcpy(  
   char *strDestination,  
   const char *strSource   
);  

왜 strcpy의 두 번째 인자만 const일까? 

const라는 것은 변하지 않는 상수 키워드인데  

destination에 넘겨주는 인자는 문자열 상수가 될 수 없다. 


"cf) 모든 포인터 변수는 초기화 되어야 한다

그렇기 때문에 source에서도 문자열 변수, 상수는 넘겨줄 수 있지만, 초기화 되지 않은 포인터 변수는 사용할 수 없는 것이다.



memcpy 원형:

void *memcpy(  
   void *dest,  
   const void *src,  
   size_t count   
);  


memcpy는 반환 값이 void형 포인터 즉, 타입이 정해져 있지 않기 때문에 연산에는 사용될 수 없다. 


<Secure Coding>


strcpy의 경우는 strcpy_s라는 함수를 이용하여 정해진 크기를 세팅해주어야 하고, 

memcpy의 경우도 memcpy_s라는 것이 있다. memcpy는 src가 가리키는 곳 부터의 사이즈만 생각하지만 memcpy_s는 des 버퍼의 사이즈도 생각해준다. 


 


위의 명령을 수행하면, 512바이트 만큼 메모리가 복사되어 0x90000의 내용과 0x07C00의 내용은 동일해진다. 이렇게 되는 이유가 "상호 관례" , "방향 인식" 때문이라고 한다.


방향 인식은 아직 잘 모르겠고, 상호 관례는 책을 보고 적긴 하는데 이게 왜 관례인지는 도통 모르겠습니다. 혹시나 이 글을 보는 사람 중에 아는 사람이 있으면 댓글을 작성해주시면 좋을 것 같습니다.

1. OS 입장에서의 관례 

OS를 시작할 시작 프로그램을 부트섹터에 넣는다 


2. BIOS 입장에서의 관례

부트섹터를 메모리의 0x07C00에 로딩한다. 


어쨌든, 복사 되는 과정은 이해했으니 다음 코드를 진행해보았다. 

rep

movw # 이 까지는 위의 코드 가져옴

jmpi go, INITSEG #  이때 까지 알고 있던 jmp 명령어는 이렇지 않았는데 .. 

go: mov ax, cs

  mov ds, ax


summary:

짚고 넘어가야 할 사항 : "jmpi go, INITSEG의 역할"

INITSEG : 새로운 어드레스 

go: mov ax, cs가 진행되게 되면 cs는 0x07C0에서 0x9000으로 변경되게 된다. 


INITSEG에 의해 새로운 어드레스 위치로 점프했지만, 

JMPI go 명령을 만나기 전과 "동일한" 코드를 실행시킬 수 있는 장점이 생긴다. 


헷갈리지 말자. 자칫하면 헷갈릴 것 같다.. 


사진도 가져왔다.


그 다음, 명령이다.

go: mov ax, cs

   mov ds, ax  # 위 코드 동일 -> ds는 0x9000, ip는 0x0000 -> 실제 주소는 0x90000이 됨. (이전 블로그 참고하면 알 수 있음)

   mov es, ax  # es역시 0x9000이 된다 

   mov  ss , ax # ss역시 0x9000이 된다.

   mov sp , #0xFF00  


Summary:

ss : stack segment -> 이놈으로 스택을 관리할 수 있다.

지금 보면, cs 값을 ax에 복사하였고 그 ax 값이 ds, es, ss에 적용 되었기 때문에 

cs, ds, es, ss는 동일한 값을 지니고 있다. (0x9000) 


이러한 사진을 보면 스택은 0x9ff00에 설정됨을 알 수 있다.


내일은 Setup 프로그램을 메모리에 로드하는 과정을 공부하고 정리를 해볼까 한다.