Programming/Win32 API2008. 11. 15. 19:20

DLL Injection Howto

 

 

 

 Table Of Contents

 

 

1.  개요

2.  Method1: Using Registry

3.  Method1: Using Windows Hook Function

4.  Method2: VirtualAllocEx & CreateRemoteThread

5.  Method3: CreateRemoteThread & WriteProcessMemory

 

 

  

1. 개요

 

- DLL Injection 이란?

 

‘DLL Injection’ 은 이미 실행되어 있는 프로세스로 DLL Code 를 삽입하는 기법들을 말한다. 이 방법은 말 그대로 특정 프로세스를 실행하기에 앞서, 필요한 코드를 프로세스를 실행하는 지점에서 가로챈 뒤 자신의 코드를 먼저 주입(inject, 실행) 하고 제어권을 다시 돌려주는 방법을 뜻하며, 이러한 방법은 ‘Code Injection’ 이라 불리우기도 한다. 이미 실행되어 있는 Process – 현재 Runtime 에서 동작되고 있는 Process - 를 변경하여 사용하기도 하며, 특정 API 를 실행하고자 할 때 Code 를 먼저 Process 상에 Injection 하여 사용하기도 한다. ‘DLL Injection’ 을 통해 구현할 수 있는 기능들은 다음과 같다. 특정 함수를 실행하기에 앞서 필요한 작업을 하는데 사용하거나, 기존 함수들의 동작을 Monitoring 하고자 할 때 사용하며, 또는 기존의 기능을 확장하고자 할 때도 사용할 수 있을 것이다.

 

이 문서에서는 ‘DLL Injection’ 을 구현하고자 할 때 사용하는 여러 가지 방법들을 소개할 것이다.

 

- DLL Injection 을 위해 필요한 기반 지식

 

 WIN32 API 에 대한 기본 지식, DLL Process, Thread 의 동작원리를 알고 있으면 DLL Injection 을 하는데 큰 어려움이 없다. 이 외에도 Windows 에서 Process 를 실행 할 때 DLL 을 호출하는 원리를 알면 된다.


  

일반적으로 DLL 은 한 프로세스 프로그램 - 이 시작될 때 프로그램의 address 영역으로

Mapping 이 이루어 진다. Mapping 이 이루어 질 경우, DLL explicit 하게 호출이 되는지, implicit 하게 호출이 되는지 여부와 상관없이 entry point 함수인 DllMain 을 호출하며 cleanup, initialize 동작을 수행할 것이다. DLL 자체는 DLL 을 호출한 (프로세스의) 쓰레드 address 공간에 자신의 주소를 가지게 될 것이며, 이로 인해 DLL 코드가 프로세스 상의 내부 영역 프로세스가 사용하는 영역 에서 Dispatch 를 수행한다든지, API 를 가로채는 등의 기능을 할 수 있게 되는 것이다.

 

사용자 삽입 이미지


2.
Method1: Using Registry

- Registry: AppInit_DLLs (http://support.microsoft.com/kb/197571)

: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

 

Registry 를 이용하여 모든 프로세스에서 DLL 을 로드할 수 있다. 위에 언급한 레지스트리 경로에 있는 AppInit_DLLs subkey 에 삽입하고자 하는 DLL 의 경로와 이름을 지정해 놓으면, Windows 에서 USER32.DLL 을 사용하는 모든 프로세스는 이 DLL 을 자동으로 LoadLibrary(…)를 이용하여 실행한다.

 

실제로 이 방법을 사용할 경우는 없을 것이다. 효율적이지 못한 것 – 모든 프로세스가DLL 을 로드하게 되는 부하가 생길 것이며, DLL 코드 내부에서 특정 상태에 따른 처리를 해 주어야 하는 문제가 생기는 것 - 이 첫째 이유라면 그럴 것이고, DLL 코드 내부에서 사용할 수 있는 함수도 KERNEL32.DLL 에서 export 되는 함수에 국한될 것이기 때문이다.

 

만약 테스트를 해 보고자 한다면 Registry 에 값을 쓰고 시스템을 리부팅을 꼭 해야 적용이 될 것이다.

 

 

3. Method2: Using Windows Hook Function

- SetWindowHookEx, UnhookWindowsHookEx

 

HHOOK SetWindowsHookEx(

  int idHook,        // type of hook to install

  HOOKPROC lpfn,     // address of hook procedure

  HINSTANCE hMod,    // handle to application instance

  DWORD dwThreadId   // identity of thread to install hook for

);

 

BOOL UnhookWindowsHookEx(
  HHOOK hhk   // handle to hook procedure to remove
);

 

사용자 삽입 이미지
 

 

 Windows 에서 Hook 함수를 사용하여 DLL Inject 하게 되면, 내부적으로는 Hook Procedure 만이 아니라, Hook Procedure 가 들어 있는 DLL 코드 전체가 프로그램의 코드 영역에 mapping 되기 때문에, DLL 코드가 실행되는 영역이 결국 DLL 을 호출한 프로그램의 내부 영역이 된다. 내부 메시지를 hook 을 걸거나, window procedure hook 을 걸어 필요한 작업을 진행하면 될 것이고 SetWindowLong(…) 을 이용하여 관련 함수를 Dispatch 하는 방법을 사용하면 될 것이다.

 

Windows Hook 을 사용하고자 할 때 코드 작성 방식은 다음과 같다.

 

- A) DLL Shared 영역 지정

: Global 변수들을 Shared 로 지정하여 DLL 을 사용하는 모든 프로그램에 대해 DLL 이 로드되는 시간 동안 DLL 간의 공유 가능한 영역을 지정한다.

 

#pragma data_seg(".hkshared")

HHOOK   gHook   = NULL;        // global hook

HWND    gHwnd   = NULL;        // for subclassing (dll injection window)

UINT    gMsg    = 0;           // hook msg

#pragma data_seg()

#pragma comment(linker, "/SECTION:.hkshared,RWS") // for linker option

 

- B) DLL Code 선언부

: Hook 을 설치, 제거하는 함수와 실제 hook 으로 데이터를 얻었을 때 이를 처리하는 함수를 구현한다.

// install Hook Function

HOOKDLL_API int fnInjectDll(HWND hWnd);

// remove Hook Function

HOOKDLL_API int fnRemoveDll();

 

// Hook Procedure

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

);

 

- C) DLL Main Code

: Dll implicit, explicit 와 상관 없이 항상 호출되는 start code 이다. DLL_PROCESS_ATTACH 일 경우에 Hook procedure 에서 사용할 Message 를 등록하는 코드를 작성한다.

 

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                       )

{

        if( ul_reason_for_call == DLL_PROCESS_ATTACH )

        {

               hDll = (HINSTANCE) hModule;   // dll module

               ::DisableThreadLibraryCalls( hDll );

 

               if(gMsg == NULL)

                       gMsg = ::RegisterWindowMessage( "WM_HOOKDLLMSG" );                 

    }

       

    return TRUE;

}

 

- C) Hook Install Code

 

HOOKDLL_API int InjectDll (HWND hWnd)

{

     gHwnd = hWnd;  // get window to hook

     gHook = SetWindowsHookEx (

                  WH_KEYBOARD,   // hook (message) type; others->WH_MOUSE, WH_CBT…

                  HookProc,      // hook procedure

                  hInstance,     // dll instance handle

                  GetWindowThreadProcessId (hWnd, NULL)

);

    

}

 

- D) Hook Uninstall Code

 

HOOKDLL_API void RemoveHook (HWND hWnd)

{

    

     UnHookWindowsHookEx(gHook);

    

}

 

- E) Hook Procedure Code

: Hook Procedure 로서, 기본 Procedure Message 를 처리하기 전에 먼저 호출되는 부분이다. 이곳에서 Hook 메시지를 찾아내어 기존 호출 방법을 변경한다.

 

#define pCW ((CWPSTRUCT*)lParam)

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

)

{

If ((pCW->message == WM_HOOKEX) && pCW->lParam) 

        {

              

               // change dll owner (process)’s procedure

               OldProc = (WNDPROC) SetWindowLong(

gHwnd, GWL_WNDPROC, (LONG)NewProc

);

              

        }

return (CallNextHookEx(gHook,nCode,wParam,lParam));

}

 

 

 

 

- F) New Procedure Code

: 기존 Procedure 를 실행하기에 앞서 실행되는 부분이다. 이 부분은 DLL Injection 되는 영역 상에서 동작한다.  

 

LRESULT CALLBACK NewProc(

HWND hwnd,     

UINT uMsg,

WPARAM wParam, 

LPARAM lParam  

)

{

       

        Switch (uMsg)

{

Case WM_LBUTTONDOWN:

        MessageBox(NULL, “[Injected Code] Left Button”, “MSG”, MB_OK”);

        break;

        }

       

        // call original window procedure

        return CallWindowProc(OldProc,hwnd,uMsg,wParam,lParam);

}

 

 

4. Method3: VirtualAllocEx & CreateRemoteThread

- VirtualAllocEx, CreateRemoteThread

 

 

LPVOID VirtualAllocEx(

  HANDLE hProcess,  // process within which to allocate memory

  LPVOID lpAddress, // desired starting address of allocation

  DWORD dwSize,      // size, in bytes, of region to allocate

  DWORD flAllocationType, // type of allocation

  DWORD flProtect   // type of access protection

);

 

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

 

이미 존재하는 프로세스 상에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 앞서 Method1 에서 사용한 DLL 코드를 새로 생성한 thread 에서 load 하기 위해 LoadLibrary 를 실행한 뒤 필요한 작업을 수행하면 된다.

 

 

사용자 삽입 이미지



- A) Open Process (in App)

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

 

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

if (GetCurrentProcessId() == dwProcessId)

               return;

        // open remote process

        HANDLE hProcess = OpenProcess(         PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

);            

// inject dll

// reject dll

}

 

- B) Dll Injection Code (in App)

: remote thread 를 생성하고 LoadLibrary 를 호출한 뒤, thread 에서 dll 코드가 종료될 때까지 기다린다. Dll 코드는 remote thread, 즉 외부 Process 영역에서 동작하며, 필요한 작업을 한 뒤 return 되는데, return 되고 난 뒤에는 만들어 놓은 thread 를 종료하면 된다.

 

int InjectDll( HANDLE hProcess )

{

       

        // 1: get kernel address, this will be used to launch LoadLibrary

HMODULE hKernel32 = GetModuleHandle("Kernel32");

       

        // 2: get full path of dll

        Char szDllPath[MAX_PATH] = {255, 0};

        if(!GetModuleFileName(hInst, szDllPath, MAX_PATH))

               return 0;

        strcpy(strstr(szDllPath,".EXE"), ".dll");

       

        // 3: allocate dll length in the remote thread

        pLibRemote = VirtualAlloc(hProcess,

NULL, sizeof(szDllPath),

MEM_COMMIT, PAGE_READWRITE

);

        // 4: write dll code to the remote thread

        WriteProcessMemory(hProcess, pLibRemote, (void*)szDllPath,

               sizeof(szDllPath), NULL

               );

 

        // 5: call DLL code in the remote thread (LoadLibrary)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “LoadLibraryA”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 6: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- C) Dll Rejection Code (in App)

: remote thread 를 생성하고 FreeLibrary 를 실행하여 DLL unload 한다.

 

void RejectDll (HANDLE hProcess)

{

       

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “FreeLibrary”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// Close handle, and free dll

CloseHandle(hThread);

}

 

 

 

 

 

5. Method4: CreateRemoteThread & WriteProcessMemory

- CreateRemoteThread, WriteProcessMemory

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

BOOL WriteProcessMemory(

  HANDLE hProcess,             // handle to process whose memory is written to

  LPVOID lpBaseAddress,        // address to start writing to

  LPVOID lpBuffer,             // pointer to buffer to write data to

  DWORD nSize,                 // number of bytes to write

  LPDWORD lpNumberOfBytesWritten   // actual number of bytes written

);

 

사용자 삽입 이미지



프로세스의 address 영역에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 이 방법은 Method3 에서 사용한 방법과 비슷한 구조를 가지지만, 앞서의 경우와는 달리 DLL 을 외부 프로세스가 Load 하도록 동작하는 것이 아니라, DLL 에서 사용한 코드 자체를 외부 프로세스의 address 영역에다 Write 하고 이를 호출하여 코드를 실행하는 방법이다.

 

 - A) WinMain

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

 

// inject dll

bRet = InjectCode(hWnd);

MessageBox(NULL, “Code Injected. Press OK to reject!”,

                “Injection Sample”, MB_OK

               );

// reject dll

bRet = RejectCode(hWnd);

}

 

 

- B) INJDATA

: 실제 Injection 을 할 때 사용할 구조체이다. 이 구조체를 remot 프로세스의 address 영역에 write 하여 사용할 것이고, 여기에 저장된 내용은 Dispatch , 사용할 프로시저나 함수의 포인터, 내부 변수등을 저장한다.

 

 

typedef struct __INJDATA {

        // pointer to the function used within injected code

        SETWINDOWLONG  fnSetWindowLong;

        CALLWINDOWPROC fnCallWindowProc;     

        //

        HWND hwnd;

        WNDPROC fnNewProc;

        WNDPROC fnOldProc;    

} INJDATA, *PINJDATA;

 

- C) Remote Code (Inject, Reject)

: Remote thread 영역에서 사용할 코드

DWORD WINAPI InjectFunc (INJDATA *pData)

{

        // change old procedure to the new one

        pData->fnOldProc = (WNDPROC) pData->fnSetWindowLong(

               pData->hwnd, GWL_WNDPROC, (long)pData->fnNewProc

               );     

        return (pData->fnOldProc != NULL);

}

 

DWORD WINAPI EjectFunc (INJDATA *pData)

{

        // restore to the old procedure

        return (pData->fnSetWindowLong(pData->hwnd, GWL_WNDPROC,

               (long)pData->fnOldProc) != NULL);

}

 

- D) InjectCode

: 실제 Injection 에 사용될 코드

 

int InjectCode(HWND hWnd)

{

       

        // 1: get user module address, this will be used with dispatch function

HMODULE hKernel32 = GetModuleHandle("user32");

       

        // 2: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

        // 3: allocate space for remote procedure(function) and data

        pDataRemote = (BYTE*) VirtualAllocEx(hProcess, 0, size,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE

               );

        pNewProcRemote = pDataRemote + sizeof(INJDATA);

       

        // 4: write pNewProcRemote to the process memory (address space)

        WriteProcessMemory(hProcess, pNewProcRemote,

               &NewProc, cbNewProc, &dwNumBytesXferred

               );     

 

        // 5: initialize INJDATA

        injData.fnSetWindowLong = (SETWINDOWLONG)

               GetProcAddress(hUser32, “SetWindowLongA”);

        injData.fnCallWndProc   =  (CALLWNDPROC)

               GetProcAddress(hUser32, “CallWndProcA”);

        injData.fnNewProc = (WNDPROC) (pNewProcRemote);

 

        // 6: write injData to the process memory           

        WriteProcessMemory(hProcess, pDataRemote,

               &DataLocal, sizeof(INJDATA), &dwNumBytesXferred

               );

       

        // 7: allocate space for remote function (InjectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbInjectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 8: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &InjectFunc, cbInjectFunc, &dwNumBytesXferred

               );

       

        //  9: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 10: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- D) RejectCode

: 실제 Injection 에 사용될 코드

 

int RejectCode(HWND hWnd)

{

       

        // 1: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

       

        // 2: allocate space for remote function (RejectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbRejectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 3: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &RejectFunc, cbRejectFunc, &dwNumBytesXferred

               );

       

        //  4: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 5: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

Posted by skensita
Programming/Win32 API2008. 10. 9. 15:11
[API] Native API의 개념과 용도, 활용 예(Native API를 이용해서 컴퓨터 전원을 꺼버리자!!)

 안녕하세요. 오랜만에 글을 올리는 수학쟁이입니다. ^^

 오랜만에 글을 쓰는것이니 만큼 팁보다는 개념적인 수준의 글을 올려보겠습니다.

 이번 강좌에서는 Native API라는 놈들의 정체좀 밝혀보겠습니다. ^^..

 API는 크게 두 종류로 나뉘어져있습니다. (명칭만 다르지 구조는 같다고 보시면 됩니다.)
 보통 API는 Win32 API Native API 두 종류 로 나뉘어져있는데요.

 우리가 흔히 접할 수 있는 API는 Win32 API 입니다.

 *** Native API라는 것은 대체 무엇일까요?

 우선 Native API를 알기 전에 API 라는 녀석에 대해서 제대로 파헤쳐봅시다.

 API 라는 놈은 Application Programming Interface의 약자로 운영체제(OS)에서 소프트웨어에게 제공하는 기능 정도로 이해하시면 됩니다.

 이 API라는 놈은 내부적으로 DLL이라는 확장자를 가진 파일(동적 연결 라이브러리; Dynamic Link Library)에 함수 형태로 들어있습니다(* 유식한 말로 Export라고 합니다.).

 *** API는 몇개일까?

 그냥 API라고 하면 종류가 다양한데(Windows API, Java API, 네이버 OpenAPI 등등...) 이 여러 API 중 비베에서 함수 형태로 호출이 가능한 Windows API의 개수만 해도 어마어마합니다. 게다가 프로그래머가 이 윈도우 API를 직접 만들 수 있습니다.(Visual C++ 등의 C 컴파일러를 이용하여)

 그렇다면 프로그래머가 만들지 않은 순수하게 제공되는 'Win32 API'는 몇개일까? Native API만 빼도 대략 어림잡아 자그마치 6,000~7,000개 정도로 예상하고 있습니다. (allapi라는 사이트에서만 집필된 api가 5,000개 정도 되는데 이 수치로는 어림도 없죠.)

 *** API의 종류를 보려면?

 
 API는 아까도 언급했듯이 종류가 한두개가 아닐 뿐더러 사용되는 목적도 제각기 각각 다릅니다. (예: FindWindow 라는 API
는 특정 창의 핸들을 찾는데 사용되어지고, OpenProcess 라는 API는 프로세스의 핸들을 구할 때(접근할때)사용되는 함수이며, DDraw.dll라는 dll의 api들은 DirectDraw에서 사용되는 API입니다.) 게다가 그 수가 약 수천개에 이릅니다. 이를 다 외워서 쓰는것은 불가능하며, 다른 문서나 프로그램을 이용해서 찾아보아야합니다. 그 방법은 아래에 짤막하게 써놓겠습니다.

 Visual Basic에서는 API 텍스트 뷰어라는 뷰어를 제공합니다. 단 이 뷰어는 미리 넣어둔 API밖에 사용하질 못합니다.

 그리고 양도 적고 버그도 있습니다. 그래서 대부분의 VB 프로그래머들은 ApiViewer 2004라는 툴을 사용하거나 직접

 선언문을 만들어서 사용합니다. (ApiViewer 2004 다운로드 게시글 바로가기)

 VB의 API 선언문은 아래와 같은 구조이니까 함수 인자와 dll, API 함수 본명만 안다면 작성할 수 있겠죠.

 (함수 본명이나 인자들은 구글 검색이나 MSDN을 이용하시면 쉽게 찾을 수 있습니다. 물론 C언어 기준으로한 선언문이지만요... DLL에 export된 똑같은 함수니 C에서만 사용 가능한것이 아니라 충분히 VB에서도 쓸수있습니다.)


 [Public/Private] Declare [Sub/Function] 함수 이름 Lib "DLL이름" Alias "함수 원래 이름" ( _

     [ByVal/ByRef] 인자1 As [인자형식], _

     [ByVal/ByRef] 인자2 As [인자형식], _

     [ByVal/ByRef] 인자3 As [인자형식], _

     ...

     [ByVal/ByRef] 인자n As [인자형식] _

 ) [As 반환값형식]

 - 선언문은 필요없고 이름이라도 좋으니 이름이라도 보고싶으시다면, VS6에서 제공하는 툴인 Dependency Walker를 이용해보실 수 있겠습니다.

위치: 시작 메뉴 - Microsoft VIsual Basic 6.0 - Microsoft Visual Studio 6.0 도구들 - Depends

사용자 삽입 이미지

 *** 그렇다면 Native API라는 녀석은 무엇이냐?

 - Windows 9x 시절로 돌아가보면, 9x 시절에는 블루스크린이 유난히 많았습니다. 심지어 빌게이츠 회장이 98 시연중 장치 드라이버 검색 도중에 블루스크린(BSOD; Blue Screen Of Death)가 뜬것만 봐도 알수가 있죠. (http://kr.youtube.com/watch?v=RgriTO8UHvs)

 이는 윈도우 9x의 동작 체계가 매우 불안정했음을 보여주는 단적인 예입니다. 그런데 어째서 그렇게 불안정할까요?
 그리고 NT계열의 OS(NTx, 2000, XP, ...)들은 블루스크린 보기가 왜이렇게 힘들까요? 그 이유은 간단합니다.

 9x는 16비트와의 연계를 할 수 있는데 (그래서 고전 게임의 대다수가 동작할 수 있었죠.), XP와 달리 운영체제 레벨(Ring0)에서 바로 실행해버려서 16비트 프로그램과 32비트 OS 모듈이 충돌할 수가 있었습니다. 충돌되면 그게 블루스크린이 뜨는것이죠.

 Windows NT 계열의 경우 16비트 프로그램을 실행하면 직접 메모리에 올려놓고 실행하는것이 아닌, WOW(Windows-On-Windows)라는 체계에서(wowexec.exe) 16비트 메모리를 에뮬레이팅하고 명령어를 에뮬레이션해서 위험한 명령어를 제외하고 실행시킵니다. 그래서 도스 프로그램의 일부는 XP에서 호환되지 않는것입니다.

 그런데 이 XP가 갑자기 16비트를 에뮬레이트하게 된 내부 요인은 무엇일까요? 이것은 NT에서부터 Intel CPU(R) 및 그의 호환기종(AMD 등)만 지원하게 된 이유와도 관련이 깊습니다. 9x까지는 대부분의 CPU를 지원했으나, NT에서는 인텔 CPU만을 수용하기 시작했습니다. 이 이유중의 하나는 인텔 CPU의 링 보호 체계(Ring Protection System)를 활용하여 운영체제를 보호하는 코드를 추가하기 시작해서 인데요. 이 링 보호 체계는 간단합니다. CPU의 권한을 네등급으로 분류하고(Ring 0 ~ Ring 3) 각 링의 권한에서 실행가능한 명령어를 제한하는것입니다. 가장 좋은 권한이 Ring0이고 가장 낮은 권한이 Ring3이죠. 현재의 WinNT 계열의 OS에서는 프로그램이 실행되면 Ring3이 실행되고, 운영체제(커널)은 RIng0에서 실행됩니다. [ 보통 Ring3은 유저 모드라고 부르고 Ring 0은 커널 모드라고 부릅니다. RIng1-2는 디바이스를 위해 예약되었지만, 현재 대부분의 OS에서는 사용하지 않는 계층입니다. ]

 어떤분은 이렇게도 말하기도 합니다. " 그러면 Ring 3 프로그램에서 CreateFile()을 이용해서 파일을 읽거나 쓸수도 있는데, 그렇게 되면 결과적으로 하드디스크에 접근하게 되는데 이건 어떻게 된거냐 "

 이 질문에 대한 답도 간단합니다. Ring3 권한에서는 '하드웨어 포트 입/출력', '몇몇 일부 레지스터 접근 제한(CRx,DRx,IDTR,EFLAGS...)', '인터럽트 발생', '물리 메모리 접근', '커널 메모리(0x80000000~0xFFFFFFFF) 접근' 등이 제한됩니다. 하드디스크에 직접적으로 데이터를 읽거나 쓰기 위해서는 하드디스크에 해당하는 포트(Hardware I/O Port)에 데이터를 입출력하여 바이너리 레벨에서 데이터를 읽거나 쓸 수 있는데요. 문제는 Ring3에서는 포트 입출력 권한이 없기 때문에 Ring0으로의 권한 이행이 필요합니다. 이 떄 Ring3 API에서 Ring0으로의 권한 이행의 중간 매개 API가 'Native API'입니다.

 CreateFile()이 호출되면 내부적으로 여러 함수들이 호출되는데, 호출되는 과정을 요약하여 그림으로 나타내면 아래와 같습니다.

사용자 삽입 이미지

 (그림에서 XXX()는 생략하였음을 의미합니다. 즉 Ob,Hal,Fs,Iop로 시작하는 내부함수들을 호출하는것을 의미)

 (참고: Ob, Hal, Fs, Iop 등은 각각 Object, HAL(Hardware Abstract Layer), File System, Input/Output [private] 의 약자입니다. 이렇듯 Zw*를 제외한 Native APi들은 모두 의미있는 prefix(접두어)들을 가지고 있습니다.)

 상당히 복잡합니다만... VB에서 Native API라고 하면 위의 ZwCreateFile() 부분일겁니다.

 * Native API는 NTSTATUS라는 이상한 값(?)들로 오류 코드를 반환합니다.
 
함수 반환값 자체가 오류 코드죠. Win32 API는 GetLastError()함수나 Err.LastDllError로 얻어야하지만요.
 
Native API는 성공 했을 때가 STATUS_SUCCESS (0)의 값 (혹은 양수의 Long 값)을 가집니다.
* 여기서 알 수 있으시다시피 Win32 API는 Native API를 더욱 안전하게 쓸 수 있도록 가죽만 씌운것에 불과합니다.

 *** Native API는 활용할 수 없나?

* VB에서도 Native API를 활용할 수 있습니다. 유저 모드에서만 호출 가능한 Native API는 ntdll.dll에 커널 모드에서만 호출이 가능한 API는 ntoskrnl.exe에 들어있기 때문이죠.

 예를 들어 직접적으로 Native API를 호출하여 컴퓨터의 파워를 꺼지게 할 수 있습니다. 파워를 끄는 API는 없지만 Native API에서는 이 기능을 지원하고 있습니다.

 윈도우 종료 API로는 ExitWindows가 있고 이 API는 내부적으로 ZwShutdownSystem이라는 함수를 호출합니다.

 이 함수를 직접 호출하면 간단하게 파워를 종료시킬 수 있습니다.

 * Windows 98에서도 지원하도록 하기 위해 호출에 실패하면 Windows 98의 krnl386.exe::ExitKernel을 호출하도록 지시하게 만들었습니다.

Private Declare Function RtlAdjustPrivilege Lib "ntdll.dll" _
     (ByVal Privilege As Long, _
     ByVal bEnablePrivilege As Long, _
     ByVal IsThreadPrivilege As Long, _
     ByRef PreviousValue As Long) As Long

Private Declare Function ZwShutdownSystem Lib "ntdll.dll" _
    (ByVal Action As Long) As Long

Private Declare Function ExitWindowsEx Lib "user32.dll" _
    (ByVal uFlags As Long, ByVal dwReturnCode As Long) As Long

Private Declare Sub ExitKernelWin9x Lib "krnl386.exe" Alias "EXITKERNEL" ()

Private Const ShutdownPowerOff As Long = 2&
Private Const EWX_FORCE As Long = 4
Private Const EWX_POWEROFF As Long = &H8
Private Const EWX_SHUTDOWN As Long = 1

Private Sub PowerOff()
    On Error Resume Next
    RtlAdjustPrivilege 19, 1, 0, 0&
    If ZwShutdownSystem(ShutdownPowerOff) Then
        If ExitWindowsEx(EWX_POWEROFF Or EWX_FORCE, 0&) = 0 Then
            ExitKernelWin9x
            ExitWindowsEx EWX_SHUTDOWN, 0&
        End If
    End If
End Sub

Posted by skensita
Programming/Win32 API2008. 9. 12. 16:33
SendMessage(hwnd, WM_LBUTTONUP, (WPARAM)0, MAKELPARAM(posx1,posy1));

이런식으로 LPARAM을 만들어 보낼 수 있다.
Posted by skensita
Programming/Win32 API2008. 9. 5. 14:41

Windows Messaging

제프리 릭터의 Programming Applications for Microsoft Windows에 담긴 내용입니다.

쓰래드와 메시지 : 기본 규칙
사용자 삽입 이미지
  • 프로세스 하나가 10,000개에 이르는 제각기 다른 종류의 사용자 객체(아이콘, 커서, 윈도우 클래스, 메뉴, 단축키 테이블 등)를 생성할 수 있다. 한 쓰래드에서 이들 객체 중 하나를 생성하는 함수를 호출했을 때, 생성된 객체는 그 쓰래드의 프로세스가 소유하게 된다(이 말은 프로세스가 죽으면 그 객체도 함께 죽는다는 말이다).

less..

  • 반면 윈도우(window)와 훅(hook) 객체는 그 윈도우를 생성하거나 훅을 설치한 쓰래드가 소유한다(이 말은 그 쓰래드가 죽으면 그 객체도 함께 죽는다는 뜻이다). -> 윈도우를 소유한 쓰래드가 그 윈도우의 모든 메시지를 관장한다. -> 하나 이상의 윈도우를 생성한 모든 쓰래드에는 메시지 큐가 할당되고, 그 때문에 그 쓰래드만의 메시지 루프를 갖게 된다.

  • 또한 각각의 쓰래드는 키보드 포커스라던가, 윈도우 활성화, 마우스 캡쳐 등을 다루는 그 자신만의 환경(simulated environment)이 있다고 생각한다. 그러므로 각 쓰래드에는 이들 환경 변수를 저장할 THREADINFO 구조체가 있고, 이 구조체로써 그 쓰래드만의 붙여진(posted) 메시지 큐, 보내진(send) 메시지 큐, 응답(reply) 메시지 큐, 가상화된(virtualized) 입력 큐, wake 플래그와 그 외의 여러 환경 변수등을 식별해낼 수 있다. (메시지 큐가 하나로만 구성된 것이 아니더라..)

  • 결국 THREADINFO 구조체는 윈도우즈 메시지 시스템의 주춧돌이 되더라. 하지만, UI관련 함수가 호출되기 전까지는 이 THREADINFO를 포함한 관련 리소스가 할당되지 않는다. (근데 이 구조체는 'internal', 'undocumented' 구조체라 직접 뜯어볼 수가 없다. 됀장 MS.)

POST 계열 메시지 함수
  • 붙여진 메시지 큐에 메시지만 넣은 다음, 그 메시지의 처리 여부에 관계없이, 바로 호출자 함수로 되돌아온다.
  • POST 계열 메시지 함수 : PostMessage, PostThreadMessage(다른 쓰래드에 메시지를 붙일 때), PostQuitMessage

SendMessage 함수
  • 메시지를 윈도우 프로시저에 직접 보낸다. 오직 메시지가 처리되었을 때만 호출자 함수로 되돌아온다. 요러한 동기적 처리방식 때문에 주로 사용하게 된다.
  • 다 른 쓰래드에 메시지를 넘길 경우 : 메시지를 보낸 쓰래드는 멈춰서서, 메시지를 받은 쓰래드가 그 메시지를 처리 완료하여 보낸 쓰래드의 응답 메시지 큐에 처리된 결과(SendMessage의 반환값)가 담긴 메시지를 붙일 때에야, 비로소 깨어나 그 결과 처리를 시작으로 다음 실행을 재개한다.
  • 호출된 쓰래드에 문제가 있어 메시지를 처리 못할 경우 : 호출자 쓰래드와, 호출된 쓰래드 모두 deadlock에 걸린다! -> 이를 위해 SendMessageTimeout, SendMessageCallback, SendNotifyMessage, ReplyMessage 함수가 있는 것이다.
  • SendMessageTimeOut 함수 : 보낸 메시지에 대한 응답을 받기까지의 최대 시간을 지정.
  • SendMessageCallback 함 수 : 메시지를 보내고 잽싸게 되돌아온다. 호출된 쓰래드에서 메시지 처리를 마치고 응답을 보내면, 지정한 CallBack 함수가 호출된다. 호출되는 시점은 호출자 함수의 다음번에 응답 메시지 큐에서 메시질 가져올 때이다. 메시지 브로드케스팅에도 이용.
  • SendNotifyMessage 함수 : 이 역시 메시지를 보내고 잽싸게 되돌아온다. 때문에 PostMessage와 비스무리할 수도 있지만, 윈도우에 직접 메시지를 보내기에 붙여진(Posted) 메시지보다 처리 우선순위가 높다.
  • ReplyMessage 함 수 : (deadlock을 피하기 위해) 위의 함수들은 메시지를 보내는 측에서 호출하지만, 이 함수는 받는 쪽에서 호출한다. 이 함수를 호출한다는 것은 시스템에게 메시지 결과를 알 만큼 충분히 처리했고, 처음 메시지를 보낸 측에 결과 메시지를 싸서 보내라고 알리는 것이다. 그 결과, 처음 보낸 측은 깨어나 응답 메시지를 처리하게 될 것이야~

쓰래드 메시지 큐에서의 메시지 추출 메카니즘
  • 쓰래드가 GetMessage나 PeekMessage를 호출했을 때, 시스템은 그 쓰래드 큐의 상태 플래그를 검사하여 어떤 메시지를 처리할지를 결정한다. 다음은 처리 순서다.

      사용자 삽입 이미지

    • QS_SENDMESSAGE 플래그가 켜있으면, 시스템은 메시지를 해당 윈도우 프로시저로 보낸다. 이 때에 GetMessage와 PeekMessage역시 이 작업에 내부적으로 관여하여, 윈도우 프로시저가 그 메시지를 다 처리한 후에도 return하지 않는다. 대신에, 처리할 다음 메시지를 기다린다.
    • 붙여진(posted) 메시지 큐에 메시지가 있으면, GetMessage와 PeekMessage는 이들 함수로 넘겨진 MSG 구조체를 채운다. 이 쓰래드의 메시지 루프는 보통 이 시점에서 DispatchMessage를 호출하여, 적당한 윈도우 프로시저가 그 메시지를 처리하도록 한다.
    • QS_QUIT 플래그가 켜져 있으면, GetMessage와 PeekMessage는 WM_QUIT를 반환하고 QS_QUIT 플래그를 재설정한다.
    • 가상화된 입력 큐에 메시지가 있으면, GetMessage와 PeekMessage는 하드웨어 입력 메시지를 반환한다.
    • QS_PAINT 플래그가 켜 있으면, 이들 함수는 적당한 윈도우를 위한 WM_PAINT 메시지를 반환한다.
    • QS_TIMER 플래그가 켜 있으면, 이들 함수는 WM_TIMER 메시지를 반환한다.

    보 다시피, GetMessage와 PeekMessage는 보내진(send) 메시지를 처리하고 나서는 바로 반환하지 않고, 그 다음 과정으로 넘어가서야 반환한다(특히 붙여진(posted) 메시지를 처리하고나서야 반환한다). 그리고 QS_QUIT에서만 TRUE를 반환한다. 큐를 하나만 사용하는 것도 아니고, 플래그까지 사용하다니, 아주 메카니즘이 지랄같다. 아마 위 설명갖고는 이해가 다 안될 것이다. 더 공부해야..
Posted by skensita