DLL Ejection 은 프로세스에 강제로 삽입한 DLL 을 빼내는 기법입니다.
기본 동작 원리는 CreateRemoteThread API 를 이용한 DLL Injection 의 동작 원리와 같습니다.
기본 동작 원리는 CreateRemoteThread API 를 이용한 DLL Injection 의 동작 원리와 같습니다.
DLL Ejection 동작 원리
먼저 지난번 강좌에서 소개되었던 CreateRemoteThread() API 를 이용한 DLL Injection 의 동작 원리를 생각해 보겠습니다. 대상 프로세스로 하여금 LoadLibraryA() API 를 호출하도록 만드는 것이었지요.
- 참고 : DLL Injection - 다른 프로세스에 침투하기 (2)
마찬가지로 DLL Ejection 의 동작 원리는 대상 프로세스로 하여금 FreeLibrary() API 를 호출하도록 만드는 것입니다.
즉, CreateRemoteThread() 의 lpStartAddress 파라미터에 FreeLibrary() API 주소를 넘겨주고, lpParameter 파라미터에 ejection 시킬 DLL 의 HANDLE 을 넘겨주면 됩니다.
* 주의!
Windows Kernel Object 에게는 Reference Count 라는 것이 있습니다. (우리말로 '참조 카운트')
LoadLibrary("a.dll") 를 10 번 호출하면 a.dll 에 대한 참조 카운트도 10 이 되어서, 나중에 a.dll 을 해제할때 역시 Freelibrary() 를 10 번 호출해줘야 합니다. (LoadLibrary() 는 참조 카운트 +1 시키고, FreeLibrary() 는 참조 카운트 -1 시킴)
따라서 DLL Ejection 을 할 때는 이 참조 카운트를 잘 고려해야 합니다.
DLL Ejection 구현
Injection 된 myhack.dll 을 Ejection 시킬 프로그램(EjectDll.exe) 을 설명드리겠습니다.
아래 소스 코드를 보시죠.
// EjectDll.cpp
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#define DEF_PROC_NAME ("notepad.exe")
#define DEF_DLL_NAME ("myhack.dll")
DWORD FindProcessID(LPCTSTR szProcessName);
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName);
int main(int argc, char* argv[])
{
DWORD dwPID = 0xFFFFFFFF;
// find process
dwPID = FindProcessID(DEF_PROC_NAME);
if( dwPID == 0xFFFFFFFF )
{
printf("There is no <%s> process!\n", "notepad.exe");
return 1;
}
// eject dll
EjectDll(dwPID, DEF_DLL_NAME);
return 0;
}
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(!_stricmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
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( !_stricmp((LPCTSTR)me.szModule, szDllName) )
{
bFound = TRUE;
break;
}
}
if( !bFound )
{
CloseHandle(hSnapshot);
return FALSE;
}
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
hModule = GetModuleHandle("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;
}
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#define DEF_PROC_NAME ("notepad.exe")
#define DEF_DLL_NAME ("myhack.dll")
DWORD FindProcessID(LPCTSTR szProcessName);
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName);
int main(int argc, char* argv[])
{
DWORD dwPID = 0xFFFFFFFF;
// find process
dwPID = FindProcessID(DEF_PROC_NAME);
if( dwPID == 0xFFFFFFFF )
{
printf("There is no <%s> process!\n", "notepad.exe");
return 1;
}
// eject dll
EjectDll(dwPID, DEF_DLL_NAME);
return 0;
}
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(!_stricmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
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( !_stricmp((LPCTSTR)me.szModule, szDllName) )
{
bFound = TRUE;
break;
}
}
if( !bFound )
{
CloseHandle(hSnapshot);
return FALSE;
}
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
hModule = GetModuleHandle("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;
}
앞에서 DLL Ejection 의 원리가 대상 프로세스로 하여금 스스로 FreeLibrary() API 를 호출하도록 만드는 것이라고 말씀드렸습니다. 위 코드에서 EjectDll() 함수가 바로 그 역할을 수행합니다.
EjectDll() 함수를 자세히 살펴보겠습니다.
#1. 프로세스에 로딩된 DLL 정보 구하기
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
CreateToolhelp32Snapshot() API 를 이용하면 프로세스 리스트 뿐만 아니라 프로세스에 로딩된 모듈(DLL)의 정보를 얻을 수 있습니다.
이렇게 구한 hSnaphot 핸들을 Module32First()/Module32Next() 함수에 넘겨주면 MODULEENTRY32 구조체에 해당 모듈의 정보가 세팅됩니다. 아래는 MODULEENTRY32 구조체 정의입니다. (tlhelp32.h 참고)
typedef struct tagMODULEENTRY32
{
DWORD dwSize;
DWORD th32ModuleID; // This module
DWORD th32ProcessID; // owning process
DWORD GlblcntUsage; // Global usage count on the module
DWORD ProccntUsage; // Module usage count in th32ProcessID's context
BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
HMODULE hModule; // The hModule of this module in th32ProcessID's context
char szModule[MAX_MODULE_NAME32 + 1];
char szExePath[MAX_PATH];
} MODULEENTRY32;
{
DWORD dwSize;
DWORD th32ModuleID; // This module
DWORD th32ProcessID; // owning process
DWORD GlblcntUsage; // Global usage count on the module
DWORD ProccntUsage; // Module usage count in th32ProcessID's context
BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
HMODULE hModule; // The hModule of this module in th32ProcessID's context
char szModule[MAX_MODULE_NAME32 + 1];
char szExePath[MAX_PATH];
} MODULEENTRY32;
szModule 멤버가 DLL 이름이고, modBaseAddr 멤버가 해당 DLL 이 로딩된 주소(프로세스 가상 메모리)입니다.
for 루프에서 szModule 와 Ejection 을 원하는 DLL 파일 이름을 비교하면 정확한 모듈 정보를 찾을 수 있습니다.
#2. 대상 프로세스 핸들 구하기
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
프로세스 ID 를 이용해서 대상 프로세스(notepad)의 프로세스 핸들을 구합니다.
(밑에서 이 프로세스 핸들을 이용하여 CreateRemoteThread() API 를 호출하게 됩니다.)
#3. FreeLibrary() API 주소 구하기
hModule = GetModuleHandle("kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
notepad 프로세스로 하여금 스스로 FreeLibrary() API 를 호출하도록 하려면 FreeLibrary() 의 주소를 알아야 합니다.
하지만 위 코드는 notepad.exe 프로세스에 로딩된 Kernel32!FreeLibrary 주소가 아니라 EjectDll.exe 프로세스에 로딩된 Kernel32!FreeLibrary 주소를 얻어오고 있습니다.
DLL Injection 을 이해하신 분들께서는 이유를 아시겠지요? FreeLibrary 주소는 모든 프로세스에 대해 동일합니다.
자세한 설명은 위에서 소개한 DLL Injection 관련 글을 참고하시기 바랍니다.
#4. 대상 프로세스에 스레드를 실행시킴
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
pThreadProc 파라미터는 FreeLibrary() API 의 주소이며, me.modBaseAddr 파라미터는 Ejection 되길 원하는 DLL 의 로딩 주소입니다.
즉, 스레드 함수로 FreeLibrary 함수를 지정하고 스레드 파라미터에 DLL 로딩 주소를 넘겨주면, 결국 대상 프로세스에서는 FreeLibrary() API 가 성공적으로 호출되게 됩니다. (CreateRemoteThread() API 의 원래 의도는 외부 프로세스에 스레드 함수를 실행시키는 것인데, 이 경우에 스레드 함수는 FreeLibrary() 가 되는 것입니다.)
BOOL WINAPI FreeLibrary(
HMODULE hLibModule
);
HMODULE hLibModule
);
ThreadProc 함수와 FreeLibrary 함수의 파라미터가 하나뿐이라는 점에 착안해서 나온 아이디어입니다.
(자세한 설명은 DLL Injection - 다른 프로세스에 침투하기 (2) 를 참고하세요.)
DLL Ejection 간단 실습
notepad 에 Injection 된 myhack.dll 을 Ejection 시켜보도록 하겠습니다.
먼저 첨부된 myhack.dll 을 C:\ 에 복사합니다.
notepad.exe 를 실행시킨 후 InjectDll.exe 를 실행시켜서 myhack.dll 을 notepad 프로세스에 Injection 시킵니다.
myhack.dll 과 InjectDll.exe 에 대한 자세한 설명은 아래 링크를 참고해주세요.
- 참고 : DLL Injection - 다른 프로세스에 침투하기 (1)
Process Explorer 로 확인해 보겠습니다.
<Fig. 1>
notepad 프로세스에 myhack.dll 이 Injection 된것을 확인 할 수 있습니다.
그럼 이제 EjectDll.exe 를 실행 시켜서 myhack.dll 이 Ejection 되는지 여러분 각자 해보시기 바랍니다. ^^
+---+
DLL Ejection 의 기본 동작 원리는 DLL Injection 과 동일하기 때문에 쉽게 이해할 수 있습니다.
처음 접하시는 분께서는 설명을 차근차근 읽어보시고 직접 실습해 보시기 바랍니다.
ReverseCore
'study' 카테고리의 다른 글
| API Hooking - 메모장 WriteFile() 후킹 (3) (26) | 2009/11/04 |
|---|---|
| API Hooking - 메모장 WriteFile() 후킹 (2) (3) | 2009/11/03 |
| API Hooking - 메모장 WriteFile() 후킹 (1) (22) | 2009/10/08 |
| API Hooking - Tech Map (10) | 2009/09/29 |
| API Hooking - 리버싱의 '꽃' (22) | 2009/09/22 |
| DLL Ejection - 침투시킨 DLL 빼내기 (20) | 2009/08/14 |
| DLL Injection - 다른 프로세스에 침투하기 (4) (27) | 2009/07/30 |
| DLL Injection - 다른 프로세스에 침투하기 (3) (6) | 2009/07/17 |
| DLL Injection - 다른 프로세스에 침투하기 (2) (99) | 2009/07/17 |
| DLL Injection - 다른 프로세스에 침투하기 (1) (10) | 2009/07/06 |
| 실행 압축 (Run-Time Packer) 에 관하여 (8) | 2009/06/11 |
CreateRemoteThread(),
DLL Ejection,
DLL Injection,
DLL 이젝션,
DLL 인젝션,
EjectDll.exe,
FreeLibrary(),
GetProcAddress(),
InjectDll.exe,
it,
LoadLibrary(),
Module32First(),
Module32Next(),
myhack.dll,
OpenProcess(),
Reverse Code Engineering,
Reverse Engineering,
Reversing,
VirtualAllocEx(),
VirtualFreeEx(),
WaitForSingleObject(),
WriteProcessMemory(),
리버싱,
소프트웨어 역공학
Trackback Address :: http://www.reversecore.com/trackback/44
- Tracked from www.reversecore.com 2009/08/18 21:42 삭제
Subject: DLL Injection - 다른 프로세스에 침투하기 (1)
DLL Injection 에 대해서 자세히 알아보겠습니다. DLL Injection 기법은 다른 프로세스에 침투하는 가장 쉽고 강력한 방법입니다. DLL Injection 이란? 다른 프로세스에 특정 DLL 파일을 강제로 삽입시키는 것입니다. 더 정확히 표현하면 다른 프로세스에게 LoadLibrary() API 를 호출하도록 명령하여 내가 원하는 DLL 을 loading 시키는 것입니다. 따라서 DLL Injection 이 일반적인 DLL loadi.. - Tracked from www.reversecore.com 2009/08/18 21:42 삭제
Subject: DLL Injection - 다른 프로세스에 침투하기 (2)
DLL Injection 구현 방법 몇 가지 구현 방법이 있습니다. 그 중에서 가장 유명한 방법이 CreateRemoteThread() API 를 이용하는 방법입니다. 이 방법은 윈도우즈 프로그래밍 서적의 바이블인 Jeffrey Richter 의 Programming Applications for Microsoft Windows 에 소개된 내용입니다. 일단 소스 코드를 보겠습니다. (엔지니어에게는 백 마디 설명 보다는 역시 소스 코드를 한번 보는게..
EjectDll.exe
myhack.dll