2013년 1월 31일 목요일

object namespace


이벤트, 뮤텍스, 세마포, 공유 메모리등은 모두 커널오브젝트입니다.
윈도우 API 중에 무언가 생성할 때 이름을 파라미터로 받아들이는 녀석이 있다면 모두 커널오브젝트를 생성하는 API 라고 생각하면 됩니다.
다들 잘 아시는 것처럼 OS 는 이 커널오브젝트들을 관리하기 계층구조로 관리합니다. WinObj 같은 툴을 이용하면 쉽게 확인이 가능하죠.




이렇게 계층 구조로 커널오브젝트를 관리하기 때문에 오브젝트를 처리할 때는 네임스페이스처리를 잘 해주어야하는데요. Windows xp 까지는 콘솔세션은 세션 0, 원격데스크탑 세션은 세션 1, 세션 2 이렇게 세션이 생성되었습니다. ( 대부분의 사용자가 사용하는 ) 콘솔세션은 서비스와 어플리케이션이 동일한 세션에서 실행되었기때문에 별 문제가 없었습니다.

보통 이름있는 이벤트를 생성해서 서비스/응용프로그램간에 공유하는 경우 아래와 같은 코드가 문제없이 실행되었을 것입니다.
HANDLE event_handle = CreateEventW(
                          NULL, 
                          FALSE, 
                          FALSE, 
                          L"my_nmaed_event"
                          );
하지만 vista 이후에는 이런 코드가 정상작동하지 않을 것입니다.
vista 이후부터는 서비스 세션(세션 0)과 응용프로그램 세션(세션 1)이 분리되었기 때문이지요.



위의 코드는 \Sessions\응용프로그램이 실행된 세션번호\BaseNamedObjects 네임스페이스에 객체를 생성합니다.
아마 \Sessions\1\BaseNamedObjects\my_named_event 가 될 것입니다.
이 이벤트를 서비스에서 오픈해서 공유하려면 아래와 같은 코드를 사용하면 되겠죠.
HANDLE event_handle = OpenEventW(
                            EVENT_ALL_ACCESS,
                            FALSE, 
                            L"\\Sessions\\1\\BaseNamedObjects\\my_nmaed_event"
                            );
그런데 어플리케이션이 세션 2 에서 실행중이라면 어쩌죠? (원격데스크탑 등으로 접속해서 실행한 경우)
반대로 서비스에서 객체를 생성하면 항상 \Sessions\0\BaseNamedObjects\ 네임스페이스를 사용하니 되지 않겠느냐 라고 생각할 수도 있겠지만 그다지 좋은 해결책은 아니죠.
그래서 만들어진것이 Global 네임스페이스입니다.

Global 네임스페이스를 사용하면 세션에 구애 받지 않고, \BaseNamedObjects 네임스페이스를 사용하게 됩니다.
즉 아래 코드는 모두 \BaseNamedObject\my_named_event 객체를 생성하고, 오픈하게 됩니다.
따라서 서로 다른 세션에 있어도 오브젝트에 대한 접근이 가능하게 됩니다.
HANDLE event_handle = CreateEventW(
                          NULL, 
                          FALSE, 
                          FALSE, 
                          L"Global\\my_nmaed_event"
                          );
이렇게 오브젝트를 생성하고,
HANDLE event_handle = OpenEventW(
                          EVENT_ALL_ACCESS,
                          FALSE, 
                          L"Global\\my_nmaed_event"
                          );
이렇게 오픈해서 사용하면 됩니다.
만일 디바이스 드라이버의 경우 \BaseNamedObjects\my_named_object 에 접근하면 됩니다.
UNICODE_STRING us;
HANDLE   r0_handle;
PKEVENT   r0_object;
RtlInitUnicodeString(&us, L"\\BaseNamedObjects\\my_named_object");
r0_object = IoCreateNotificationEvent(&us, &r0_handle);


결론!
공유가 필요한 커널오브젝트를 사용하는 경우 오브젝트 네임스페이스 문제를 고려하자.
일반적으로 Global 네임스페이스를 이용하는게 편리하다.

2013년 1월 17일 목요일

Microsoft specific predefined macro


전체 목록은 여기에서 확인 할 수 있습니다. 자주 사용하는 내용인데도, 매번 헤깔리네요.


  • __cplusplus     : C++ 파일인 경우 정의
  • _DEBUG          : /LDd, /MDd, /MTd 와 함께 정의 됨 (디버그 버전인 경우)
  • __FUNCDNAME__   : 함수 이름을 보여줌


// Demonstrates functionality of __FUNCTION__, __FUNCDNAME__, and __FUNCSIG__ macros
void exampleFunction()
{
 printf("Function name: %s\n", __FUNCTION__);
 printf("Decorated function name: %s\n", __FUNCDNAME__);
 printf("Function signature: %s\n", __FUNCSIG__);
 
 // Sample Output
 // -------------------------------------------------
 // Function name: exampleFunction
 // Decorated function name: ?exampleFunction@@YAXXZ
 // Function signature: void __cdecl exampleFunction(void)
}


  • _M_AMD64, _M_X64    : x64 프로세서인 경우 정의 됨
  • _M_IX86             : x86 프로세스인 경우 정의 됨
  • _M_IA64             : IA64 (인텔 아이테니엄) 프로세서인 경우 정의 됨

2013년 1월 14일 월요일

LNK4197 경고 해결하기

1>afc_scm.obj : warning LNK4197: export 'send_command' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'stop_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'start_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'uninstall_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'install_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'free_scm_context' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'create_scm_context' specified multiple times; using first specification
32비트에서는 아무런 문제없이 빌드되던 프로젝트가 오늘 보니 4197 링크 경고를 뿌려대고 있었습니다.
http://support.microsoft.com/kb/835326/en-us 페이지에 경고에 대해서 설명된 내용이 있네요.

결론은 dllexport 를 사용하던지 def 파일을 사용하던지 하나만 하라는건데요...

문제가 좀 있어보입니다.
def 파일을 사용하지 않고, __declspec(dllexport) 만 사용하는 경우 __stdcall 호출방식을 사용할때 네임맹글링 문제로 인해서 원치않는 형태의 이름으로 export 되지요.
아래 그림처럼요...

그렇다면 def 파일을 이용해서 dll export 를 하면어떨까요?
이건 제 경우에만 문제가 될 수 있는 문제인데요. 저는 dll 을 만들때 아래와 같은 구조의 코드를 항상 사용합니다.
#ifdef AFC_SCM_EXPORTS
 #ifdef __cplusplus
  #define AFC_SCM_API   extern "C" __declspec(dllexport) 
  #define AFC_SCM_CLASS  __declspec(dllexport) 
 #else
  #define AFC_SCM_API   __declspec(dllexport) 
 #endif//__cplusplus
#else
 #ifdef __cplusplus
  #define AFC_SCM_API   extern "C" __declspec(dllimport)
  #define AFC_SCM_CLASS  __declspec(dllimport) 
 #else
  #define AFC_SCM_API   __declspec(dllimport)
 #endif//__cplusplus  
#endif//AFC_SCM_EXPORTS

AFC_SCM_API 
scm_ctx
__stdcall
create_scm_context(
 _In_z_ const wchar_t* driver_path, 
 _In_z_ const wchar_t* service_name, 
 _In_z_ const wchar_t* service_display_name,
 _In_ bool uninstall_service_on_free
 );
AFC_SCM_API 가 정의되어있는 경우 (dll 프로젝트 자체) create_scm_context() 함수는 dllexport 가 됩니다. 그렇지 않은 경우  create_scm_context() 함수는 dllimport 가 되죠. 즉 dll 을 묵시적으로 링크하는 프로젝트가 되겠죠.
dll 사용자는 dll, lib, header 파일만 있으면 별도로 손대지 않고, 그냥 dll 을 나름 편하게 사용할 수 있습니다. def 파일을 이용해서 dll 을 만드는 경우 이런 이점을 버려야 할것 같네요. (별도로 dllimport 를 해주거나 명시적 링킹을 해야 하니까요...)

대체 MS 는 왜 이런 짓을 하는거야..ㅆㅂ ㅆㅂ 하면서 잠시 생각해보니 나이스한 해결책이 있습니다. ( 사실은 해결책이 아니라 제가 바보였던거죠. )

x86 프로젝트에서는 예전처럼 dllexport 와 DEF 파일을 모두 사용하고, x64 프로젝트에서는 def 만 사용안하면 그만입니다. x64 는 호출규약이 하나뿐이라 네임맹글링 문제가 없어서 def 와 dllexport 를 함께 사용하지 않도록 (불필요한 실수를 줄이기 위해) 가이드하는거 같습니다... (아님 말구요)

결론: dll 을 만들때는
- dllexport 를 이용하고, 
- 32 비트에서 네임맹글링 문제가 있다면 def 파일을 함께 사용
- 64 비트에서는 그냥 dllexport 만 사용 (네임맹글링 문제가 아예 없다!)




2013년 1월 2일 수요일

windows api hooking 예제

BoB 에서 멘토링을 하면서 API 후킹에 대한 예제를 만들었는데요.
최대한 이해하기 쉽게 심플하게 만들었습니다. 
하드에 썩히는것보다는 공개하면 혹시 도움되시는 분들이 계실까? 하고 올려봅니다.