모두의 dream

DLL Injection & Ejection 본문

분야/Reversing

DLL Injection & Ejection

오리꽥이로 2022. 3. 21. 20:54
Contents 접기

공부를 하며 정리한 내용으로 잘못된 내용이 있을 수 있습니다.

 

DLL Injection

DLL Injection: 실행중인 다른 프로세스에 특정 DLL을 강제로 삽입하는 것.

 

DLL Injection의 전체적인 흐름.

1. 타겟 프로세스를 잡는다. (메모장, 계산기 등등)

2. DLL과 DLL injector를 제작한 후, DLL injector를 이용하여 DLL을 타겟 프로세스에 삽입한다.

DLL 에는 Injection 후 행동할 코드가 들어가고, Injector에는 단순히 Injection을 시키기 위한 코드가 들어간다.

 

DLL injection 환경 (+제작환경)

Windows11 (64bit)

visual studio 2019, 문자 집합: 멀티바이트 집합, x64 컴파일

 

DLL 제작

// Injdect_DLL.dll
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(NULL, L"DLL Injection", L"Success", MB_OK);
        // LoadLibrary를 호출하면 실행되는 부분
    case DLL_THREAD_ATTACH:
        // DLL을 이 프로세스 주소 영역에서 매핑 해제 (종료 처리)
    case DLL_THREAD_DETACH:
        // 스레드 생성
    case DLL_PROCESS_DETACH:
        // 스레드 종료
        break;
    }
    return TRUE;
}

DLL_PROCESS_ATTACH:
DLL이 프로세스 주소 공간에 최초로 매핑되면, DLL_PROCESS_ATTACH 값을 전달하여 DLLMain 함수 호출

 

위 DLL이 injection 되면 messagebox가 출력된다.

 

DLL Injector 제작

- 전체 코드

#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"

//#define DEF_PROC_NAME ("notepad.exe")
//#define DEF_DLL_PATH ("C:\\Users\\RokLcw\\source\\repos\\DLL_Injection\\x64\\Debug\\Inject_DLL.dll")

DWORD FindProcessID(LPCTSTR szProcessName);
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName);

int main(int argc, char* argv[])
{
	DWORD dwPID = 0xFFFFFFFF;

	printf("process: %s / DLL: %s\n", argv[1], argv[2]);
	dwPID = FindProcessID(argv[1]);	// DEF_PROC_NAME
	if (dwPID == 0xFFFFFFFF)
	{
		printf("<%s> 프로세스 발견 못함.\n", argv[1]);
		return 1;
	}

	InjectDll(dwPID, argv[2]);	// DEF_DLL_PATH

	return 0;
}

DWORD FindProcessID(LPCTSTR szProcessName)
{
	DWORD dwPID = 0xFFFFFFFF;
	HANDLE hSnapShot = INVALID_HANDLE_VALUE;
	PROCESSENTRY32 pe;

	pe.dwSize = sizeof(PROCESSENTRY32);
	hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

	Process32First(hSnapShot, &pe);
	do
	{
		if (!_stricmp(szProcessName, pe.szExeFile))
		{
			dwPID = pe.th32ProcessID;
			break;
		}
	} while (Process32Next(hSnapShot, &pe));

	CloseHandle(hSnapShot);

	return dwPID;
}

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
	HANDLE hProcess, hThread;
	LPVOID pRemoteBuf;
	DWORD dwBufSize = lstrlen(szDllName) + 1;
	LPTHREAD_START_ROUTINE pThreadProc;

	if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
		return FALSE;

	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);

	CloseHandle(hThread);
	CloseHandle(hProcess);

	return TRUE;
}

* 인터넷 여기저기서 짬뽕한 코드입니다. *

 

- Main 함수

int main(int argc, char* argv[])
{
	DWORD dwPID = 0xFFFFFFFF;

	printf("process: %s / DLL: %s\n", argv[1], argv[2]);
	dwPID = FindProcessID(argv[1]);	// DEF_PROC_NAME
	if (dwPID == 0xFFFFFFFF)
	{
		printf("<%s> 프로세스 발견 못함.\n", argv[1]);
		return 1;
	}

	InjectDll(dwPID, argv[2]);	// DEF_DLL_PATH

	return 0;
}

FindProcessID 함수를 이용하여 Injection 대상의 프로세스 ID를 얻는다.

그리고 InjectDll 함수로 Injection 대상의 프로세스 ID와 Injection 할 DLL의 주소를 전달해준다.

 

FindProcessID 함수의 코드는 아래와 같다.

DWORD FindProcessID(LPCTSTR szProcessName)
{
	DWORD dwPID = 0xFFFFFFFF;
	HANDLE hSnapShot = INVALID_HANDLE_VALUE;
	PROCESSENTRY32 pe;

	pe.dwSize = sizeof(PROCESSENTRY32);
	hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

	Process32First(hSnapShot, &pe);
	do
	{
		if (!_stricmp(szProcessName, pe.szExeFile))
		{
			dwPID = pe.th32ProcessID;
			break;
		}
	} while (Process32Next(hSnapShot, &pe));

	CloseHandle(hSnapShot);

	return dwPID;
}

Process32First와 Process32Next 함수를 이용하여 현재 실행중인 모든 프로세스를 찾는다.

내가 만약 notepad.exe 프로세스를 입력값으로 전달했다면 notepad.exe 프로세스를 찾을때 까지 Process32Next 함수로 찾아내게 된다.

 

* PROCESSENTRY32 : Process32First, Process32Next, CreateToolhelp32Snapshot API를 사용하기 위해서 지정하는 구조체.

* CreateToolhelp32Snapshot : 현재 실행중인 프로세스들의 정보를 캡쳐해서 불러온다.

 

- InjectDLL 함수

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
	HANDLE hProcess, hThread;
	LPVOID pRemoteBuf;
	DWORD dwBufSize = lstrlen(szDllName) + 1;
	LPTHREAD_START_ROUTINE pThreadProc;

	if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
		return FALSE;

	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);

	CloseHandle(hThread);
	CloseHandle(hProcess);

	return TRUE;
}

가장 핵심이 되는 코드다.

 

5개의 API가 순서대로 실행된다.

OpenProcess, VirtualAllocEx, WriteProcessMemory, GetProcAddress, CreateRemoteThread

 

(1) OpenProcess: 프로세스 ID를 이용하여 프로세스의 핸들을 얻는다. (Injection 할 프로세스 제어권 획득)
핸들을 얻으면 메모리 내부에 접근이 가능해진다.

 

다음으로 Injection 할 DLL 경로를 해당 프로세스에 저장한다.

(2) VirtualAllocEx: DLL 경로(문자열)를 저장할 공간을 할당한다. (DLL 경로 길이만큼 할당함. dwBufSize 변수)

그리고 할당된 주소값이 pRemoteBuf 변수에 저장된다.

 

(3) WriteProcessMemory: 할당된 주소에 DLL 경로(문자열)를 저장한다.

 

Injection할 DLL의 경로를 저장했으니 로드를 하면 된다.

(4) GetProcAddress: LoadLibrary API를 이용하여 로드를 한다. 

LoadLibrary API는 kerner32.dll 내부에 존재하기 때문에 GetModulHandle을 이용하여 kernel32.dll의 핸들을 구한다.

구한 핸들값을 이용하여 LoadLibrary의 주소를 변수에 저장한다.

(LoadLibrary API를 이용하려면 주소를 구해서 불러와야 사용할 수 있다.)

 

(5) CreateRemoteThread

내가 Injection할 대상 프로세스(1번에서 핸들을 이용하여 제어권 획득)가 LoadLibrary API를 호출하도록 한다.

최종적으로 LoadLibrary API 가 PRemoteBuf 주소에 있는 DLL을 불러와서 Injection에 성공하게 된다.

 

여기서 의문점. 위에서 불러온 LoadLibrary API는 Injector 프로그램에 있는 kernel32.dll의 LoadLibrary API 주소일텐데 어떻게 Injection 당하는 프로그램에서 정상적으로 사용을 한걸까?

-> kernel32.dll은 모든 프로그램의 프로세스에서 같은 주소에 로드된다고 한다. (Image Base 확인)

 

결과:

MessageBox & process explorer dll check

 

참고: DLL Injection - 다른 프로세스에 침투하기 (2) :: 리버스코어 ReverseCore

 

DLL Injection - 다른 프로세스에 침투하기 (2)

DLL Injection 구현 방법 몇 가지 구현 방법이 있습니다. 그 중에서 가장 유명한 방법이 CreateRemoteThread() API 를 이용하는 방법입니다. 이 방법은 윈도우즈 프로그래밍 서적의 바이블인 Jeffrey Richter 의

reversecore.com

 

DLL Ejection

- 사용자가 강제로 집어넣은 DLL을 빼냄.

- Injection 진행시 GetProcAddress API 에서 LoadLibrary API를 불러왔다면, Ejection 에서는 FreeLibray를 불러온다.

'분야 > Reversing' 카테고리의 다른 글

프로세스와 스레드, 핸들(Windows)과 커널  (0) 2022.04.02
윈도우 권한 (Windows privilege)  (0) 2022.04.01
Comments