DLL Injection
훅(Hook)
갈고리, 낚시바늘, 원하는 것을 낚아 챌때 사용하는 도구
정보를 엿보거나 가로채는 경우
Message Hook
OS - App - User 사이에 오고가는 정보를 엿보고 조작할 수 있음.
Windows 는 event driven으로 동작하는데
Event가 발생하면 OS message queue에 추가된다.
OS는 OS message queue에서 message를 꺼내서 어느 프로세스에서 발생했는지 보고 해당 프로세스의 Application Message Queue에 추가한다.
프로세스는 본인의 Application Message Queue를 모니터링 하다가 추가된 message가 있으면 해당 event handler를 호출해서 처리한다.
message Hook이 설치되어 있다면 프로세스보다 먼저 message를 볼 수 있음.
SetWindowsHookEX(hook_type, hook_procedure, hook_DLL_handle, hook_id)함수를 통해 구현할 수 있음.
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void(*PEF_HOOKSTART)();
typedef void(*PEF_HOOKSTOP)();
void main() {
HMODULE hDll = NULL;
PEF_HOOKSTART HookStart = NULL;
PEF_HOOKSTOP HookStop = NULL;
char ch = 0;
hDll = LoadLibraryA(DEF_DLL_NAME);
HookStart = (PEF_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PEF_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
HookStart();
printf("hooking");
while (_getch() !='q')
{
}
printf("hooking stop");
HookStop();
FreeLibrary(hDll);
}
DLL을 로딩한 후 HookStart 함수를 호출, q눌리면 HookStop 함수 호출 후 Free DLL
#include "stdio.h"
#include "windows.h"
#include "pch.h"
#define DEF_PROCESS_NAME "notepad.exe"
#define DEF_PROCESS_NAM "chrome.exe"
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) {
switch (dwReason) {
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
char szPath[MAX_PATH] = { 0, };
char* p = NULL;
int a = 0;
if (nCode >= 0) {
if (!(lParam & 0x8000000000000000)) {
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
if (!_stricmp(p + 1, DEF_PROCESS_NAME))
a = 1;
if (!_stricmp(p + 1, DEF_PROCESS_NAM))
a = 1;
if (a == 1)
return 1;
}
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookExW(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
DLL을 보면 HookStart와 HookStop을 export하고 있고 DLL이 로딩될때 g_hInstance = hinstDLL;해주고 있음.
HookStart에서 위에서 설명한 메시지 후킹 함수를 호출하고 있고, id가 0이기때문에 모든 프로세스에서
keyboard이벤트 발생시 g_hInstance의 DLL을 injection 하고
해당 프로세스의 KeyboardProc가 호출 된다.
디버깅해보면
HookStart로 메세지훅 설치 함 -> 어떤 프로세스에서 키보드 이벤트가 발생함 -> 그 프로세스에 DLL이 injection됨 -> DLL의 callback(여기선 KeyboardProc)이 호출 됨 -> callback에서 return 1하면 message가 끊기고 CallNextHookEx(g_hHook, nCode, wParam, lParam); 하면 다음 call로 넘어감.
이런 방식으로 키로거를 만들 수 있는데 아이디/비밀번호 입력시ctrl+C/V로 문자를 입력하면 방어할 수 있다.
키로깅 당해도 방화벽을 사용해서 정보 유출을 막을 수 있다.
DLL인젝션
실행중인 다른 프로세스에 특정 DLL파일을 로딩시키는 것.
다른 프로세스에게 LoadLibrary API를 호출시켜서 DLL을 로딩하는것.
방법
원격 스레드 생성(CreateRemoteThread API)
레지스트리 이용(AppInit_DLLs 값)
메시지 후킹 (SetWindowsHookEx API) - 위에서 설명함
원격 스레드 생성
#include "windows.h"
#include "tchar.h"
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
// #1. dwPID 를 이용하여 대상 프로세스(notepad.exe)의 HANDLE을 구한다.
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
// #2. 대상 프로세스(notepad.exe) 메모리에 szDllName 크기만큼 메모리를 할당한다.
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
// #3. 할당 받은 메모리에 myhack.dll 경로("c:\\myhack.dll")를 쓴다.
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
// #4. LoadLibraryA() API 주소를 구한다.
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
// #5. notepad.exe 프로세스에 스레드를 실행
_tprintf(L"s(%x)\n", pThreadProc);
_tprintf(L"s(%x)\n", pRemoteBuf);
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int _tmain(int argc, TCHAR* argv[])
{
// inject dll
if (InjectDll((DWORD)4612, L"C:\Users\a0102\source\repos\Dll1\x64\Release\Dll1.dll"))
_tprintf(L"InjectDll ssed!!!\n");
else
_tprintf(L"InjectDll failed!!!\n");
return 0;
}
위의 DLL파일을 그대로 이용해서 해보았다.
함수 설명을 하자면 먼저 main에서 inject dll을 호출한다. 나의 경우 메모장의 PID가 4612여서 4612를 적고 그 뒤에 DLL의 절대위치를 넣어줬다.
InjectDll을 보면 먼저
PID를 바탕으로 프로세스 핸들을 얻는다.
injection할 DLL 위치를 적을 메모리를 메모장 프로세스에서 할당하도록 한다.
할당 받은 메모리에 위치를 적는다.
LoadLibrary함수 위치를 구한다.
메모장에 원격스레드를 실행한다.
원격 스레드를 실행할때 함수1개, 그 함수 인자 1개가 들어가서
함수에는 LoadLibrary, 인자에는 Dll의 위치를 적은 주소를 넣어주면 스레드가 실행되면서
loadlibrary에 Dll위치를 넣어서 실행하게 되는 것이다.
문제는 Loadlibrary위치가 고정일 경우이다.
요즘은 aslr이 켜져있어서 이곳저곳 돌아다닌다.
그리고 86/64 비트도 문제가 있다..
아니다 틀렸다 아래 코드를 보면 된다.
#include "windows.h"
#include "tchar.h"
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
// #1. dwPID 를 이용하여 대상 프로세스(notepad.exe)의 HANDLE을 구한다.
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
// #2. 대상 프로세스(notepad.exe) 메모리에 szDllName 크기만큼 메모리를 할당한다.
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
// #3. 할당 받은 메모리에 myhack.dll 경로("c:\\myhack.dll")를 쓴다.
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
// #4. LoadLibraryA() API 주소를 구한다.
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
// #5. notepad.exe 프로세스에 스레드를 실행
_tprintf(L"s(%p)\n", pThreadProc);
_tprintf(L"s(%p)\n", pRemoteBuf);
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pThreadProc, (LPVOID)pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int _tmain(int argc, TCHAR* argv[])
{
// inject dll
if (InjectDll((DWORD)25364, L"C:\\Users\\a0102\\source\\repos\\Dll1\\x64\\Release\\Dll1.dll"))
_tprintf(L"InjectDll ssed!!!\n");
else
_tprintf(L"InjectDll failed!!!\n");
return 0;
}
이 코드가 위 코드랑 다른 건
위에 일단 문자열이 \로 쓰면 안되고 \\로 써야 된다.
그리고 빌드 할 때 메모장이 64비트라 64비트로 바꿔서 빌드해야 된다.
이렇게 하면 메모장에 원격 스레드가 생성되면서 실행이 된다.
프로세스마다 kernel32의 위치와 함수의 위치가 거의 고정되어있어서 작동한다.
DLL injection 다음 방법은 AppInit_DLLS를 이용하는 것이다.
레지스트리 편집기에 들어가서 AppInit_DLLs에 DLL 경로 문자열을 쓰고, LoadAppInit_DLLs 항목의 값을 1로 설정하고 재부팅 하면 User32.dll이 로딩되는 모든 프로세스에게 그 DLL을 로딩시킨다.
AppInit_DLLs에 C:\Users\a0102\source\repos\Dll1\x64\Release\Dll1.dll를 넣고
LoadAppInit_DLLs를 1로 바꾸고 재부팅 후 프로세스를 실행시켜보면 Dll1이 로딩된다.
강력하지만 DLL에 문제가 있을 경우 부팅이 안될 수 있음..