1. DLL Ejection 동작 원리
앞에서 소개했던 DLL Injection의 동작 원리는 다음과 같다.
대상 프로세스가 LoadLibrary()
API를 호출하도록 만드는 것
마찬가지로 DLL Ejection의 동작 원리는 다음과 같다.
대상 프로세스가 FreeLibrary()
API를 호출하도록 만드는 것
즉 CreateRemoteThread()
의 lpStartAddress
에 FreeLibrary()
API의 주소를 넘겨주고 lpParameter
에 Ejection할 DLL의 핸들을 넘겨주면 된다.
참고로 Windows Kernel Object에는 Reference Count 라는 것이 있다. 예를 들어
LoadLibrary("a.dll");
를 10번 호출하면 a.dll 에 대한 Reference Count도 10이 되어 나중에FreeLibrary()
를 10번 호출해줘야 한다. 따라서 DLL Ejection을 할 때는 Reference Count를 잘 고려해야 한다.
2. DLL Ejection 구현
2.1 EjectDll.cpp
// EjectDll.exe
#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"
#define DEF_PROC_NAME (L"notepad.exe")
#define DEF_DLL_NAME (L"myhack.dll")
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
// Get the snapshot of the system
pe.dwSize = sizeof( PROCESSENTRY32 );
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );
// find process
Process32First(hSnapShot, &pe);
do
{
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
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 EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;
// dwPID = notepad 프로세스 ID
// TH32CS_SNAPMODULE 파라미터를 이용해서 notepad 프로세스에 로딩된 DLL 이름을 얻음
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE;
break;
}
}
if( !bFound )
{
CloseHandle(hSnapshot);
return FALSE;
}
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
}
int _tmain(int argc, TCHAR* argv[])
{
DWORD dwPID = 0xFFFFFFFF;
// find process
dwPID = FindProcessID(DEF_PROC_NAME);
if( dwPID == 0xFFFFFFFF )
{
_tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
return 1;
}
_tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);
// change privilege(Windows 10 64bit에서 제대로 작동하지 않아 주석 처리함)
// if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
// return 1;
// eject dll
if( EjectDll(dwPID, DEF_DLL_NAME) )
_tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
else
_tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);
return 0;
}
2.1.1. 프로세스에 로딩된 DLL 정보 구하기
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );
CreateToolhelp32Snapshot()
API를 통해 프로세스에 로딩된 DLL의 정보를 얻을 수 있다. 이렇게 구한 hSnapShot 핸들을 Module32First()
과 Module32Next()
함수에 넘겨주면 MODULEENTRY32
구조체에 해당 모듈의 정보가 세팅된다. 잠시 구조체의 정의를 보면 다음과 같다.
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE *modBaseAddr; // 해당 DLL이 로딩된 주소
DWORD modBaseSize;
HMODULE hModule;
char szModule[MAX_MODULE_NAME32 + 1]; // 모듈 이름
char szExePath[MAX_PATH];
} MODULEENTRY32;
2.1.2 대상 프로세스의 핸들 구하기
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
2.1.3 FreeLibrary()
API 주소 구하기
hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
모든 프로세스에서 FreeLibrary()
의 주소는 동일하기 때문에 EjectDll.exe에서 로딩된 API의 주소를 불러오고 있다.
2.1.4 대상 프로세스에 스레드 실행시키기
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL);
// pThreadProc = FreeLibrary() 의 주소
// me.modBaseAddr = Ejection할 DLL의 주소
스레드함수로 FreeLibrary()
를 지정하고 그 파라미터로 Eject할 DLL의 주소를 넘겨주면 대상 프로세스에서는 FreeLibrary()
API가 성공적으로 호출이 될 것이다.
ThreadProc()
과 FreeLibrary()
의 파라미터가 하나뿐이라는 점에 착안해서 나온 아이디어이다.
자세한 설명은 03-03. DLL Injection 문서를 참고하면 된다.
모든 코드와 설명의 출처는 https://reversecore.com/ 에 있습니다.
'리버싱 핵심원리 > 03. DLL Injection' 카테고리의 다른 글
03-01. Windows 메세지 후킹 (0) | 2020.08.10 |
---|---|
03-03. DLL Injection (0) | 2020.08.10 |