본문 바로가기

My Study/Reversing

Segment Register

다들 올디를 사용해 보셨을 탠데요.


빨간색 네모친 부분을 항상 볼탠데 저 부분이 무엇을 나타내는 부분인지 몰라서 공부를 해보았습니다.

해당 부분은 세그먼트 레지스터라고 하는 16bit로 이루어진 레지스터입니다.
이제 저 레지스터가 무엇을 하는 놈인지 알아보기 전에 선행되어야할 지식을 알아두면 좋겠습니다.
(페이징기법, 세그먼트기법, 메인 메모리를 하드디스크까지 확장시킨 스왑 파일 개념)

전 여기서 간단하게 설명만 하겠습니다.

페이징기법 : 각 프로세스에서 사용하는 가상 메모리 주소는 0부터 시작한다고 하면
프로세스 1의 가상 주소 첫번째 페이지의 주소인 0은 실제 메모리 0번째 프레임에
프로세스 2의 가상 주소 첫번째 페이지의 주소인 0은 실제 메모리 1번째 프레임에
이런 식으로 가상 주소와 실제 메모리 사이에 페이지 테이블이라는 것이 존재하여 가상 메모리 번지의 페이지를 페이지 테이블에서 찾아 실제 메모리에 맵핑 시켜주는 것입니다.

스왑파일 : 메인 메모리가 만약 2G이고 프로세스들에 의해 2G가 전부 찼습니다. 하지만 프로세스에서는 또 메인메모리를 요구로 하면 메인 메모리는 부족합니다. 이 때 어떻게 해야 될까요?? 하드 디스크의 일부를 메인 메모리로 쓰는 것입니다. 현재 메인 메모리를 사용하고 있는 프로세스 페이지 중 잘 사용하고 있지 않는 페이지를 하드로 옮기고 새로 요구한 것을 실제 메인 메모리에 올리는 것입니다. 이때 하드디스크 일부를 스왑파일이라고 합니다.

세그먼트 기법 : 페이징 기법에서는 움직이는 단위가 4KB입니다. ( 윈도우 기준 ) 하지만 세그먼트 기법에서는 움직이는 단위가 세그먼트 단위 입니다. 세그먼트란 코드부, 데이터부, 스택부 등등 이런 것들을 세그먼트라고 합니다.
그래서 각 세그먼트는 비슷한 특징을 가지고 있습니다.
장점 : 세그먼트 단위로 권한 조정 가능( 읽기 영역/ 쓰기 영역  등). 그래서 프로세스를 보호하는데 장점이 있음.
단점 : 세그먼트는 크기가 일정하지 않아서 메모리에 올라갔다 내려갔다 하면 비는 공간이 생기게 됩니다. 즉, 외부 단편화가 생기게 됩니다. 비는 공간에 생기면 나중에 큰 세그먼트는 올라가질 못하게 되겠죠.

그래서 실제로 사용되고 있는 방법은 페이징 기법과 세그먼트 기법의 장점을 섞어서 사용하고 있습니다.
페이징 기법은 외부단편화가 존재하지 않고 세그먼트 기법은 메모리 공유, 보호 기능들을 수행할 수 있습니다.

그래서 가상주소에서 실제 주소까지 갈때는
첫째, 이 주소가 세그먼트에 접근 권한 있는지 등 체크
둘째, 해당 주소를 선형 주소로 바꾼다음 페이징 기법 사용.


이정도면 설명하면 되겠습니다. 더욱 자세하게는 책을 참조하셔도 되고 인터넷을 찾아보셔도 됩니다. ^^

위에서 페이징을 하려면 선형 주소를 만들어야 합니다.
선형 주소를 만드려면 가상 주소에서 세그먼테이션이라는 과정을 거쳐야 합니다.

세그먼테이션 과정을 봐보겠습니다. 위에서 설명했듯이 위 과정에서는 여러가지 보호 속성과 제한 속성을 통해 시스템에 대한 보호 작업을 수행하게 됩니다.

그러면 다시 본론으로 돌아와 선형주소를 만드려면 가상주소와 세그먼트 디스크립터의 베이스 주소와 합쳐야 합니다.
여기서 또 모르는 단어가 나왔습니다. 세그먼트 디스크립터가 무엇일까요??
보호모드의 마이크로프로세서에서는 여러가지 보호 정보를 저장하고 있는 64bit의 세그먼트 디스크립터라는 것이 있습니다.
(보호모드 : 보호 작업을 수행하도록 작동하는 상태)

그러면 이 64bit의 세그먼트 디스크립터라는 것은 어떻게 이루어져있는지 알아봐야하는데 전 사진만 올려드리겠습니다.

총 64bit로 이루어져 있습니다.
베이스 필드 : 실제 세그먼트를 통해 메모리에 접근할 때 가산되어지는 값
리미트 필드 : 세그먼트가 허용하는 메모리의 범위
액세스 비트 : 세그먼트 사용 빈도수를 알아보기 위함
프레젠트 비트 : 이 디스크립터가 유효한지를 체크하는 비트
디폴트 비트 : 0 -> 코드를 16bit로 해석, 1 -> 32bit로 해석
Granularity비트 : 메모리의 범위를 4KB단위로 확장
시스템 비트, 타입 비트 : 세그먼트의 목적을 나타내는데 사용

DPL(Descriptor Privilege Level) : 디스크립터 항목 내에 보관된 특권 값

디스크립터라는게 있다는 것을 보여준 다음 설명하려고 한 내용이 있습니다.
바로 원래 모든 세그먼트 레지스터(CS,SS,DS,ES,FS,GS)는 80bit로 이루어져 있습니다.
하지만 올디에서는 16bit만이 보일 것입니다. 
왜냐하면 소프트웨어에 의해서 읽혀질 수 있는 영역은 16bit뿐입니다. 나머지 64bit에는 접근이 불가능하죠.
이렇게 접근이 불가능한 64bit 영역 안에는 바로 디스크립터가 들어있습니다.


이런 세그먼트 디스크립터도 2가지가 있습니다.
GDT(Global Descriptor Table) : 모든 프로그램이 참조할 수 있는 세그먼트 디스크립터들의 모임(필수)
LDT(Local Descriptor Table) : 각 태스크 단위로 정의할 수 있는 것
그러면 각 세그먼트 디스크립터들은 디스크립터 테이블에 있는데
GDT를 참조해야 될지 LDT를 참조해야 될지 어떻게 알까요??

그것은 GDTR, LDTR이라는 특수한 레지스터에 의해 알 수 있습니다.(각 레지스터 구조는 인터넷 참조)

이런 그림이 될 수 있겠습니다. ( 위에 8191이라고 나와있는 것은 최대 세그먼트 디스크립터의 개수 입니다. )

이제 거의 막바지 입니다. 그러면 GDTR, LDTR두 레지스터 중 어떤 것을 사용하게 하는지 결정하는 것은 무엇일까요??
바로 세그먼트 레지스터 입니다. 세그먼트 레지스터의 구조를 보겠습니다.

Selector라는 부분은 13bit로 이루어져 있습니다. ( 2의 13승 = 8192 )
최대 세그먼트 디스크립터 개수는 8191개 이므로 13bit만 있으면 모든 디스크립터를 가리킬 수 있습니다.

Selector : 어떤 디스크립터를 택할건지
TI : GDT(0), LDT(1) 중 어떤 것을 택할지
RPL(Request Privilege Level) : 디스크립터를 통해 요구할 때의 특권
CPL(Current Privilege Level) : 코드세그먼트(CS)에서 RPL부분.

CPL, RPL, DPL은 모두 특권 값을 지니고 있으므로 2bit로 표현하게 됩니다.
여기는 안나왔지만 프로세서 내부에는 특권 레벨을 갖는 또하나의 값이 있습니다.
EPL(Effective Privilege Level) : 유효 특권 레벨, CPL값과 RPL값 중 가장 큰 값을 의미.

CPL, RPL, DPL, EPL 4개의 값에 따라 보호모드 수행...
세그먼트 레지스터를 수정하려면 아래와 같은 규칙이 성립해야됩니다.
CPL <= RPL  ( CPL은 RPL보다 작거나 같아야 한다. )
EPL <= DPL  ( EPL은 DPL보다 작거나 같아야 한다. )

CPU는 16bit 세그먼트 레지스터 값을 보고 메모리 내의 디스크립터 테이블에서 해당하는 디스크립터를 찾아서 세그먼트 레지스터의 가려진 영역(64bit)으로 로딩합니다.

이제 실제로 프로그램에서 사용되고 있는 세그먼트 레지스터(셀렉터)에 대해 보겠습니다.

이렇게 6개가 있습니다. 즉, 프로그램은 한번에 6개의 세그먼트 디스크립터를 가리킬 수 있다는 것입니다.
CS(Code Segment)를 봐보겠습니다.
1B는 이진수로 -> 0000000000011 0 11
보기 좋게 띄어쓰기 해봤습니다.
CPL은 11-> 3입니다.
TI은 0입니다.
즉, 코드 세그먼트 부분은 GDT를 가리키며 그 테이블에서 인덱스 3번째 디스크립터를 가리킵니다.
그리고 특권레벨은 3입니다.

이 디스크립터가 어떻게 구성되어있는지 실제로 봐보겠습니다.

가장 첫번째 디스크립터를 보통 사용하지 않습니다. 그래서 첫번째 것을 뺀 3번째를 봐보면
베이스 주소는 0x00000000 이고 리미트는 0xffffffff입니다. 타입은 코드영역이구요. 특권레벨은 3입니다.

이러한 베이스 주소와 가상주소가 합쳐져서 선형주소가 되는 것입니다.
세그먼트 레지스터가 무엇을 하는 것인지 알아보았습니다. ( 어렵네요 ;; )

하나만 더 보겠습니다. 코드상에서 자주 보는 FS레지스터입니다. 이 레지스터는 TIB(Thread Information Block)의 영역을 가리키는데 사용됩니다.
현재 실행되고 있는 스레드 예외 정보, 스택 시작 주소, 스택 마지막 주소 등등이 있습니다.
FS레지스터를 위와 같은 방법으로 봐보겠습니다.

3B - > 111 0 11
111 -> 7
위 그림에서 7번째 인덱스를 봐보면

시작 주소가 7ffdf000입니다.
올디에서 직접 봐보겠습니다.

7FFDF000이라고 나와있습니다. 일치하는 것을 알 수 있습니다.
즉, 첫번째 스레드가 가지는 FS의 베이스 주소는 7FFDF000인 것입니다.
만약 두번째 스레드가 가지는 FS베이스 주소는 4KB를 뺀 7FFDE000이 될 것입니다.

혹시 눈치 채신 분들도 계실태지만 2G는 유저영역 ,2G는 커널 영역인데 0~ffffffff이면 일반 어플이 커널영역까지 전부 접근가능 한거 아니냐 라고 생각하신 분들도 계실탠데요.  그 이유는 커널 메모리에 대한 보호를 세그먼트 디스크립터를 통해서 하고 있지 않습니다.
바로 이 선형주소를 물리주소로 바꾸는 페이징 과정에서 수행하게 됩니다.

그리고 IOPL(Input Output Privilege Level)이라는게 있습니다.
입출력 특권 값인데 프로그램 코드가 메모리에 접근하는 경우가 아닌, 하드웨어 장치에 접근하는 경우와 몇 가지 특별한 명령이 실행되는 경우에 비교되어 사용하는 특권 값입니다. 이런게 있다는 것만 말하려고 했습니다.

'My Study > Reversing' 카테고리의 다른 글

커널모드에서 유저모드 디버깅  (0) 2010.03.11
Vmware를 사용한 커널 디버깅  (2) 2010.03.11
Assembly 점프 문  (4) 2010.02.26
네이트온 안티디버깅 우회  (0) 2010.02.20
OllyDbg 심볼 파일  (0) 2010.02.19