2014. 12. 2. 17:25

DLL Export 함수가 Entry Point를 공유하는 경우 (SetHandleCount, LockResource)

본 포스트를 확인하기 전,

[reverse engineering] - PE 파일을 disassembly하여 CALL 명령에 연결된 Win32 API 찾기 (IAT, Import Address Table)

를 먼저 보신다면 도움이 되실 겁니다.


일단, 현재 몇몇 PE 파일에 대해 개별 분석 작업을 하다,

CALL [xxxxx]에 연결된 함수에서 충돌이 발생하는 것을 프로그램을 통해 확인하게 되었습니다.


대표적인것이, kernel32.dll의 SetHandleCount와 LockResource 입니다.

일단, XPSP3 기준에서 kernel32.dll의 dependency를 보면 아래와 같습니다.

위 그림과 같이 LockResource와 SetHandleCount의 Entry Point가 같은 것으로 나옵니다.

만일, 코드에서,

     ::SetHandleCount(....)

     ::LockResource(...)

를 호출하였다면,

     CALL [SetHandleCount] 예) CALL [401240]

     CALL [LockResource] 예) CALL [401244]

와 같이 변환됩니다.


문제는, [401240]과 [401244]가 가리키는 값이, (7C800000 + CD27)로 같은 값을 가리킨다는 뜻입니다.

(7C800000은 아래와 같이, kernel32.dll의 Image Base입니다.)


뭔가 이상합니다. Win32 API, 즉, 함수는 분리되어 있는데, 다른 함수인줄 알고 호출했지만, 실제 함수의 body는 동일해서, 결국, 같은 함수를 호출한 결과가 발생한다는 점입니다.


그래서, 좀 이상하다 싶어, 해당 Export 정보를 csv로 변환하고, 엑셀에서 중복된 정보를 찾아 보았습니다.

그러니, 아래와 같은 함수들이 Entry Point를 공유하고 있었습니다(XPSP3 기준).


 공유하는 함수

 Entry Point

 lstrcmpi, lstrcmpiA

 0x0000BB31

 lstrlen, lstrlenA

 0x0000BE46

 lstrcpy, lstrcpyA

 0x0000BE91

 LockResource, SetHandleCount

 0x0000CD27

 lstrcpyn, lstrcpynA

 0x000101A1

 GetEnvironmentStrings, GetEnvironmentStringsA

 0x0001CC7B

 GetSystemWow64DirectoryA, GetSystemWow64DirectoryW, SetHandleContext 

 0x00021454

 lstrcmp, lstrcmpA

 0x00030D64

 lstrcat, lstrcatA

 0x00034D59

 _hread, _lread

 0x000353E6

 FindCloseChangeNotification, FindVolumeMountPointClose

 0x000357ED

 ReplaceFile, ReplaceFileW

 0x00036C54

 _hwrite, _lwrite

 0x00038AFF

 GlobalCompact, LocalCompact

 0x0005F578

 GetBinaryType, GetBinaryTypeA

 0x0006900B


생각보단 많습니다.다만, ~~~A, ~~~ 함수 같이, ~~~A, ~~~W, ~~~ 인 경우, ~~~A와 ~~~는 같은 함수를 의미하는데, A를 쓰든, 안쓰든, 같은 함수로 유도하기 위해, Entry Point를 공유한 것으로 추측됩니다. 


제가 알고 있기로는, ~~~A, ~~~W와 같이 A,W를 사용하지 않고 ~~~만 사용하면 #ifdef UNICODE에 따라 A인지 W인지 결정됩니다. 즉 아래와 같습니다.

#ifdef UNICODE

#define ReplaceFile  ReplaceFileW

#else

#define ReplaceFile  ReplaceFileA

#endif // !UNICODE

#endif // (_WIN32_WINNT >= 0x0500)

만일, 코드에서 ::ReplaceFile(....) 한다면, UNICODE define에 따라 ReplaceFileW 혹은 ReplaceFileA가 호출될 것입니다. 그런데, 만일, pfnReplaceFile = ::GetProcAddress(hKernel32, "RepleaceFile"); 해서 얻은 결과를 호출한다면, (보통 상식으로는 ~A, ~W 이외는 Export안되어 있다고 하겠지만) ~A가 호출될것 같지만, 실제로는 ~W 함수가 호출된다는 점입니다. 만일, 그 방식으로 한다면, 반드시 WCHAR 형식이 와야 될 겁니다. 즉, (*pfnReplaceFile)(lpszPathW, ....)와 같이 호출해야 한다는 점입니다(lpszPathW는 LPCWSTR 형식).


FindCloseChangeNotification와 FindVolumeMountPointClose에 대해, MSDN을 보니, 대략 같은 내용일듯 하네요. 이 두개의 함수는 각각 호출해도, 결국 같은 body를 실행하게 됩니다.


_hread, _lread, _hwrite, _lwrite등은 딱히 document되어 있지 않아 확인은 안되네요. 하지만, 이들 역시, 함수명은 달라고, 결국 같은 내용일 것입니다.


GetSystemWow64DirectoryA, GetSystemWow64DirectoryW는 X64전용 함수인데, 일단, X86 XP에서 Export는 시켜주고 있는거 같습니다. 위 제시한 표는 XPSP3(X86)인데, XPSP3 X64에서 확인하면,

와 같이 X64 XP에서는 다른 Entry Point를 가지고 있습니다. X86에서는 되도록 호출하면 안되겠죠?

엉뚱하게 SetHandleContext라는 함수가 끼어 있는데, X64에서는 다른 값으로 되어 있습니다.


그렇다면, LockResource와 SetHandleCount를 알아볼까요?

우선, Win7에서 확인하면, 아래와 같이, 분리되어 있습니다.

일단 복잡하니, Win7은 나중에 검토하기로 하고...


우선, LockResource와 SetHandleCount에 대해 MSDN을 찾아보면, LockResource는 Resource 정보를 Lock한다고하며, SetHandleCount는 과거 16-bit windows 호환성을 위해 명목상으로 Export한 함수라는것이 확인되었습니다. 그래도, 서로 관계가 전혀 없어보이는 LockResource 함수와 SetHandleCount를 함께 동거시키고 있다는 것은, 쉽게 이해되지는 않습니다(MS 관계자만이 아실듯).


LockResource 함수를 가지고 한번 디버깅하고 놀라운 사실을 발견하게 되었습니다. (Win 7에서 테스트)

int _tmain(int argc, _TCHAR* argv[])
{
	LockResource((HGLOBAL)0x1234);
}

위 코드를 빌드한뒤, ollydbg로 실행해 봅니다.


일단, CALL __security_init_cookie 부터 시작합니다.

Visual C++ stub code를 건너뛰기위해, scrollbar를 최상단으로 올려, break point를 건 다음 실행시킵니다.


PUSH 1234 다음 LockResource가 CALL됨이 확인됩니다.

따라 들어가 보면, 함수 Argument를 그냥 Return해주는 함수임이 확인되었습니다.


LockResource((HGLOBAL)0x1234); 대신 SetHandleCount(0xabcd)로 변경하여 테스트해 보았습니다.

역시 동작은 동일하였습니다.

즉, 위 함수로 호출됩니다. 내용은 LockResource와 동일합니다. 다만, 번지는 따로 존재합니다.

이것이 XP와 Win7의 차이점일듯 합니다. XP에서는 동일한 Entry Point로 되어 있어, 아예 동일한 위치를 호출하게 되며, Win7에서는 위치는 동일하진 않지만, Copy-Paste가 이뤄진, 내용이 동일한 함수가 호출되었습니다.

아래와 같이 EAX에 0xABCD가 들어감이 확인되며, 이후, 이 EAX가 함수의 리턴값으로 결정됩니다.


그렇다면, 다음은 어떨까요?

int _tmain(int argc, _TCHAR* argv[])
{
	printf("LockResource(0x1234) = 0x%x\r\n", LockResource((HGLOBAL)0x1234));
	printf("SetHandleCount(0xabcd) = 0x%x\r\n", SetHandleCount(0xabcd));
}

LockResource를 보면, HGLOBAL handle값을 전달해야 하는 것으로 나옵니다. 그리고, 그 역할이 분명이 명시되어 있습니다. 그런데, 위 코드를 실행하면 아래와 같습니다.

LockResource(0x1234) = 0x1234
SetHandleCount(0xabcd) = 0xabcd
계속하려면 아무 키나 누르십시오 . . .

즉, LockResource는 Argument를 그냥 리턴하는 단순한 함수임이 확인됩니다. SetHandleCount도 마찬가지고요.


본 포스트의 결론은 아래와 같습니다.


- DLL Export 함수들의 Entry Point가 공유되는 경우가 있다. 이 때에는, 다른 함수명이 동일한 함수 body를 공유하게 된다.


- LockResource와 SetHandleCount는 XP에서는 동일한 함수 body를 공유한다.


- LockResource에서 MSDN에서는 분명 역할이 있는 함수이지만, 실제로는 Argument를 리턴하는 단순한 함수일 뿐이다. 그러니, LockResource를 굳이 호출할 필요는 없다.