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