PE 관련 퀴즈~
PE (윈도우용 실행 파일 포맷) 관련 코드를 작성하다가 지금까지 몰랐던 사실을 하나 알게되어 지금까지 나만 그랬던 것인지 알고싶어, 퀴즈를 하나 내보려고 합니다. ^^;;

보통 PE 를 다루는 코드는 아래의 코드 처럼
CreateFile() -> CreateFileMapping() -> MapViewOfFile()
API 를 호출해서 file 에 대한 포인터를 구한다음 어쩌구 저쩌구 처리를 하죠.

지금까지 저도 별 생각없이 이런식으로 처리했었는데요. 오늘 유닛테스트를 하다가 심각한 문제가 있음을 깨달았습니다.
Exception 에 의한 crash 가 발생할 수도 있는 버그입니다.
데체 뭘까요?
( SafeHandle 객체는 그냥 resource 관리를 위한 객체이니 신경쓰지 마시구요 ^^)

ps. 최초로 정답을 맞추시는 분께는 ... 박수쳐드릴께요. -_-;;



BOOL CheckPE(LPCTSTR path)
{
HANDLE hFile = CreateFile(
(LPCTSTR)path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hFile) return FALSE;
SafeHandle sfFile(hFile);

HANDLE hImageMap = CreateFileMapping(
hFile,
NULL,
PAGE_READONLY,
0,
0,
NULL);
if (NULL == hImageMap) return FALSE;
SafeHandle sfMap(hImageMap);

PBYTE ImageView = (LPBYTE) MapViewOfFile(
hImageMap,
FILE_MAP_READ,
0,
0,
0);
if(ImageView == NULL) return FALSE;
SafeView sfView(ImageView);

// IMAGE_DOS_HEADER check
//
PIMAGE_DOS_HEADER idh = (PIMAGE_DOS_HEADER)ImageView;
if(idh->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;

// IMAGE_NT_HEADERS check
//
PIMAGE_NT_HEADERS inh = (PIMAGE_NT_HEADERS) ((DWORD_PTR)idh + idh->e_lfanew);
if (IMAGE_NT_SIGNATURE != inh->Signature) return FALSE;

// blah..blah..
//
}





jz 님께서 답을 맞추주셨습니다. ~~ 짝짝 ~~ 박수~~
아래는 수정된 코드 입니다.

CHECK #1 에서는 이 파일이 만일 PE  파일의 최소 사이즈를 검사합니다.
대상이 PE 라면 적어도 IMAGE_DOS_HEADER 크기 보단 클테니까요.

CHECK #2 에서는 dos file 사이즈를 검사합니다.
e_cp 는 파일이 차지하는 페이지의 수이고, e_cblp 는 마지막 페이지의 바이트 수입니다.
따라서 PE 라면 (IMAGE_DOS_HEADER::e_cp * PAGE_SIZE)  이상의 크기이어야 합니다.

CHECK #3 에서는 e_lfanew 오프셋에 대한 유효성 검사를 합니다.
당연히 해야겠죠. :-)

예전에 작성한 코드엔 저런 예외처리 코드가 없었는데, 심심이 코드를 보니 다 처리하고 있었더군요.
매번 까먹는 제 머리가 문제였던것이군요. -_-;;



BOOL CheckPE(LPCTSTR path)
{
HANDLE hFile = CreateFile(
(LPCTSTR)path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hFile) return FALSE;
SafeHandle sfFile(hFile);

// CHECK #1 file size
// - 최소한 IMAGE_DOS_HEADER 보단 클 테니 ..
//
LARGE_INTEGER fileSize;
if (TRUE != GetFileSizeEx(hFile, &fileSize)) return FALSE;
if (sizeof(IMAGE_DOS_HEADER) > fileSize.QuadPart) return FALSE;

HANDLE hImageMap = CreateFileMapping(
hFile,
NULL,
PAGE_READONLY,
0,
0,
NULL);
if (NULL == hImageMap) return FALSE;
SafeHandle sfMap(hImageMap);

PBYTE ImageView = (LPBYTE) MapViewOfFile(
hImageMap,
FILE_MAP_READ,
0,
0,
0);
if(ImageView == NULL) return FALSE;
SafeView sfView(ImageView);

PIMAGE_DOS_HEADER idh = (PIMAGE_DOS_HEADER)ImageView;
if(idh->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;

// CHECK #2 check DOS file size
//
DWORD dosSize = (idh->e_cp * 512);
if (dosSize > fileSize.QuadPart) return FALSE;

// CHECK #3 check e_lfanew offset
//
if (TRUE == IsBadReadPtr((idh + idh->e_lfanew), sizeof(DWORD)/* sizeof (IMAGE_NT_SIGNATURE) */)) return FALSE;

PIMAGE_NT_HEADERS inh = (PIMAGE_NT_HEADERS) ((DWORD_PTR)idh + idh->e_lfanew);
if (IMAGE_NT_SIGNATURE != inh->Signature) return FALSE;

// blah..blah..
//
}


이 글과 관련있는 글을 자동검색한 결과입니다 [?]

by somma | 2009/03/11 00:14 | 시스템 프로그래밍 | 트랙백(1) | 덧글(20)
트랙백 주소 : http://somma.egloos.com/tb/4086171
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from 미친감자의 BLOG at 2009/03/11 09:48

제목 : [PE]DOS대가리의 취약점
PE 관련 퀴즈~미친감자는 PE 대가리를 가리키고 있지만. 이 PE 대가리를 가리키기 위해서는 DOS대가리의 e_lfanew 을 참고해야한다.그런데, 이값이 너무 커버리면. PE를 LOAD하다가 Crash가 발생할 수 있다.[참고URL]http://zesrever.xstone.org/55http://micingamja.egloos.com/4566402...more

Commented by jh0x4s at 2009/03/11 07:49
정렬값과 관련된 SECTION 배치문제인가요? 예전에 SEC_IMAGE 지원여부가 불투명해 저는 수동으로 배치했었는데.
Commented by somma at 2009/03/11 07:57
땡~~ ㅎㅎㅎㅎ
언제 오픈하냐..블로그..
Commented by uriel at 2009/03/11 07:54
심각한 것은 아니고, if(ImageView == NULL) return FALSE; 을 할 시점에서 앞에서 파일 핸들을 열어 놓은 상태네요. 파일 핸들을 닫고 return 해야 할 듯 합니다.

근데 심각한 문제라니까 이건 아닐 것 같고..
Commented by somma at 2009/03/11 07:57
땡 ~

참고로, SafeHandle sfFile(hFile);
요 코드에서 SafeHandle 이 클래스 인데 소멸자가 호출되면서 자동으로 CloseHandle() 을 호출해 주기때문에 핸들에 관한 leak 은 전혀 없습니다. ^
Commented by uriel at 2009/03/11 07:59
혹시 해당 pe image가 이전에 다른 permission으로 이미 메모리에 로딩 된 경우에 제대로 안읽혀지려나요..
Commented by somma at 2009/03/11 08:00
공유 위반에 관한 문제는 일단 퀴즈내용과는 상관없습니다. ^^;
거의 실시간 답글이죠 ??
Commented by jz at 2009/03/11 08:08
idh->e_lfanew 를 검사한 뒤 참조해야합니다.... 엄청 큰 값이 들어가면 크래쉬~!
Commented by somma at 2009/03/11 08:22
짝짝짝~ 벌써 답이 나왔네요~
그나 저나 - 다들 엄청 일찍 출근하시네요 >.<
Commented by 미친감자 at 2009/03/11 09:32
음..그렇군요...idh->e_lfanew 는 PIMAGE_NT_HEADERS의 위치이니...그리고 4바이트를 가리키고 있으니....4기가나 가리킬수 있으니 validation 채크를 해줘야 겠군요. 약 2G보다 작은값인지 ...
Commented by somma at 2009/03/11 15:21
idh->e_lfanew 는 base 로부터의 offset 이니까, 매핑된 파일의 base + e_lfanew 의 주소가 유효한지만 검사하면 됩니다. ^^
Commented by 미친감자 at 2009/03/13 13:08
그러게요^^
Commented by 마로 at 2009/03/11 14:52
체크를.. 한다면. 2G보다 작은 값 보다는.. 읽어들인 파일의 크기보다 작은 값이 낫지 않을까요 ;ㅅ;
설마 헤더가 파일 크기보다 멀리 있기야 하겠어요... ;ㅅ;
Commented by 미친감자 at 2009/03/13 13:10
음...파일 크기보다 큰 위치로 정말 안갈까요????
음...그럴것 같네요..........
Commented by jz at 2009/03/11 16:34
박수 감사합니다 ㅋㅋ

e_lfanew 는 꼭 sizeof(IMAGE_DOS_HEADER)보다 클 필요는 없습니다 ^^;

e_lfanew가 가리키는 NT헤더가 MZ 바로 뒤에 와도 실행이 됩니다 ㅋㅋ 게다가

magic과 e_lfanew를 제외한 나머지 멤버의 값은 쓰레기로 채워도 실행이 잘 됩니다 하하

각 헤더별로 정확해야만 하는 요소가 있고 틀려도 상관없는 요소가 있어서

그걸 잘 섞으면 헤더 압축(;;)도 되지요.

이런 pe obfuscation 자체가 pe tool들에게는 상당한 anti가 됩니다^^

Commented by somma at 2009/03/11 16:37
그렇군요.
전에 외국의 어느 사이트에서 가장 작은 실행파일을 만드는 contest 비슷한걸 하는걸 봤었는데, 지금 생각해 보니 그때 그걸 잘 살펴봤더라면 많은걸 배울 수 있었겠네요.
시간나면 한번 만들어봐야겠네요.
재밌는거 알려주셔서 감사합니다. ^^
Commented by 미친감자 at 2009/03/13 13:12
음...그런 경시대회가 있었군요...지금도 url 살아 있을까요? 있으면 함 구경하고 싶네요
Commented by window31 at 2009/03/11 23:29
와보니까 이미 끝나 있네요 ㅎㅎ 짝짝짝 박수 !!
Commented by 지나가다.. at 2009/03/13 17:24
꼭 PE 퀴즈라고 보긴 그런데요~...
어떤 포맷이든(파일이든,패킷이든,등등)... parsing 들어가면 저런 유형의 에러처리가 필요한 경우가 생기죠~... 포인터의 디레퍼런스한 값에 대한 범위의 sanity체크라고 해야할 지...
Commented by somma at 2009/03/14 10:11
그렇죠. 코딩하다보면 당연히 해야 하는 것들인데 안하고 넘어가는 경우들이 많죠.
포인터 파라미터의 유효성 검사라든지, 버퍼 사이즈 검사등 대부분 귀찮아서 그냥 넘어가는 사소한 문제들이 나중에 엄청난 무한 짜증 디버깅을 해야 하게 만들곤 합니다 ㅜ.ㅜ
사실 사소한 것들이 아닌데도 말이죠.

코드 살펴보다가 젤 답답한것들 중에 하나가.. 리턴값 검사 안하는것. -_-;;
void ptr = malloc( some_size );
이렇게 호출하고, ptr 이 NULL 인지 검사하는 코드를 작성하는 분들이 의외로 찾기 어렵더군요.
자주 발생하는 문제는 아니지만 발생하는 경우가 분명히 있거든요. 저런 상황이 발생하면 크래시로 연결될 확률이 높은데도 말이죠.
이런 문제는 코딩을 잘 하고, 못하고, 많이 알고 모르고의 문제가 아니라 올바른 코딩 규칙/코딩 습관의 문제인듯 싶습니다.
Commented by n0fate at 2009/03/20 17:30
저도 아무생각없이 저렇게 IMAGE_NT_Header에 접근했었는데..큰일 날뻔했군요-_-;

까먹을지 모르니 미리 수정해 두어야 겠습니다-_-;

에고 세미나를 너무 늦게 알아버려서 참석을 못해 아쉽네요 ㅠ_ㅠ

소마님 블로그에 소홀한 제탓입니다 ㅠ_ㅠ

:         :

:

비공개 덧글



< 이전페이지 다음페이지 >