IT하는 참새
PE 03 본문
오늘 공부할 내용:
PE Header 에서
1. DOS Header
2. DOS Stub
3. NT Header
나머지 Section Header들은 다음에 하겠습니다!
(PE View프로그램을 켜주세요)
---------------------------------------------------------------------------------------------------------
1. DOS Header
이거는 DOS파일에 대한 호환성을 고려해서 만든것인데요.
그래서 이거를 확장시킨 구조체인 IMAGE_DOS_HEADER가 존재합니다
그냥 DOS Header라는 것을 IMAGE_DOS_HEADER라는 구조체로 만든것입니다.
이 구조체가 어떻게 생겼는지 우리는 알아야 합니다...!!
(직접한번 확인해보세요)
(저는 visual studio로 확인했습니다)
(1. #include <winnt.h> 하시고)
(2. winnt.h에 우클릭하셔서 open Document 하시고)
(3. 컨트롤+F 누르고 IMAGE_DOS_HEADER 하시면 따란~ 나옵니다)
이렇게 생겨먹었는데 우리가 당장 알아야할것은 e_magic, e_lfanew입니다.
BYTE = 1바이트
WORD = 2바이트
DWORD, LONG = 4바이트
알아두시면 정신건강에 좋겠습니다.
e_magic = 주석보면 매직넘버?? 라고 써있는데 얘는 그냥 고정값이라고 보면됩니다.
즉 이건 PE파일이야! 하고 도장쾅쾅 해놓은거죠
(있다가 확인해볼겁니다)
e_lfanew = 주석보면 뭐라써있긴한데 얘도 그냥 NT_Header가 존재하는 주소값을 가집니다.
(전 시간에 말했듯이 PE에서는 대부분 주소를 RVA형식으로 저장하고 있습니다)
PE View를 켜고, 아무런 exe파일을 끌어다가 던져보세요!
저는 메모장을 던져보았습니다. 그럼 저런 화면이 나올겁니다
추가설명
---------------------------------------------------------------------------------------------------------
좌측상단에 pFile은 파일에서의 Offset. (전 시간에 PE상태가 두가지 있다고 했죠?)
그 오른쪽에 RAW DATA는 그 주소에서의 데이터 값입니다.
(진짜 실제값이 들어있을 수도있고, RVA형식인 주소값이 있을수도 있습니다)
또한 다음단계로 나가기전에 알아둘 개념이있습니다.
쉬워요! 겁먹지마요!
Byte Ordering (바이트를 정렬하는 방식)
자세한 설명말고 저는 PE를 분석하는데 있어 필요한것만 말씀드리겠습니다
Big Endian
Little Endian
두 가지 방식이 크게 존재합니다.
우리가 볼건 Little Endian이에요.
intel x86 cpu에서는 Little Endian방식으로 데이터바이트를 저장합니다
얘는 자료가 역순으로 저장되는것을 의미합니다.
(다 그런건 아니에요. 예시 참고)
예를들어
(0x 즉 16진수로 숫자저장한 이유는 보기편하기 위해서도있고, 두자리쓰면 1바이트이기 때문임)
BYTE b = 0x12;
WORD w = 0x1234
DWORD dw = 0x12345678
char[] str = "abcde"
그러니까 이런 변수에 값이 있는데! 이게 컴퓨터 내부에 저장이될때는
b는 0x12가 저장되고
w는 0x3412가 저장되고
dw는 0x78563412가 저장되고
str는 abcde의 16진수값이 차례대로 저장된다는 겁니다.
즉 1바이트(BYTE, 문자열) 일때말고는 거꾸로 저장이 된다고 생각하면됩니다
--------------------------------------------------------------------------------------------------------- 추가설명 끝
---------------------------------------------------------------------------------------------------------
자 다시 사진을 한번 보고오시면
구조체에서 시작멤버는 e_magic이고 끝멤버는 e_lfanew네요
즉 시작주소부터 e_magic이라는 뜻이고 e_magic은 WORD(2바이트) 2칸을 차지하네요
보니까 e_magic = 0x4D5A 라는 값을 가지고 있습니다.
Value를 보니까 MZ 라네요.
이건 고정값입니다. 아! 얘는 PE파일이구나 이뜻으로 아시면 되겠습니다.
(왜 MZ라는 값인지는 검색해보면 유래가 나옵니다ㅋㅋ)
이제 e_lfanew멤버를 살펴보겠습니다. 얘는 어디있나요? File Offset을 찾아보세요.
(직접 계산해보세요. 구조체들의 크기따라서 e_lfanew는 이 주소에 있겠다.)
(주소읽는 법:
(pFile에 00000000, 00000010과 같이 나와있죠? 여기서 오른쪽으로 한칸씩 갈때마다
(00000000, 00000001, 00000002, 00000003, ~~~~~~~~ 0000000A ~~~ 00000000F)입니다
계산해보니 e_lfanew는 0000003C에 존재합니다!
e_lfanew의 크기는 얼마였죠? LONG 4바이트 였습니다.
그럼 4칸을 차지하겠네요
값을보니 D8 00 00 00이라고 저장되있습니다.
e_lfanew의 값이 무엇을 의미한다고했죠? NT_Header가 존재하는 주소를 가진다고했습니다.
그럼 D8000000이라는 주소를찾기위해 스크롤을 끝까지 내려봅시다....
(응 그런주소 절대안나와~)
아까 리틀엔디언 배웠죠? 다시생각해보세요
D8000000 vs 000000D8
네 000000D8이 맞습니다.
그럼 D8이라는 주소에 NT_Header라는놈이 존재하겠네요? (네.)
이렇게 DOS Header의 내용은 끝났습니다. (IMAGE_DOS_HEADER 라고해도 무관하다)
2. DOS Stub
얘는 사실 중요한 개념은 아닙니다. 그래도 뭐하는애인지는 알아야겠죠
컴파일러에서 지원해주는 옵션인생이기 때문에
없어도그만, 있어도 그만인 불쌍한 존재입니다.
(간단히말해서 16bit DOS환경, 32bit 환경에서 둘다 실행할 수 있게끔 하는 옵션코드)
사진 보이죠?
아까 IMAGE_DOS_HEADER의 마지막멤버가 3C부터 3F까지 썼죠? 그럼 다음에 연달아
00000040주소부터 DOS_Stub의 시작주소입니다.
그냥 알아두세요. 00000040~0000004D까지는 16비트 어셈블리 명령어인데
얘는 32비트 컴퓨터에서는 PE파일 일부로 인식하므로 명령어 실행이안되요!!
뭐 더 말하자면 할 수 있는데 진짜 지금으로썬 필요가없으므로 여기까지알고가겠습니다.
DOS_Stub끝~
3. NT Header
네.. 얘가 아주 중요합니다.
솔직히 1, 2번애들은 그냥 그렇구나 하는데 이건 그냥 그렇구나하면 안됩니다.
아까 NT Header의 위치는 누가 가지고 있다고했죠??
IMAGE_DOS_HEADER구조체의 e_lfanew입니다.
000000D8이라고 했으니 한번 가봅시다.
네 이렇게 있네요.
사실 NT_Header 얘도 구조체로 이미 만들어놨습니다.
우리는 구조체를 공부하면되요!! (아까 IMAGE_DOS_HEADER 찾은방식과 동일)
NT_Header 구조체이름 = IMAGE_NT_HEDAER
이따구로 생긴 구조체입니다. 기뻐할건 멤버가 3개밖에없네요?? 얄루~!
첫번째멤버: Signature
뭔지모르겠죠? 근데 시그니처... 라고하니 뭐 대표하는거같긴 하네요.
한번 봅시다.
첫번째멤버니까 IMAGE_NT_HEADER의 시작주소와 같겠죠?
D8부터 보면됩니다.
D8부터 DWORD(4바이트)크기만큼 보니까 무슨값이 있나요?
50450000 이라는 값이있네요.
(아스키 코드표 검색하셔서 16진수로 50, 45가 문자로 뭔지 확인해보시길 바랍니다.)
아! 그니까 IMAGE_NT_HEADER의 Signature멤버는 PE00이라는 값을 가지는구나! -끝-
두번째 멤버: FILE_HEADER
시작하기전에! 얘의 시작주소를 구해보세요. 직.접 계산해서
(Signature주소가 D8이였고 크기가4바이트니까 D8+4하면 DB. 즉 D8~DB까지가 Signature)
(FILE_HEADER는 DC부터겠네요)
얘는 음... 자료형이 WORD, DWORD 이런게아니라 IMAGE_FILE_HEADER 구조체형이네요??
그럼 IMAGE_FILE_HEADER가 뭔지 알면되겠죠?? (그럴겁니다. 다시생각해보세요)
(여태 찾아온방식으로 IMAGE_FILE_HEADER도 찾아봅시다)
이번에는 멤버가... 7개나 있네요. 걱정마세요! (다음꺼는 더많아요)
몇개만 살펴보겠습니다.
1. Machine
cpu별로 고유한 값을 나타냅니다
DC부터 WORD(2바이트)보니까 4C 01 즉 0x014C라는 값이 있군요.
얘는 또 winnt.h에 매크로상수로 예를들어
#define IMAGE_FILE_MACHIN_I386 0x014c 이렇게 정의되어있어요.. (이거 사진은 생략)
저거말고도 많이 선언이 되있습니다!
2. NumberOfSections
이거 중요합니다! Section의 개수 라는 뜻같이 보이네요
Section... 우리 본적있죠?? 그 Section이에요
일단 얘는 0보다 커야하고, 여기써있는 Section개수하고 실제 Section의 개수가 같아야 한다!
이정도만 알아둡시다
위에 사진보면 4C 01 다음에 04 00있잔아요? NumberOfSections의 영역이에요.
0x0004 즉 Section개수가 4개라네요 -끝-
3. SizeOfOptionalHeader
얘는 바로다음에나올 Optional Header의 크기를 의미합니다. (얘도 IMAGE_OPTIONAL_HEADER)
4. Chracteristics
단순합니다. 이 파일의 속성을 나타내는 값인데 bit OR 형식으로 구성되어있습니다.
(얘도 #define으로 상수가 정의되어 있습니다. 찾는법은 많이했으니 알아서...)
제 주소로는 EE에 있네요.
보면 EE주소에 02 01이라고 있으니까 저는 0x0102라는 값을 가집니다.
Bit OR 연산이라고 했죠?
0002, 2000값은 외워두면 좋습니다.
//위 사진 참조
0100
0002
-----
0102가 되었습니다.
나머지는 뭐 됐습니다!
TimeDateStamp는 파일의 빌드시간인데 내가 맘대로 바꿀수도있어서 안봐도되요 안중요함
-끝-
세번째 멤버: OPTIONAL_HEADER
얘도 구조체로 존재합니다 (IMAGE_OPTIONAL_HEADER)
멤버가 드럽게많아요
중요하고 자주쓰이는것만 봅시다. 저도 처음에 그렇게 공부했습니다
1. Magic
32비트인경우 값이 010B, 64비트인경우 020B값으로 세팅됩니다.
(주소로 직접가서 값 확인해보는건 이제 다 할 수 있을거라 생각합니다)
(사실 양이너무많아서 찍기 힘드네요...)
2. AddressOfEntryPoint
EP: Entry Point (프로그램이 실행되면 제일먼저 시작되는 주소, 위치) RVA값을 가지고 있습니다.
(중요하겠죠?)
3. ImageBase
네 저번에 세계수 비유했었죠??
더 정확히 말하면 우리 32비트 컴퓨터의 가상메모리 크기는
00000000~FFFFFFFF 입니다. (이유는 따로 검색해서 계산해보세요!)
이렇게 넓은메모리에서 딱! 뿌리내리는 시작주소를 의미합니다.
exe, dll같은파일은 user 영역인 00000000~7FFFFFFF
sys 같은 파일은 kernel 영역인 80000000~FFFFFFFF
보통 이렇게 로딩이됩니다. 그냥 알아만두세요.
4. SectionAlignment, FileAlignment
얘네는 같은 Alignment를 나타내지만 차이가 있습니다
PE설명하기전에 두가지 상태가 있다고했죠?
파일상태, 메모리상태 맞죠?
FileAlignment : 파일상태에서의 Section의 최소단위(배수)
SectionAlignment : 메모리올라간 상태에서의 Section의 최소단위(배수)
이건 사진으로 확인해보겠습니다.
저는 00000110 주소부터네요 (값은 리틀엔디언이죠)
SectionAlignment : 1000
FileAlignment : 200
자 그럼 예상해봅시다
SectionAlignment는 메모리에 실행시킨상태로 확인해야하는데 지금 그럴방도가 없으니 PASS
FileAlignment만 확인해봅시다. 이거확인되면 SectionAlignment도 맞겠죠ㅇㅇ
실제로 section1의 주소를 가보겠습니다.
400주소부터 Section1 (body)의 시작이네요.
그럼 FileAlignment가 200이랬으니 얘는 크기가 200씩 끊어져야겠네요??
200으로 나누어떨어져야함!
쭉쭉 내리다보니 section1의 영역이 끝나고 다음 section2 (body)가 나올겁니다. 밑에처럼
Section2 (body)의 시작은 AC00이네요.
그렇다면 section1은 400~ABFF까지 차지한다는 얘기가 되겠네요??
이제 AC00을 200으로 나누어봅시다. 나누어떨어지면 여태 제가 끄적인게 다 진실이 될겁니다.
(계산기 이용합시다. 프로그래머용으로 바꿔서 ㅇㅇ, Hex체크해주시고요, AC00/200 하면되요)
값이 56이 나왔습니다! 나누어떨어졌네요 증명완료. 다음 Section도 찾아서 해봐도 됩니다
5. SizeOfImage
얘는 PE파일이 실행되었을때 (메모리에 올라갔을때) 가상메모리에서의 파일 크기를 의미합니다.
(File상태에서의 크기, Memory올라갔을때의 크기 다릅니다~ 알아두세요)
6. SizeOfHeader
얘는 PE Header의 전체크기를 나타냅니다!(NULL Padding까지 포함해서)
이 값 또한 FileAlignment의 배수여야 합니다.
PE Header, PE Body로 나누어진다고했죠?
PE Body에는 NULL Padding과 Section (body)들이 있었고.
그럼 파일 처음주소에서 SizeOfHeader의 값만큼 떨어진곳에 Section1이 있겠네요? 굳
7. NumberOfRvaAndSizes = NumberOfDataDirectories
(구조체에 나와있는 이름) (PE View에 나와있는 이름)
얘는 지금당장 쓸곳은 없습니다. 하지만 PE구조 대장중 한명이라고 봐도 무방합니다.
DataDirectory배열의 개수를 나타냅니다.
지금은 이것만 알아둡시다.
실제로 이것들은
DataDirectory[0] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
.
.
.
뭐 많아요 나중에 하게될겁니다.
PE 03 끝
-----------------------------------------------------------------------------------------------------
다음이야기:
오늘 내용 엄청많고 힘들었을거 같아서 다음시간에는
이거보다는 양은적지만 중요도는 같은 Section Header에 대해 알아보겠습니다.