Operating System/이해하면 인생이 바뀌는 Windows API hook

DLL injection

Tony Lim 2024. 12. 13. 16:10

DLL 인젝션을 하는 이유와 알려진 방법들

  • CreateRemoteThread() API 활용
    • 가장많이 사용되는 방법
  • 레지스트리에서 AppInit_DLLs 값을 변경해서 인젝션
    • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows
  • SetWindowsHookEx() API 활용

 

 

TCP Socket API hook

  • Chatting client/server 예제를 활용해 Winsock API를 hook
    • Server는 전형적인 멀티스레드 기반으로 구현
      (접속하는 클라이언트 마다 스레드 생성)
    • 본 수업에서 예제 차제는 설명하지는 않음
  • 최초 Client 코드 수준에서 Hook 관련 코드를 추가해 동작을 확인하고 DLL Injection 기능을 추가하는 방식으로 확장

 

CreateRemoteTherad() 기반 DLL Injection

  • DLL injection 대상 프로세스를 개방해 새로운 스레드를 생성해주는 API
  • 스레드로 실행할 함수에 전달되어야 할 정보들 (매개변수 및 포인터가 가리키는 대상 메모리)은 대상 프로세스의 가상 메모리에 접근해 공간을 미리 확보하고 주소를 넘기는 방식
  • 대상 시스템에는 본래 존재하지 않았던 코드가 외부에 의해 실해되는 것이 핵심

Remote 는 원격이란 의미이지만 여기서는 Process 안에 없는 , 외부의 이런의미이다. 원격코드는 개발자가 process에 작성하지 않은 코드이다.

하고자하는 것은 Injector에서 CreateRemoteThread를 통해 Target Process에서 스레드를 생성하게 하고 LoadLibrary(path[Injection DLL] )를 호출하게 하는것이다.

DWORD getChatClientPid(void)
{
    DWORD aPid[1024] = { 0 };
    DWORD dwNeeded = 0;
    if (::EnumProcesses(aPid, sizeof(aPid), &dwNeeded))
    {
        DWORD count = dwNeeded / sizeof(DWORD);
        HANDLE hProcess;
        for (DWORD i = 0; i <= count; ++i)
        {
            hProcess = ::OpenProcess(
                PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                FALSE, aPid[i]);
            if (hProcess != NULL)
            {
                TCHAR name[MAX_PATH + _MAX_FNAME] = { 0 };
                ::GetModuleFileNameEx(hProcess, NULL, name, sizeof(name));
                if (wcsstr(name, L"chatClient.exe") != NULL)
                {
                    ::wprintf(L"%s [PID: %d]\n", name, aPid[i]);
                    return aPid[i];
                }

                ::CloseHandle(hProcess);
            }
            //else
            //    cout << "ERROR: Failed to open process. [PID: " <<
            //    aPid[i] << "]\n";
        }
    }

    return 0;
}

int main()
{
    setlocale(LC_ALL, "");
    DWORD pid = getChatClientPid();
    if (pid == 0) {
        puts("ERROR: Failed to get PID(Chat Cliet)!");
        return 0;
    }

    HANDLE hProcess = injectDll(pid);
    if (hProcess == NULL)
    {
        puts("ERROR: Failed to inject hook dll!");
        return 0;
    }

    puts("Press 'q' to unload hook module...");
    while (_getch() != 'q');

    ejectDll(hProcess, pid);
    ::CloseHandle(hProcess);
    return 0;
}

getChatClientPid에서 chatClient를 pid를 찾는다.

HANDLE injectDll(DWORD pid)
{
    HANDLE hTarget = NULL;
    wchar_t* pszParam = NULL;
    HANDLE hThread = NULL;
    DWORD threadId = 0;
    DWORD threadExitCode = 0;
    HMODULE(WINAPI * pfLoadLibraryW)(LPCWSTR) = NULL;
    wchar_t szLibPath[MAX_PATH + _MAX_FNAME] = {
        L"F:\\이해하면 인생이 바뀌는 Windows API hook\\ApiHook64"
        L"\\x64\\Release\\16_sockHook.dll"
    };

    //_putws(szLibPath);
    HMODULE hK32 = ::GetModuleHandle(L"Kernel32");
    pfLoadLibraryW = (HMODULE(WINAPI *)(LPCWSTR))
        ::GetProcAddress(hK32, "LoadLibraryW");

    hTarget = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (hTarget == NULL)
        goto CLEAN_UP;

    pszParam = (wchar_t*)::VirtualAllocEx(hTarget,
        NULL, sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE);
    if (pszParam == NULL)
        goto CLEAN_UP;

    ::WriteProcessMemory(hTarget, pszParam, szLibPath,
        sizeof(szLibPath), NULL);
    hThread = ::CreateRemoteThread(hTarget, NULL, 0,
        (LPTHREAD_START_ROUTINE)pfLoadLibraryW,
        pszParam, 0, &threadId);
    if (hThread == NULL)
        goto CLEAN_UP;

    if (::WaitForSingleObject(hThread, INFINITE) == WAIT_OBJECT_0)
    {
        puts("*** Injection Success ***");
    }

CLEAN_UP:
    if (hThread != NULL) ::CloseHandle(hThread);
    if (pszParam != NULL)
        ::VirtualFreeEx(hTarget, pszParam,
            MAX_PATH + _MAX_FNAME, MEM_RELEASE);

    return hTarget;
}

injectDLL에서 pid를 기반으로 process를 hTarget를 찾고 해당 process에 

pfLoadLibraryW 함수를 수행하는 스레드를 실행시킨다.

그러면 send socket api를 hooking 하는 ddl 을 targetProcess (client chat)이 로드를 하게 된다.

void ejectDll(HANDLE hTarget, DWORD pid)
{
    HANDLE hSnapshot = NULL;
    HANDLE hThread = NULL;

    MODULEENTRY32 me = { sizeof(me) };
    hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);

    BOOL bFlag = Module32First(hSnapshot, &me);
    for (; bFlag; bFlag = Module32Next(hSnapshot, &me))
    {
        if (!_wcsicmp(me.szModule, L"16_sockHook.dll") ||
            !_wcsicmp(me.szExePath, L"16_sockHook.dll"))
        {
            HMODULE hModule = GetModuleHandle(L"kernel32.dll");
            LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)
                ::GetProcAddress(hModule, "FreeLibrary");

            hThread = CreateRemoteThread(hTarget, NULL, 0,
                pThreadProc, me.modBaseAddr, 0, NULL);
            if (hThread != NULL)
            {
                ::WaitForSingleObject(hThread, INFINITE);
                ::CloseHandle(hThread);
                puts("*** Ejection Success ****");
            }
            break;
        }
    }

    ::CloseHandle(hSnapshot);
    return;
}

eject에서는  snapshot api를 통해 dll을 찾고 이를 FreeLibary로 언로드 시킨다.

 

 

'Operating System > 이해하면 인생이 바뀌는 Windows API hook' 카테고리의 다른 글

Inline hook  (0) 2024.12.02
IAT hook  (0) 2024.12.02
사전지식 - 두 번째  (0) 2024.11.27
사전 지식 - 첫 번째  (0) 2024.11.22