사용자 삽입 이미지
Chapter 1. 루트킷이란?
            ㄴ 루트킷의 의미
Chapter 2. 루트킷의 동작원리
Chapter 3. 후킹기술 소개
           ㄴ Import Address Table (IAT) 후킹
           ㄴ Inline 함수 후킹
           ㄴ DLL 인젝션 기법 소개
           ㄴ 커널 후킹 기술 알아보기
Chapter 4. 루트킷 탐지기술 소개
           ㄴ 후킹 탐지기법과 메모리 스캐닝 기법 소개
           ㄴ HIPS(행동 탐지) 기반 탐지 기법 소개
           ㄴ 루트킷 탐지툴(RootkitRevealer)의 작동원리 소개


1. 루트킷이란?

사용자 삽입 이미지
루트킷의 의미...


루트킷(Rootkit)이 뭘까?
루트킷은 필자가 가장 좋아하는 드라이버, 커널 시스템 프로그래밍 분야에서 가장 관심을 갖고 둘러보고 있는 분야 중 하나이다.
루트(Root)의 사전적 의미를 살펴보자.
루트는 식물의 "뿌리 근(根)"이라는 의미를 갖고 있다. 그 다음에 나온 Kit은?
많이 알려졌다 시피 "도구" 라는 의미로 해석되고 있다.


쉽게 해석해보자면 컴퓨터의 뿌리. 가장 근원이자 근본이 되는것은 바로 root 계정일 것이다.
root 계정이란 리눅스(Linux), 유닉스(Unix) 운영체제에서 쓰이는 계정을 뜻하는 단어 중 하나로 우리가 쓰는 Windows에서 슈퍼 관리자(Administrator)와 같은 권한을 갖고 있다.

이 말만 봐도 한가지 알수 있는 사실이 있다.

최초의 루트킷은 바로 유닉스에서 나왔다는 것이다.

루트킷이 최초로 나왔을 당시는 유닉스가 한창 유행이었을 1990년대 시절이다.

지금이나 예나 마찬가지겠지만 별도의
트로이목마백도어 툴 없이 컴퓨터 시스템을 크래킹(불법적으로 시스템에 침투, 자료 파괴, 데이터 갈취 행위의 전체적인 불법행위)할 경우..
다시 이 시스템에 침투할때 마다 별도의 크래킹 작업이 필요했다. 이 별도의 크래킹 작업은 많은 시간이 소요된다.

사용자 삽입 이미지

커널과 OS의 관계(http://www.sweeny.co.kr)

때문에 크래커는 침투를 매번 어렵게 하지 않기 위해 루트킷을 제작하고 그로 인해 루트킷이 최초로 탄생하게 되었다. 최초의 루트킷은 커널에 침투하지 않고 사용자 계층에서 구동되는 단순한 트로이목마였다고 한다.

리눅스, 유닉스의 콘솔(Console)창에서 ls라는 명령어가 있다. 이 ls명령어는 Windows 도스(DOS)에서 사용했던 명령어인 dir와 같은 기능으로써 현재 디렉터리에서 하위 디렉터리까지 리스트로 보여주는 기능이다.

이 ls라는 명령어를 통해 모든 디렉터리와 파일들을 나열하게 되는데 이 ls를 조작한 것이라고 생각해 보자. 즉, 상대방 컴퓨터에 Knife 라는 크래킹툴을 심어놨다고 가정했을때 시스템 관리자는 ls 명령어를 통해 자신이 설치하지도 않은 파일인 Knife 라는 것이 설치되었음을 간파할 것이고 이것은 더 이상 Knife라는 크래킹툴을 통해 크래킹을 할 수 없음을 나타낸다는 것이다. 그래서 생각해낸 것이 시스템에 가장 먼저 침투하여 ls명령어를 조작하거나 침투한 크래커가 직접 별도로 제작한 ls프로그램을 덮어쓰기 해버리는 것이었다. 이러면 사용자가 ls 명령어를 쓰더라도 크래커가 심어놓은 파일이나 디렉터리가 보이지 않고 원하는 작업을 계속 수행할 수 있었다.

시스템 관리자들은 이러한 행위를 막기 위해 언제, 어떤 파일이 교체 되었는지를 알 수 있는 파일 체크 프로그램을 개발하였다.

기는 놈 위에 걷는 놈 있고, 걷는 놈 위에 뛰는 놈 있다고....

체크 프로그램을 개발하여 더 이상 ls프로그램을 대체하는것으로 시스템에 침투하는 것이 어려워지자 그때부터 크래커들은 컴퓨터의 근원이자 근본인 커널에 눈을 돌리기 시작하고, 이 때부터 시스템에 침투하려는 많은 크래커들과 이 크래커들을 어떤 방법으로 막아야 될지에 대한 커널과 커널 루트킷에 대한 연구가 급속도로 진척되기 시작했다.

위에서 루트킷의 역사에 대해 어느정도 알아봤다.

그렇다면 루트킷의 의미는 무엇일까?

위에서 언급했듯이 문맥상으로도 어느정도 감이 잡힐 것이라 생각한다.
루트킷의 진정한 기능이자 진정한 의미는 은폐, 숨겨진(Masking, Hidden)이라는 것이다.

실상 현재 굉장히 많은 네티즌들과 블로거, 언론기자, 심지어는 커리어(career)와 견해 지식이 짧은 보안 전문가까지 루트킷에 대한 언어를 오용(用)하고 있다.

루트킷 자체가 멀웨어(Malware)에 포함되고, 시스템 관리자에게 해를 끼치는 프로그램으로 잘못 인식하고 착각하고 있다는 것이다. 이는 해커해킹. 크래커와 크래킹을 제대로 구분하지 못하는 것과 마찬가지이다. (사실 해커와 크래커는 혼동할 만한 법도 있는 것이 해커가 마음을 바꿔먹으면 그대로 크래커가 된다. 이때 언어의 사용선택은 각자 개인에게 있지만 그것을 대중에게 전파해주는 언론 기자만큼은 올바른 언어를 선택해야 할 것이다. 이런 점에서 국내 최고의 보안기업인 안철수연구소. 안랩(Ahnlab)의 주력제품인 V3와 V3 365 Clinic에 "해킹 차단"이라는 단어 선택은 정말 단어오용의 단적인 예를 가장 보여주는 듯 하다. 국내 가장 큰 기업이 단어선택을 이렇게 하므로 해서 해킹은 더욱더 나쁜일로만 치부되었다는 점에서 V3 프로그램은 괜찮다싶지만 안랩은 글쎄...)

루트킷 자체가 바이러스, 버퍼 오버런, 버퍼 오버플로우같은 자체적인 공격 프로그램이 아니다.
현재 루트킷은 디바이스 드라이버와 바이러스가 결합된 상태로 패키징 된다고 루트킷 자체를 공격 프로그램으로 결부시키는 것은 옳은 행동이 아니다.

루트킷은 바이러스 은닉 기능말고도 현재 우리가 사용하는 백신이나 방화벽의 프로세스(Process)와 커널 드라이버를 은닉시켜 주는 기능을 한다. 만약 루트킷을 멀웨어라고 칭해버리면 멀웨어를 잡는 백신 역시 마찬가지로 멀웨어라는 범주에 속해버리게 되는.. "멀웨어가 멀웨어를 잡는 동족사냥 프로그램"이라는 이상한 관계로 서로 뜻하는 언어가 뒤엉켜버리게 된다.

루트킷은 절대 나쁜 행위를 끼치는 것이 아닌, 그저 은폐와 은닉을 가지고 있는 컴퓨터 "기술"이라고만 알아뒀으면 좋겠다.

다음에는 몇몇 루트킷의 예를 들어 루트킷이 동작하는 원리를 알아보도록 하겠다.
  1. BlogIcon vermouth 2008.07.29 09:26 신고

    트랙백해주셨네요^^

※ 흐릿한 그림은 클릭(Click)하면 원본크기로 나옵니다.



PE 파일 형식 예와 구성

그림 1-5는 EXE 파일을 바이너리로 읽어 들인 것입니다.
맨 앞의 MZ로 시작하는 부분이 MS-DOS의 헤더입니다. "This Program cannot be run is dos mode" 라는 부분의 앞 부분이 도스에서 윈도우 프로그램이 실행되는 것을 막는 역할을 합니다. PE 헤더는 Winnt.h 안에 들어 있습니다.

사용자 삽입 이미지
 
<그림 1-5>


아래 그림 1-6은 PE형식의 구성도입니다.

사용자 삽입 이미지
 
<그림 1-6>


PE 구조 첫 부분이자 첫 시작! 도스 스텁(Dos Stub)과 도스 헤더(Dos Header)

이 도스스텁과 도스헤더는 DOS 시절에 사용되었지만 현재 윈도우에서는 사용되고 있지는 않습니다.
다만, 이 부분을 모르고 넘어간다면 다음에 나올 DOS_HEADER에 대한 구조체를 이해하기 힘들기 때문에 반드시 짚고 넘어가야 하는 부분입니다. (단, 현재 악성코드들이 DOS 헤더를 이용하는 경우가 가끔 있음)

사용자 삽입 이미지
<그림 1-7>

아래는 구조체 필드의 정보를 적어봤습니다. 현재 윈도우에서 사용되고 있는 필드는 거의 두개뿐이므로 이 두개에 대한 정보만을 적겠습니다.

1. e_magic
IMAGE_DOS_HEADER의 시작을 나타내는 것으로 항상 'MZ'라는 문자열로 시작 합니다. 그리고 e_lfanew 필드는 IMAGE_DOS_HEADER 다음에 나오는 헤더 파일의 오프셋 값을 가지고 있습니다. 그 외의 필드들은 Windows에서 파일을 실행시켰을 때 이용되지 않는다. 즉, DOS헤더의 MZ라는 정의입니다.

2. e_lfanew
위 그림에서 마지막 빨간색 네모칸의 e_lfanew는 PE헤더가 있는 곳의 실제 오프셋 값을 나타내며 실제 PE 형식의 주소를 가리키고 있는 상대 주소 값입니다. e_lfanew는 나중에 나올 추후 나올 IMAGE_NT_HEADER의 시작위치를 알려줍니다.

실제주소를 얻기 위해서 아래 코드와 같이 이용하게 합니다.

hFile = CreateFile( "C:\\winnt\\system32\\KERNEL32.DLL", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
hFileMap = CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL);
dwSize = GetFileSize( hFile, 0);
pBaseOfFile = (char*)MapViewOfFile( hFileMap, FILE_MAP_READ, 0, 0, dwSize );

PIMAGE_DOS_HEADER          pIDH = (PIMAGE_DOS_HEADER)pBaseOfFile;
PIMAGE_NT_HEADERS          pIDH = (PIMAGE_NT_HEADERS)((DWORD)pIDH + pIDH -> e_lfanew);

사용자 삽입 이미지



PE 두번째 부분 PE 헤더IMAGE_NT_HEADERS 입니다.

사용자 삽입 이미지


IMAGE_NT_HEADERS는 위 그림과 같은 구조를 지니고 있습니다.

typedef struct _IMAGE_NT_HEADERS {
  DWORD Signature;
  IMAGE_FILE_HEADER FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;


첫번째 시그너처(Signature)의 값은 PE\0\0라는 값을 갖습니다. 이 값을 체크해서 PE포멧 파일인지 아닌지 구별할 수 있습니다. 윈도우에서는 이 값을 식별하기 위하여 IMAGE_NT_SIGNATURE 라는 상수를 정의해 놓았습니다. 이 값은 항상 4바이트로 정의되어 있습니다.

< 참고할 IMAGE_NT_HEADERS의 주소 위치 >
사용자 삽입 이미지


이번엔 PE 형식 세번째 구조 IMAGE_FILE_HEADER 입니다.

사용자 삽입 이미지


파일헤더는 그림 1-5 에서 0xD0 번지부터는 "PE\0\0"을 볼 수 있는데 이는 PE라는 것을 밝히는 PE의 사인입니다.  이 구조체에는 파일에 대한 기본적인 정보가 담겨 있습니다.


typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;
  WORD NumberOfSections;
  DWORD TimeDateStamp;
  PointerToSymbolTable;
  DWORD NumberOfSymbols;
  
 WORD SizeOfOptionalHeader;
  WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

아래는 구조체의 정보들입니다.

1. Machine

어떤 플랫폼에서 동작하기 위한것인지 나타냄. 그리고 또 한가지 눈에 띄는 점은 필자의 닉네임택과 Tistory 주소 택 이미지가 추가 되었습니다. 그 전까진
twister1018@naver.com 이라는 제 이메일로 대신했지만... 앞으론 저 택들이 쭉 함께 있을것입니다.

 
사용자 삽입 이미지
<그림 1-8>

2. NumberOfSections;

섹션을 분석하기 위해 사용되는 값입니다. 파일을 Hex파일등으로 열어 하드코딩시에 이 값을 변경시켜 섹션수를 늘리고 코드를 추가할 수 있습니다. 즉, 섹션 개수라고 생각하면 됩니다.


3. TimeDateStamp;
파일을 만든 날짜와 시간입니다.

4. PointerToSymbolTable;
COFF 심볼 테이블의 주소입니다.

5. NumberOfSymbols;
COFF 심볼 테이블의 심볼의 개수
입니다.

6. SizeOfOptionalHeader;

IMAGE_FILE_HEADER 바로 다음에 위치한 IMAGE_OPTIONAL_HEADER 구조체의 크기입니다. 32-bit 윈도우에선 0xE0라는 어마어마한 크기를 갖고 있습니다.


7. Characteristics;
파일이 exe인지, dll인지에 대한 플래그를 갖고 있는 변수입니다.


헤더의 멤버중 SizeOfOptionalHeader(옵션 IMAGE_OPTIONAL_HEADER(예제 1-2) 이것이 가르킴) 엔트리 포인트 주소, 스택 사이즈 등을 이 헤더에서 관리합니다.

7번째 AddressOfEntryPoint는 프로그램 코드의 시작 주소를 나타내며 절대 주소가 아닌 상대 주소(RVA, Relative Virtual Address, 특정 주소로부터 얼만큼 떨어져있는지 나타낸 값을 칭함. 만약 주소가 0x50이고 실제 주소가 0x30이라면 RVA는 0x20 됨. 단, 대상이 가상메모리임을 명심) 시작 점입니다.

AddressOfEntryPoint 값이 가리키는 값은 .text에 위치하며, IMAGE_OPTIONAL_HEADER는 프로그램이 구동하기 위한 기본 정보들을 담고 있으므로 중요한 구성원은 굵은 글씨를 보면 됩니다.

사용자 삽입 이미지

<그림 1-9>

 

typedef struct _IMAGE_OPTIONAL_HEADER {

  WORD Magic;
  BYTE MajorLinkerVersion;
  BYTE MinorLinkerVersion;
  DWORD SizeOfCode;
  DWORD SizeOfInitializedData;
  DWORD SizeOfUninitializedData;
  DWORD AddressOfEntryPoint;
  DWORD BaseOfCode;
  DWORD BaseOfData;
  DWORD ImageBase;
  DWORD SectionAlignment;
  DWORD FileAlignment;
      WORD MajorOperatingSystemVersion;
  WORD MinorOperatingSystemVersion;
  WORD MajorImageVersion;
  WORD MinorImageVersion;
  WORD MajorSubsystemVersion;
  WORD MinorSubsystemVersion;
  DWORD Win32VersionValue;
  DWORD SzieOfImage;
  DWORD SizeOfHeaders;
  DWORD CheckSum;
  WORD Subsystem;
  WORD DllCharacteristics;
  DWORD SizeOfStackReserve;
  DWORD SizeOfStackCommit;
  DWORD SizeOfHeapReserve;
    DWORD SizeOfHeapCommit;
  DWORD LoaderFlags;
  DWORD NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;


이렇게 PE 사인과 IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER를 합쳐 IMAGE_NT_HEADERS라 합니다. 아래는 IMAGE_NT_HEADERS의 구조체입니다.

typedef struct _IMAGE_NT_HEADERS {
       DWORD Signature;
       IMAGE_FILE_HEADER FileHeader;
       IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;



PE의 마지막 구조체인 섹션 헤더(Section Header)

이제 마지막인 섹션 헤더입니다. 섹션 헤더에서의 IMAGE_SECTION_HEADER은 .text, .data, .idata, .reloc 등의 구역(Section)의 시작 주소와 사이즈에  관한 정보가 들어 있습니다. 우선 .text는 실제 코드가 들어 있습니다. .data 구역은 전역이나 정적 변수들이 존재합니다. 그리고 아래쪽에 .idata 테이블이 있는데 이 테이블은 DLL로부터 가져다 쓴 함수들의 실제 주소가 적혀 있습니다.

섹션 테이블의 구조체 정의는 아래와 같습니다.

typedef struct _IMAGE_SECTION_HEADER{
  BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
   DWORD PhysicalAddress;
   DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  
 WORD NumberOfRelocations;
  WORD NumberOfLinenumbers;
  DWORD Characteristics;
}
IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

1. Name[IMAGE_SIZEOF_SHORT_NAME];
섹션의 이름이 들어 있습니다.이 멤버는 섹션이름을 나타내는 문자열을 저장하게 되는데 IMAGE_SIZEOF_SHORT_NAME 은 8바이트 크기를 나타내는 상수입니다. .text나 .data 같은 이름을 갖지만 섹션의 이름으로 섹션의 성격을 파악해선 안됩니다. 이 멤버의 값은 null일 수도 있으며 섹션이름이 같다고 하여 항상 같은 속성을 지니진 않습니다. 섹션의 속성을 파악하기 위해선 Characteristics 멤버를 참조하여야 합니다.


2. PhysicalAddress/VirtualSize
PE로더에 의해 이미지가 메모리에 올려진 후에 해당 섹션이 얼마만큼의 크기를 가지고 있게 되는지의 정보입니다. 이 멤버는 Misc 유니온 구조체의 멤버이며 이 이름은 변수 의미에 대해 충분한 오해의 소지가 있습니다. 이는 물리적주소를 의미하는게 아니라 가상주소 상에서 해당 섹션의 크기를 나타내는 멤버라는 점을 주의하기 바랍니다.

3. VirtualAddress
PE로더에 의해 이미지가 메모리에 올려진 후에 해당 섹션이 어느 주소에 위치하는지의 RVA 주소를 값으로 가지고 있습니다. 이 멤버는 항상 IMAGE_OPTIONAL_HEADER의 멤버인 SectionAlignment 의 배수값을 가집니다. PE로더가 메모리에 섹션을 올릴때 이 멤버의 값을 참조하게 됩니다.
이 멤버의 값이 0x1000h 이고 IMAGE_OPTIONAL_HEADER의 ImageBase 값이 0x00400000h 라면 PE로더는 해당 섹션을 0x00401000h 번지에 올립니다. 항상 이 값은 ImageBase를 기준으로 하는 RVA값이라는 걸 주의하기 바랍니다.

4.
.text 섹션
.text 섹션에는 일반적으로 실행되는 코드들이 들어 있습니다. 따라서 어셈블러나 컴파일러가 만드는 코드들이 들어가게 됩니다. .idata는 .text와 DLL주소간의 매핑 테이블이며, 이런 .idata의 기능 덕분에 .text는 읽기 전용이 가능하다는 결론입니다. .text은 IAT를 참조하여 함수,변수를 호출하고 사용합니다.

5. .data 섹션
.data 섹션은 초기 데이터들이 있으며 변수들이 컴파일하며 초기화 됩니다. 지역 변수의 경우 쓰레드 스택에 저장되며 .data 영역에는 존재하지 않습니다. 즉, .text섹션과 함게 프로그램 코드와 데이터를 포함한 영역입니다.


6. .idata 섹션
다른 DLL로부터 가져다 쓰는 함수들의 정보가 담겨 있습니다. 정적 임포트 데이터 섹션으로 런타임 동적으로 로드된것은 (LoadLibrary API 또는 GetProcAddress API) 임포트 섹션이 필요가 없습니다. 로드타임 동적 링크인 경우에 사용되어 집니다. .idata역시 import directory table(IDT)가 있으며 임포트 하려는 DLL들의 엔트리가 저장 되어 있습니다.

7. .edata 섹션
함수의 리스트들이 들어 있는 섹션입니다. export directory table(edt)가 있으며 주 내용으로 "순서수 베이스", EAT(export address table)의 엔트리수, ENPT(export name pointer table)의 엔트리수, EAT의 RVA, ENPT의 RVA, 순서수 테이블의 RVA가 있습니다.


 

마치며...

PE의 시작부터 끝까지 모두 잘 살펴보았습니다.
Win32 PE의 구조를 살펴본다는 것은 컴퓨터를 만지는 전산장이에게는 상당히 즐거운 일일수도, 반대로 재미 없는 일일 수도 있겠습니다만, 컴퓨터를 활용하는데에 있어서 네트워크 분야나 프로그래밍의 한 분야만을 고수하기 보다는 여러 분야의 장단점을 생각하여 본인만의 컴퓨터 학습 방법을 따로 익히는 것이 좋을것 같습니다.

참고 자료 - MSDN, An In-Depth Look info the Win32 PE File FOrmat - Part 1
참고 도서 - 해킹, 파괴의 광학(출판사 : 와이미디어, 김성우 저자)
 

  1. BlogIcon Rude.K 2009.05.19 15:15 신고

    정리 잘 해주셔서 감사합니다.

※ 흐릿한 그림은 클릭(Click)하면 원본크기로 나옵니다.


PE파일을 언급하는 이유는 추후 있을 버퍼 오버플로우 기법과 바이러스 제작 강좌에 응용력을 키울수 있도록 하게끔 하는 것이 그 목적입니다. 스펙이라는 특성상 매우 지루할 내용이긴 하나 잘 이해만 해두신다면 시스템 프로그래머에 한발 더 앞서가는 일이 아닐까 합니다.

본론으로 넘어가서...

PE 파일 형식(Portable Exucutable File Format)이란?

파일에 담겨 다른 곳에 옮겨져도 실행될 수 있도록 규정한 형식이라는 뜻입니다.


PE 형식을 제대로 알지 못하면 윈도우 내부를 살펴보는데 많은 제약이 따르고 PE 형식은 단시간내에 마스터할 수 있는 내용은 아니기에 우선 형식이 어떤 것인지 프로그램 소스코드를 제공하며 유추해보도록 하겠습니다.

일단, 아래 예제를 작성하여 파일을 하나 만들어보도록 하지요.
 
#include <stdio.h>

int main(void)
{
          double  a;
          a = 3.14;
          printf("%The value of pi is %f. \n", a);

return 0;
}



위 예제를 실행파일로 만들때 과정은 아래와 같습니다.

사용자 삽입 이미지
<그림 1-2>



우선 stdio.h를 사용하기 때문에 컴파일러는 stdio.h와 hello.c를 한 개로 합친 후 컴파일 하여 기계어 코드를 만들어 냅니다.

컴파일 폴더로 가서 이 프로젝트 폴더의 obj 파일을 열어보면 아래창처럼 확인할 수 있습니다.


사용자 삽입 이미지
<그림 1-3>


빨간색 칸에 예제에서 입력한 "the value of pi is" 문자열 있습니다.

그러나 이 문자열만 갖고서 실행시킬수가 없습니다.
특정 행동을 하는 값을 우리가 입력 시켜야만 가능하고 우리가 입력 시킨값을 컴파일러가 CPU를 통해 기계어로 변환해줘야만 실행을 시킬수가 있지요.

그림 4-2의 hello.exe는 PE 파일이나 마찬가지라는 것 입니다.

일단 PE파일 형식으로 적힌 내용을 운영체제가 분석하기 전까지의 작업을 알아 본 후 PE 파일 형식을 구체적으로 알아보도록 하겠습니다.

모든 어플리케이션에 4GB를 부여한다?

사용자에 의해 프로그램 실행이 요청되면 컴퓨터는 우선적으로 실행 파일 앞에 붙어있는 헤더를 보고 내가 실행할 수 있는지 확인합니다. 예를 들어 ARM CPU 기반에서 동작하는 윈도우 CE에서 실행시키기 위해 컴파일 된 실행파일이라면 펜티엄에서는 동작시킬 수 없습니다. 이 테스트를 비록한 몇 가지 테스트에 모두 통과하면 운영체제는 이 프로그램에게 4G 바이트를 할당해 줍니다. 그런데 운영체제는 실제로 4G 바이트를 한 번에 다 주는게 아니라 주어진 4GB 중에 실제로 사용하려고 하는 시점에 메모리를 할당해 주는 것입니다.

Microsoft(이하 MS)의 운영체제인 Windows는 실행되는 모든 프로그램에 전부 2의 30승, 즉 4GB 라는 어마어마한 공간의 메모리를 줍니다. 보통 개인 컴퓨터에 일반적으로 256MB ~ 2GB을 장착하는 것을 생각해보면, 애당초 프로그램 하나에게도 4GB를 주지 못합니다. 그런데 Windows는 각 프로그램에 4GB씩 실제로 할당하고 있습니다.

할당하는 방식에는 크게 두 가지 방식이 있습니다.

한가지는 한번에 4GB를 다 할당해 주지 않고 당장 필요한 만큼만 주는 방식입니다. 보통 간단한 프로그램은 실제 동작하는데 있어서 수백 KB나 몇 MB정도의 메모리면 충분합니다.

다른 한가지는 많이들 알고 있는 방식인 하드디스크를 메모리처럼 사용하는 것입니다.
하드디스크는 DRAM보다 느려서 그렇지 읽고 쓰는데는 아무 문제가 없다는 점에서 하드디시크는 결국 메모리와 본질이 같습니다. 이 방법은 페이징이라는 테크닉으로 이루어지는데 매우 복잡한 문제이므로 페이징과 각 프로그램 당 메모리 할당 문제는 추후 다루도록 하겠습니다.

여기서 기억할 것은 Windows에서 실행되는 모든 프로그램은 각자 자기만의 4GB 메모리 공간을 할당 받는다는 것이라는 점!

자, 모든 프로그램에게 4GB 받았더라도 첫번째 방법처럼 사용하려고 하는 시점에 메모리를 할당해 주는 방식이라면 5만 바이트의 hello.exe를 메모리에 올려야 합니다. 왜냐면 CPU는 메모리에 올라온 것들만 읽거나 쓸 수 있기 때문입니다. 따라서 CPU는 프로그램이 올라오면 5만 바이트만큼의 메모리 공간을 달라고 메모리 관리자에게 요청하게 됩니다. 요청을 받은 메모리 관리자는 DRAM에 공간이 있는지 살펴보고 5만 바이트를 담을 수 있는 공간을 모아서 주게 됩니다.

아래 그림 1-4을 보면서 이해를 높이시길..

사용자 삽입 이미지
<그림 1-4>


이때, 가상 메모리 관리자는 메모리를 효율적으로 관리하기 위해 4KB 단위로 메모리를 관리하게 됩니다. 여기서 4KB를 1페이지(page)라고 합니다. 이 같이 프로그램이 메모리를 사용할 때 DRAM과 하드디스크에서 안 쓰는 공간을 페이지 단위로 취합해서 쓸 수 있도록 해주고, 프로그램이 종료되거나 프로그램이 일정 부분의 메모리를 더 이상 쓰지 않아 운영체제에 되돌려 줄 때 다른 프로그램이 사용할 수 있도록 할당한 메모리를 해제하는 역할을 하는 것이 바로 가상 메모리 관리자입니다.

이렇게 5만 바이트를 할당받아 hello.exe 전체를 할당받은 메모리에 전부 복사합니다.
그런데 복사한 5만 바이트 중에 현재 운영체제의 재정 상태와 건강 상태를 알맞게 실시간으로 고쳐 주어야 하는 값들이 있습니다. 이 값들을 채워주면 실행 준비 완료됩니다. 운영체제는 이 프로그램을 실행시키기 위해 CPU 컨텍스트를 한 개 준비 합니다.

CPU 컨텍스트는 동시에 여러 작업을 하기 위한 방법 중 멀티쓰레딩에 해당한다는 것을 기억하기 바랍니다.

CPU 컨텍스트 중에 실행할 명령어가 담긴 주소를 담는 레지스터가 IP(Instruction Pointer)라고 기억해 두길 바랍니다. 네트워크 용어인 IP Address의 IP가 아닙니다.
다만 요즘 CPU들은 64bit, 32bit이기 때문에 Extended를 붙여 EIP(Extended Instruction Pointer) 라고 부릅니다. EIP 라고 부르니 네트워크 용어와 좀 더 차별화되어 보이나요? 그렇담 헷갈리지 않을 것이니 다행이라 생각합니다.

PE 파일 형식에는 실제 컴파일해서 나온 기계어가 위치한 첫 번째 주소가 적혀 있는 지점이 있습니다. 이 값을 읽어 온 뒤에 EIP 레지스터에 집어넣고 실행 풀(Pool)에 CPU 컨텍스트를 올려놓으면 실행됩니다. 이 작업을 해 주는 것이 Windows에서 프로그램을 실행시키는 명령어인 CreateProcess입니다.

읽어주셔서 감사합니다.


참고 자료 - MSDN, An In-Depth Look info the Win32 PE File FOrmat - Part 1
참고 도서 - 해킹, 파괴의 광학(출판사 : 와이미디어, 김성우 저자)
  1. 그것은 당신이 기부 버튼이없는 유감이야! 나는 의심 할 여지없이 환상적인 블로그에 기부거야! 난 지금의 나는 책 표시하고 내 Google 계정에 RSS 피드를 추가하기위한 해결하자. 나는 새 업데이트를 기대와 페이스 북 그룹과이 블로그를 공유합니다. 빨리 말해!

  2. BlogIcon healthy weight loss plan 2013.01.15 15:54 신고

    멋진 게시물! 우리는 우리의 웹 사이트에이 특히 큰 문서에 연결됩니다.좋은 글을 계속.

+ Recent posts