2013년 12월 16일 월요일

windows xp 에서 MSEC extension 사용하기


즐거운 연말을 보내기 위해 1주일 짜리 '소프트웨어 버그헌팅' 교육을 가게 되었습니다. 오호호~~
워드 프로세서 프로그램의 크래시를 일으키는 실습을 하던중에 예전에 '오 이런것도 있네' 했던 MSEC 확장이 생각났습니다. 

http://msecdbg.codeplex.com/

msec 은 MS 에서 배포하는 고마운 WinDbg 확장모듈로, 자동으로 크래시에 대한 분석을 수행하고, 이 크래시가 exploitable 한지 확인을 해줍니다. 
냉큼 다운로드 받고, WinDbg 로 로딩하려는데 계속 에러가 나는군요.


우선 depends 로 봤더니 msvcr11.dll, msvcp11.dll 이 필요하네요. visual studio 2012 로 빌드를 했나봅니다. visual studio 2012 redistributable package 를 설치해 주면 되겠죠?
(http://www.microsoft.com/en-us/download/details.aspx?id=30679)
재배포 패키지 설치 후 다시 로드를 해봐도 마찬가지로 에러를 내면서 로딩이 안됩니다. Orz



depends 로 다시 확인해 보니, Kenel32.dll 의 GetTickCount64 함수를 import 하고 있는데, msdn 에서 확인해보니 이 함수는 vista 부터 사용가능한 함수입니다. 
제 테스트 환경은 windows xp sp3 라서 dll 로딩이 실패할 수 밖에요.
어떻게 할까 한 10초 고민하다가 그냥 PE 의 import 테이블을 직접 고치는게 제일 편할것 같습니다. GetTickCount() 나 GetTickCount64() 나 뭐 정밀도의 차이가 좀 있을뿐 프로그램 돌아가는데는 별 문제 없으니, GetTickCount() 함수를 import 하도록 실행파일을 변경시켜 버리면 될것 같습니다.


100만년 전에 열심히 사용하던 LordPE 를 검색해서 다운로드 받고 실행합니다. (우와 PE+ 도 지원하고, 정말 오랜만이네요, 반갑다!)



IAT 를 고쳐야 하니까 'Directories' 버튼을 눌러주고요.





GetTickCount64 를 GetTickCount 로 변경해주면 됩니다.

이제 다시 !load winext/msec.dll 명령으로 extension 을 로드하고, !exploitable 명령을 실행해보면... 
오오옷! exploit 가능한 크래시군용~ 



 참 쉽죠? :-) (쉽긴 개뿔...-_-;;)




2013년 11월 11일 월요일

Visual studio 컴파일러 버전 별 처리하기

최근 Visual Studio 6 으로 개발을 하고 있는데, 여간 귀찮은게 아니네요. ㅠ.ㅠ

std::list 를 사용하려고 했더니 엄청나게 많은  warning 을 뱉어내기에 봤더니 


C:\Program Files (x86)\Microsoft Visual Studio\VC98\INCLUDE\list(131) : warning C4786: 'std::reverse_bidirectional_iteratorstringunsigned short>,std::allocator >,std::allocator
td::basic_stringunsigned short>,std::allocator > > >::const_iterator,std::basic_stringunsigned short>,std::allocator >,std::basic_string ,std::char_traits,std::allocator > const &,std::basic_stringunsigned short>,std::allocator > const *,int>' : identifier was truncated to '255' characters in the debug information

이런 경고네요. VS6 은 찾아보기 파일이나 디버그 정보에 255 자를 넘는 문자열을 처리못하나 봅니다. 
이런건 뭐 별수 없죠. 워닝을 끄는 수 밖에요. 


아래 코드를 무식하게 stdafx.h 에 넣어두었습니다. 




#if defined(_MSC_VER) && _MSC_VER < 1300
#pragma warning (disable: 4786)
#endif

참고로 _MSC_VER 매크로 값은 아래와 같다고 합니다.





1000 : Visual C++ 4.x (4.0)
1100 : Visual C++ 5    (5.0)
1200 : Visual C++ 6    (6.0)
1300 : Visual C++ .NET (7.0)
1310 : Visual C++ .NET 2003 (7.1)
1400 : Visual C++ .NET 2005 (8.0)
1500 : Visual C++ .NET 2008 (9.0)
1600 : Visual C++ 2010 (10.0)
1700 : Visual C++ 2012 (11.0)


끝!!

2013년 9월 11일 수요일

커널모드 유닛테스팅 프레임웤 활용하기

간단하게 커널모드 코드를 테스트 할 수 있는 방법에 대해서 설명할까 합니다.

응용프로그램 레벨에서는 사용할 수 없는 인스트럭션 (예. mov eax, cr0 )들에 대한 테스트가 필요한 경우도 있고, 간단한 커널모드 코드 조각을 테스트해야 하는 경우도 있고, 커널모드 안티디버깅 같은거 테스트 할 때... 필요한 상황이야 많습니다.

대부분의 응용프로그램 개발자들은 커널모드 프로그램 개발에 대해서 알지 못하고, 간단한 테스트 하나 하자고, 두꺼운 책들과 씨름하기엔 너무 귀찮고, 좀 어렵죠?

좀 쉽게 할 수 있는 방법이 없을까요? (있으니까 이런 글을 쓰고 있겠죠.)
유닛테스트 프레임웤 중에 cfix 라는 녀석은 윈도우즈 커널모드 코드에 대한 유닛테스팅을 지원합니다. 예전에도 몇번인가 제 블로그에서 다뤘던거 같은데요.

cfix 를 이용해서 테스트할 커널 코드를 작성하고, 프레임웤을 이용해서 실행해보면 되는거죠.  드라이버를 실행하기 위한 서비스 등록/시작/중지/제거기능, 드라이버 구현을 위한 기본 코드따위는 프레임웤이 알아서 다 해줍니다.

우린 그냥 프레임웤에 테스트할 코드만 넣어주면 땡입니다. 아싸~

우선 여기에서 cfix 프레임웤을 받아서 설치합니다.
홈페이지에 보면 Visual Assert 와 cfix 두 가지가 있는데 읽어보시면 알겠지만 visual assert 는 cfix 를 기반으로 만들어진 visual studio 용 플러그인입니다.

둘다 사용법은 같으니 아무거나 설치해도 됩니다만 나중에 visual studio 에서 유닛테스트를 사용할 때도 쓰려면 visual assert 를, 드라이버 테스트만을 위한거라면 cfix 를 설치하시면 됩니다.

설치할때 주의할 점은 설치 경로에 공백문자나 한글이 있는 경우 커널모드 유닛테스트를 사용할때 문제가 발생 할 수 있습니다. 그냥 C:\VisualAssert 에 설치하는게 가장 좋습니다.


C:\VisualAssert 아래 Doc, example 에 보면 필요한 내용은 다 있습니다. 그거 참고해서 알아서들 하세요... 라고 하고 싶... :-)

C:\VisualAssert\examples\KernelMode 에 보면 우리에게 필요한 모든게 다 있습니다.



suite.c 파일에 있는 주석들 대충 읽어보면 뭐가 뭔지 알거에요. 나중에 읽어보시고, 아래처럼 간단하게 고쳐봅시다.

#include 

static void __stdcall Test1()
{
    // CR4 레지스터에 접근해 볼까요?
    unsigned long _cr4=0x00000000;
    __asm 
    {
        mov eax, cr4
        mov _cr4, eax
    }
    
    CFIX_LOG(L"cr4 = 0x%08x", _cr4);
}

CFIX_BEGIN_FIXTURE( MyFixture )
 CFIX_FIXTURE_ENTRY( Test1 )
CFIX_END_FIXTURE()

이제 드라이버 코드를 빌드해야 겠죠? 당연히 드라이버 빌드를 위해서는 DDK가 설치되어있어야 합니다. 우선 32 비트 xp 에서 테스트 할 것이므로 아래 그림처럼 WindowsXP \ x86 checked Build Environment 를 선택합니다.
당연히 디버깅을 할 상황도 생길 수 있기때문에 checked build 를 선택하는게 좋겠죠?



MakeFile, Sources 파일이 위치한 경로로 이동해서 "build -ceZ" 명령을 내려주면 됩니다.

빨간 부분으로 표시한 곳을 보면 에러가 발생했네요.
잘 읽어보면 에러를 워닝으로 간주하도록 설정이 되어있어서 워닝 -> 에러가 된 상황이죠. ( 간혹 워닝은 워닝이니까 하고, 무시하는 님들이 계신데, 워닝은 에러... 라고 생각하고, 미주알 고주알 따져서 반드시 다 없애주는 버릇을 들여줘야 합니다. 무시한 워닝 하나로 인해 정말 찾기 힘든 버그 만들어서 개고생 하는 사람들 많이 봤습니다 )

워닝 메세지를 보니 cr4 라는 레이블을 인식못한다는 군요. 이것은 DDK 에 내장된 어셈블러가 'cr4' 문자열을 레지스터 이름으로 인식하지 못하고, 일반 문자열로 인식하기 때문이겠죠.

mov eax, cr4 명령을 기계어로 넣어주거나, __readcr4( ) 함수를 이용하면 됩니다. 자세한건 인터넷 찾아보시고요. 아래 처럼 코드를 수정해 주시고, 다시 build -ceZ 명령을 해봅시다.

static void __stdcall Test1()
{
    // CR4 레지스터에 접근해 볼까요?
    unsigned long _cr4=0x00000000;
    __asm
    {
        _emit 0x0f   ; mov eax, cr4
        _emit 0x20 
        _emit 0xe0   
        
        mov _cr4, eax
    } 
    
    CFIX_LOG(L"cr4 = 0x%08x", _cr4);
}


아 ㅅㅂ 또 에러인데, 이번도 warning 때문에 발생한 문제네요. 그런데 워닝 메세지도 없습니다. ㅠ.ㅠ

build 명령을 내리면 현재 MakeFile 이 있는 디렉토리에 buildXXX_XXX_XXX.wrn/err/log 파일이 생성되는데 각 파일들은 워님, 에러, 로그를 담고있습니다.
wrn 파일을 열어보면 아래와 같은 내용이...

1>c:\visualassert\examples\kernelmode\cl : warning D9035 : option 'Wp64' has been deprecated and will be removed in a future release
1>c:\visualassert\examples\kernelmode\suite.c : warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss

대충 요약해 보면 첫 번째 라인은 64 비트 호환모드 옵션(/Wp64)때문인데 아마 vs 2003 이후 컴파일러부터는 필요없는 옵션일겁니다.
두 번째는 소스 파일 인코딩이 cp949 가 아니라는 거죠. 외국에서 만든거니 코드페이지가 달라서 발생한 문제입니다.

다 귀찮으니 SOURCES 파일의 컴파일 옵션을 아래처럼 수정하고, 다시 빌드합니다.
( 64 비트 호환모드 체크 안함 / 워닝을 에러로 처리 안함 )

# MSC_WARNING_LEVEL=/W4 /WX /Wp64
MSC_WARNING_LEVEL=/W4


그대로 따라했다면 " C:\VisualAssert\examples\bin\chk\i386\kern.sys/pdb/obj/lib " 같은 파일들이 생겼을겁니다. 젠장, 이제 겨우 빌드에 성공했군요. -_-;

이제 빌드 한 커널드라이버를 실행하기 위해서 가상머신에 cfix 프레임웤을 설치해야 합니다. 사실 개발 머신에는 cfix 프레임웤을 설치할 필요는 없으나 예제 코드를 그대로 사용하려고 했던거고요.

정말 중요한건 드라이버를 실행할 PC 에 cifx 프레임웤을 설치해야 하는거죠.
가상머신에 visual studio 가 설치되어있지 않은 경우 visual assert 를 설치하려하면 오류가 나고, 설치가 중단됩니다. 당연히 visual assert 는 visual studio 플러그인이니까요. 홈페이지에서 cfix 를 다운로드 해서 설치하거나 " c:\VisualAssert\bin\i386 " 폴더 아래의 파일들을 가상머신의 적당한 경로에 복사합니다. 편한대로 하세요~

저는 가상머신의 c:\dbg 폴더 아래에 복사했고, 빌드 할 실행파일 kern.sys 도 같은 경로에 복사했습니다. c:\dbg\cfix32.exe -kern kern.sys 명령을 실행하면 아래처럼 우리가 작성했던 코드의 결과를 확인할 수 있습니다.


참~ 쉽죠~?! ^__^

2013년 2월 5일 화요일

MASM 팁

어셈블리 뻘짓 하던 내용을 정리해서 포스팅합니다.
나중에 또 뻘짓할게 뻔하기에... @,.@


.686
.model flat, StdCall
option casemap:none

.code

; int __stdcall test_sum(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret
test_sum ENDP

; int __stdcall test_sum2(int _param1, int _param2)
test_sum2 PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret 08h
test_sum ENDP

end

이렇게 코드를 작성하면 매크로 어셈블러가 자동으로 prologue, epilogue 를 생성해주기 때문에 test_sum, test_sum2 함수는 모두 아래와 같은 동일한 코드로 변환됩니다.

push ebp
mov  esp, ebp
mov  eax, dword ptr [ebp+08h]
add  eax, dword ptr [ebp+0ch]
leave
ret  08h

어셈블리 코드에서 test_sum 함수는 ret 명령의 경우 파라미터 스택 사이즈를 지정하지 않았지만 ret 08h 로 자동으로 지정해주죠. 편합니다.

하지만 때때로 prologue / epilogue 를 자동생성하지 않게 해야 하는 경우도 있는데 이때는  option prologue, epilogue 로 함수 부분을 감싸주면 됩니다. 아래 test_sum3 함수처럼요.

.686
.model flat, StdCall
option casemap:none

.code

; int __stdcall test_sum(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret
test_sum ENDP

; int __stdcall test_sum2(int _param1, int _param2)
test_sum PROC StdCall _param1, _param2
 mov eax, _param1
 add eax, _param2
 ret 08h
test_sum ENDP


option prologue:none
option epilogue:none

; int __stdcall test_sum3(int _param1, int _param2)
test_sum3 PROC Stdcall _param1, _param2
 ; build stack frame
 push ebp
 mov  ebp, esp
 
 mov  eax, dword ptr [ebp+08h]
 add  eax, dwrod ptr [ebp+0ch]
 
 ; restore stack frame
 mov  esp, ebp
 pop  ebp
 ret  08h
test_sum3 ENDP

option prologue:PrologueDef
option prologue:EpilogueDef

end

이상 허접한 팁이었습니다.
MSDN 의 MASM 레퍼런스에는 option 에 대한 키워드는 있는데, 각각의 옵션 키워드에 대한 설명이 없네요!! 왜 그런거지...

2013년 2월 4일 월요일

C4018 워닝을 잡아라!

C4018 warning을 아시나요?
Visual Studio 에서 뿌려주는 컴파일 워닝 코드입니다. (cl.exe 가 뿌려주는거라고 말하는게 정확한 것이겠죠?)
어찌되었거나 Level3 짜리 워닝인데요, "signed int 와 unsigned int 간의 비교연산이 있다" 라는 경고입니다.

여러분은 어떤가요? 컴파일러가 이런 워닝을 뱉어내면 어떻게 하시나요?
"워닝은 반드시 잡아야 하는 대상이 아닌 그냥 무시해도 되는거..." 정도로 생각하시는 분들이 생각보다 많더군요.
C4018 때문에 발생하는 재앙(?)을 한번 볼까요?


void test_compare_uint_int()
{
    int32_t  i32;
    uint32_t u32;

    i32 = 1;
    u32 = 10;  
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");

    i32 = -1;
    u32 = 10;
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");
}

위의 코드는 i32(signed) 와 u32(unsigned) 간의 비교연산을 시도하는 코드입니다.
실행해보면 어떤 결과가 나올까요?
첫 번째는 비교는 사실 문제가 없습니다.
두 번째는 어쩌면 생각과는 다른 결과가 나올 수 도 있을 겁니다.
상식적으로 -1과 10 중에 누가 더 클까요? 사람은 10이 더 크다고 생각하겠지만 컴퓨터도 그럴까요?
-1 == 0xFFFFFFFF, 10 == 0x0000000a 입니다. 누가 더 큰가요? 당연히 0xFFFFFFFF 입니다.
이게 결국 의도하지 않은 버그를 만들어내게 되지요.
이런 류의 버그는 평소엔 잠잠히 있다가 아주 가끔씩, 비 정기적으로 (보통때는 양의 정수이다가 아주 가끔 음수값이 들어올때) 시스템을 망가뜨리게 되죠.
당연히 디버깅하기가 매우 까다로운 녀석중에 하나입니다.

그럼 어떻게 해야 할까요?
정석대로라면 signed 와 unsigned 를 용도에 따라서 정확히 구분해서 사용하는거죠.
하지만 그게 말처럼 간단하지 않습니다. 애매한 경우가 많이 생기죠.
외부 라이브러리가 리턴하는 값이 signed 인데 내 코드의 비교 대상 변수는 unsigned 라든지...

그럼 어쩌라구?!
그렇습니다. signed 변수와 unsigned 변수를 모두 담을 수 있는 크기의 변수로 캐스팅하는겁니다.

i32 = -1;
u32 = 10;
/* 이렇게 하면 안되요!! */
printf("i32 %s u32 \n", i32, i32 > u32 ? ">" : "<", u32);

/* 이렇게 해야 합니다. */
printf("i32 %s u32 \n", i32, INT64(i32) > INT64(u32) ? ">" : "<", u32);

오늘의 교훈! 컴파일러의 워닝 메세지도 모두 없애는 습관을 들이자.

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 후킹에 대한 예제를 만들었는데요.
최대한 이해하기 쉽게 심플하게 만들었습니다. 
하드에 썩히는것보다는 공개하면 혹시 도움되시는 분들이 계실까? 하고 올려봅니다.