코드 후킹을 위한 DLL Injector 만들기 (CUI)

#REVERSING#C-Family

C++ CUI 환경에서 DLL Injection을 구현해 보기


코드 후킹은 출현한 지 오래되었지만 여전히 지금도 강력한 기법이다. 코드 후킹은 DLL Injection 작업이 선행돼야 가능하다.

인터넷에 DLL 인젝션 툴이 많이 있지만 대부분 게임해킹에 사용되어 백신과 안티치트에 검거되는 편이므로 직접 개인 DLL 인젝터를 만들 수 있어야 된다고 한다. 옛날에 온라인 게임 핵에서 사용하는 주사기가 이 DLL 인젝션에 해당한다.

전체 소스코드는 깃허브에 있으므로 핵심 코드만 정리

Process32First, Process32Next

DWORD GetPIDByName(LPCTSTR processName) {
    DWORD dwPID = 0;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 
    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);
 
    if (Process32First(hSnapshot, &pe)) {
        do {
            if (_tcscmp(pe.szExeFile, processName) == 0) {
                dwPID = pe.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapshot, &pe));
    }
 
    CloseHandle(hSnapshot);
    return dwPID;
}

우선 DLL 인젝션을 할 프로세스의 PID가 필요한데 CreateToolhelp32Snapshot()Process32First() 그리고 Process32Next()를 이용하여 프로세스의 PID를 구하는 코드가 위 코드이다.

프로세스의 이름과 확장자 예를 들어 Notepad.exe를 인자로 넘겨주면 해당 프로세스의 PID 값을 반환 시켜주는 함수이다.

CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 을 통해서 모든 프로세스에 대한 스냅샷을 생성하고 Process32First()Process32Next() 를 통해서 스냅샷을 기반으로 프로세스를 순회하면서 타켓 프로세스의 PID를 찾는다.


OpenProcess

// 프로세스 제어권 얻기
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

다음은 인젝션할 프로세스의 제어권을 가져와야 한다. 여기서 사용되는 함수가 OpenProcess이다. 이 함수는 타켓 프로세스의 PID가 필요한데 위에서 구한 PID를 이용하면 된다.

VirtualAllocEx, WriteProcessMemory

프로세스의 제어권을 가져왔다면 프로세스가 사용하는 메모리 내부에 접근이 가능하다. VirtualAllocEx는 타 프로세스에 메모리를 할당하는 API이다.

메모리를 할당할 사이즈는 DLL 경로 문자열 길이 정도면 충분하다.

그리고 할당된 공간에 WriteProcessMemory를 이용하여 DLL 경로를 작성한다.

// DLL 경로 문자열 길이만큼 공간 확보
SIZE_T dwBufSize = (_tcslen(DllPath) + 1) * sizeof(TCHAR);
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
// 확보된 공간에 DLL 경로 작성
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)DllPath, dwBufSize, NULL);

LoadLibraryW

이제 쓰여진 DLL을 프로세스에 로드하는 작업이 필요하다. Kernel32.dll 속의 LoadLibraryW함수를 이때 사용한다. 그러나 우리는 LoadLibraryW함수의 주소를 모르기 때문에 GetModuleHandle 를 통해 Kernel32.dll 의 핸들을 얻은 후 GetProcAddress 함수를 이용해서 LoadLibraryW함수의 주소를 찾는다.

// 쓰여진 DLL을 프로세스에서 로드하기
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

★CreateRemoteThread★

그리고 마지막으로 가장 핵심이라고 생각되는 CreateRemoteThread를 이용한다. 이 API는 타 프로세스의 메모리에 스레드를 생성할 수 있게 해준다. 타켓 프로세스에 스레드를 생성한 후 위에서 찾은 LoadLibraryW 의 주소를 이용하여 우리의 DLL을 인젝션한다.

// 쓰여진 DLL을 원격쓰레드 생성을 통해 프로세스에서 로드
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
// 쓰레드가 실행될때 까지 대기
WaitForSingleObject(hThread, INFINITE);

실제 작동

실제로 DLL 인젝션이 되는지 확인을 해보자. DLL_Injector.exe의 인자로 NotePad(타켓 프로세스)와 TestDll.dll(주입할 dll)을 인자로 주고 실행하면

우선 NotePad가 실행될 때까지 대기하다가 NotePad가 실행되면 바로 DLL 주입을 한다. cmd_result

그리고 프로세스 탐색기로 확인해보면 NotePad.exe에 TestDll.dll이 잘 주입된 것을 확인할 수 있었다. process_explorer