DLL Injection은 다른 프로세스 내 DLL을 강제로 삽입하는 것을 의미한다. 주로 코드 후킹을 위해 이를 사용한다.
DLL Injection을 하기 위해 사용하는 API로, 타 프로세스 메모리에 스레드를 생성할 수 있다. 즉 다른 프로세스에서 나의 코드가 실행되도록 할 수 있다.
실제 코드를 통해 어떻게 DLL Injection을 하는지 살펴보자
#include <windows.h>
#include <stdio.h>
#include <string.h>
int main() {
DWORD pid;
char dllPath[MAX_PATH] = {0};
printf("[*] Target PID: ");
scanf_s("%d", &pid);
printf("[*] Full path to DLL: ");
printf("%s\n", dllPath);
scanf_s("%s", dllPath, (unsigned)_countof(dllPath));
dllPath[MAX_PATH - 1] = '\0';
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) {
printf("[-] Failed to open process. Error: %lu\n", GetLastError());
return 1;
}
LPVOID pRemoteMemory = VirtualAllocEx(hProcess, NULL, strlen(dllPath) + 1,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pRemoteMemory) {
printf("[-] VirtualAllocEx failed. Error: %lu\n", GetLastError());
return 1;
}
WriteProcessMemory(hProcess, pRemoteMemory, dllPath, strlen(dllPath) + 1, NULL);
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (!hKernel32) {
printf("[-] GetModuleHandleA failed. Error: %lu\n", GetLastError());
return 1;
}
FARPROC pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
if (!pLoadLibrary) {
printf("[-] GetProcAddress failed. Error: %lu\n", GetLastError());
return 1;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
pRemoteMemory, 0, NULL);
if (!hThread) {
printf("[-] CreateRemoteThread failed. Error: %lu\n", GetLastError());
return 1;
}
printf("[+] DLL injected successfully!\n");
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}
DLL Injection을 하기위해 사용한 코드인데 한 부분씩 살펴보자
pid, dllPath를 입력 받는다. pid는 process id로 어떤 실행 파일이든 실행을 하면 고유 pid가 존재한다. 타겟 프로세스의 pid를 입력하고, 삽입할 dll의 경로를 입력해 준다.
입력받은 pid를 오픈해 준다. 프로세스를 연다는 의미는 현재 실행 중인 다른 프로세스에 대해 조작 가능한 핸들을 얻는 다는 것을 믜히나다.
메모리에 접근하거나, 쓰레드를 만들거나, 상태를 조회하는 것이 가능하다.
VirtualAllocEx함수는 프로세스의 가상 메모리 공간에 새로운 메모리 영역을 할당하는 함수다. 각 파라미터를 살펴보면
LPVOID VirtualAllocEx(
HANDLE hProcess, // 대상 프로세스 핸들
LPVOID lpAddress, // 할당받고 싶은 주소 (NULL이면 자동으로 선택)
SIZE_T dwSize, // 할당할 크기
DWORD flAllocationType,// MEM_COMMIT, MEM_RESERVE 등
DWORD flProtect // PAGE_READWRITE 등
);
위 함수를 해석하자면 dllPath+1 길이 만큼 타겟 프로세스의 가상 메모리 공간을 할당하게 된다.
프로세스 메모리에 쓰는 동작을 하게 된다. 위에서 할당한 메모리 공간에 DLL의 경로를 쓰는 동작을 한다.
kernel32.dll모듈의 핸들을 현재 프로세스에서 얻는다.
위에서 얻은 모듈을 이용하여 LoadLibraryA의 주소를 얻는다.
스레드를 생성하는 함수로 위에서 얻은 LoadLibraryA의 주소를 이용하여 타겟 프로세스에서 LoadLibraryA(pRemoteMemory)를 호출한다. pRemoteMemory는 위에서 dll경로를 저장한 메모리 공간이다.

DLL은 간단하게 DllMain만 있는 코드를 작성하였다. 작성한 코드는 빌드를 통해 dll파일을 생성해 주었다.

pid를 찾는 방법은 작업관리자의 세부정보를 보면 pid를 알 수 있다. 대상 프로그램은 위도우의 메모장으로 하였다.

위에서 작성한 코드를 실행하여 pid, dll경로를 입력하면 DLL Injection이 성공했다고 뜬다. 잘 됐는지 확인하기 위하여 Process Explorer 프로그램을 이용하여 확인하였다.

회색 부분을 보면 작성한 TestDll 파일이 존재하는 것을 볼 수 있다.
모든 프로그램에 대해서 DLL Injection이 되는 것은 아니다. window 프로그램 중에서는 Win32 app은 가능하제만 UWP app의 경우에는 보호기법이 적용되어 프로세스를 여는 것이 안되거나 특정 API를 사용하지 못하게 돼있다고 한다. 예를 들어 윈도우의 메모장 프로그램은 Injection이 가능했지만 계산기의 경우 불가능 하였다.(Window 7 계산기는 가능)
실제 상용 프로그램(kakao, chrome)등 에서는 하지 않는 것이 좋다. 법적 문제 등 위험 소재가 있다.
출처 : 리버스 엔지니어링 바이블(지은이 강병탁, 2012)