DLL Injection Howto
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’ 을 구현하고자 할 때 사용하는 여러 가지 방법들을 소개할 것이다.
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 하고 이를 호출하여 코드를 실행하는 방법이다.
: 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);
…
}