'DLL Injection'에 해당되는 글 3건

  1. 2008.12.03 SetWindowsHookEx()를 이용한 DLL Injection을 막을 수 있는 방법은 없을까?!
  2. 2008.12.03 DLL Injection
  3. 2008.11.15 DLL Injection
Programming/Kernel / Driver2008. 12. 3. 10:10

※ 참고 : 이 글은 "User32!SetWindowsHookExA/W 를 후킹하지 않는다" 와 "오직 커널 레벨에서만 막는다(?)"는 가정하에 작성되었습니다.


보통 메시지 훅을 할 때는, User32.dll에 Export 되어있는 SetWindowsHookExA/SetWindowsHookExW 라는 API를 이용합니다.

[물론 아닌 경우도 있겠지만, 적어도 저는 이걸 이용합니다!]


이 API를 이용해서 DLL을 Injection을 할 수도 있습니다.

아래와 같은 경우죠.


DLL InjectTarget.dll의 Export된 함수인 MsgProc를 HOOKPROC로 사용하는 경우

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, INT nCmdShow)

{

      PVOID hHook;

      PVOID AddressOfFunction=(PVOID)GetProcAddress(LoadLibrary("injectTarget.dll"), "MsgProc");

      hHook=(PVOID)SetWindowsHookEx(......, AddressOfFunction, ......);

      ......중략......

}


그런데, 이상한 것은, SetWindowsHookExA/SetWindowsHookExW는 다른 함수와는 달리, 프로세스에 별다른 접근을 하지 않고

바로 SYSENTER를 합니다.

그리하여, 프로세스를 숨겨버린다 해도 SetWindowsHookEx()를 막기는 힘듭니다.

심지어 자기자신을 숨기고, KeAttachProcess/KeStackAttachProcess마저 후킹하는 nProtect마저도

SetWindowsHookEx()를 이용한 DLL Injection은 막지 못하였습니다. [지금은 바뀌었는지 모르겠지만]

필자는 프로세스를 보호하기 위해 커널 모드에서 PsLookupXxx 루틴과 KeXxxAttachProcess 루틴, 심지어는 ObXxx 루틴까지 후킹을 했으나

윈도우 프로세스의 경우 SetWindowsHookEx를 막아낼 수 없었습니다.


그 이유를 지금부터 알아보기 위해, SetWindowsHookExA/W를 차차 살펴보기로 합시다.


USER32!SetWindowsHookExA를 보면......

사용자 삽입 이미지


USER32!SetWindowsHookExW를 보면......

사용자 삽입 이미지


위와 같이, USER32.DLL의 SetWindowsHookA/W는 내부적으로 USER32.DLL::SetWindowsHookExAW를 호출합니다.


이제는 SetWindowsHookExAW를 보면......

사용자 삽입 이미지


SetWindowsHookAW는 USER32!_SetWindowsHookEx를 호출하는군요.


이어서 _SetWindowsHookEx를 봅시다.

사용자 삽입 이미지


_SetWindowsHookEx는 결과적으로 USER32!NtUserSetWindowsHookEx를 호출합니다.

아마도 USER32!NtUserSetWindowsHookEx는 NTDLL!NtXxx 루틴과 비슷한 일을 하는것으로 예상됩나다.


드디어, NtUserSetWindowsHookEx를 보는군요.

사용자 삽입 이미지


자 이것을 전부 정리(?) 해 보면,

SetWindowsHookExA/SetWindowsHookExW->SetWindowsHookExAW->_SetWindowsHookEx->NtUserSetWindowsHookEx 순이 되는군요.

그렇다면, Win32K!NtUserSetWindowsHookEx를 확인해 봐야겠군요.


자, Win32K!NtUserSetWindowsHookEx를 봅시다.

사용자 삽입 이미지


자, 보이십니까???

Win32K!NtUserSetWindowsHookEx는 내부적으로 Internal 함수인 Win32k!zzzSetWindowsHookEx를 호출합니다.


Win32k!zzzSetWindowsHookEx를 한번 보면......

사용자 삽입 이미지

여기서는 별다른 특별한 것은 보이지 않았습니다.


다음 장을 봅시다.

사용자 삽입 이미지

으으으음. AddHmodDependency()라...... 웬지 이 함수를 보면 뭔가 알 수 있을 것 같기도 한데.......

계속해서 봅시다.

사용자 삽입 이미지

으음.....zzzJournalAttach같은 내부함수도 보이는군요.

그나저나, 이 함수는 어디서 끝나는 걸까요.....하악하악

사용자 삽입 이미지

드디어 끝났습니다! [zzzSetWindowsHookEx가 이리 길줄이야......OTL]

으음....수많은 내부함수들이 보이는군요.

그나저나, 코드가 상당히 길어 분석이 좀 까다롭게 되었군요.


어쩔 수 없이, W2K 소스 참조.

PHOOK zzzSetWindowsHookEx(
    HANDLE hmod,
    PUNICODE_STRING pstrLib,
    PTHREADINFO ptiThread,
    int nFilterType,
    PROC pfnFilterProc,
    DWORD dwFlags)
{
    ACCESS_MASK amDesired;
    PHOOK       phkNew;
    TL          tlphkNew;
    PHOOK       *pphkStart;
    PTHREADINFO ptiCurrent;

    /*
     * Check to see if filter type is valid.
     */
    if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) {
        RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, "");
        return NULL;
    }

    /*
     * Check to see if filter proc is valid.
     */
    if (pfnFilterProc == NULL) {
        RIPERR0(ERROR_INVALID_FILTER_PROC, RIP_VERBOSE, "");
        return NULL;
    }

    ptiCurrent = PtiCurrent();

    if (ptiThread == NULL) {
        /*
         * Is the app trying to set a global hook without a library?
         * If so return an error.
         */
         if (hmod == NULL) {
             RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, "");
             return NULL;
         }
    } else {
        /*
         * Is the app trying to set a local hook that is global-only?
         * If so return an error.
         */
        if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) {
            RIPERR0(ERROR_GLOBAL_ONLY_HOOK, RIP_VERBOSE, "");
            return NULL;
        }

        /*
         * Can't hook outside our own desktop.
         */
        if (ptiThread->rpdesk != ptiCurrent->rpdesk) {
            RIPERR0(ERROR_ACCESS_DENIED,
                   RIP_WARNING,
                   "Access denied to desktop in zzzSetWindowsHookEx - can't hook other desktops");

            return NULL;
        }

        if (ptiCurrent->ppi != ptiThread->ppi) {
            /*
             * Is the app trying to set hook in another process without a library?
             * If so return an error.
             */
            if (hmod == NULL) {
                RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, "");
                return NULL;
            }

            /*
             * Is the app hooking another user without access?
             * If so return an error. Note that this check is done
             * for global hooks every time the hook is called.
             */
            if ((!RtlEqualLuid(&ptiThread->ppi->luidSession,
                               &ptiCurrent->ppi->luidSession)) &&
                        !(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) {

                RIPERR0(ERROR_ACCESS_DENIED,
                        RIP_WARNING,
                        "Access denied to other user in zzzSetWindowsHookEx");

                return NULL;
            }

            if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) &&
                    !(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {

                /*
                 * Can't hook console or GUI system thread if inter-thread
                 * calling isn't implemented for this hook type.
                 */
                 RIPERR1(ERROR_HOOK_TYPE_NOT_ALLOWED,
                         RIP_WARNING,
                         "nFilterType (%ld) not allowed in zzzSetWindowsHookEx",
                         nFilterType);

                 return NULL;
            }
        }
    }

    /*
     * Check if this thread has access to hook its desktop.
     */
    switch( nFilterType ) {
    case WH_JOURNALRECORD:
        amDesired = DESKTOP_JOURNALRECORD;
        break;

    case WH_JOURNALPLAYBACK:
        amDesired = DESKTOP_JOURNALPLAYBACK;
        break;

    default:
        amDesired = DESKTOP_HOOKCONTROL;
        break;
    }

    if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired)) {
         RIPERR0(ERROR_ACCESS_DENIED,
                RIP_WARNING,
                "Access denied to desktop in zzzSetWindowsHookEx");

         return NULL;
    }

    if (amDesired != DESKTOP_HOOKCONTROL &&
        (ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO)) {
        RIPERR0(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION,
                RIP_WARNING,
                "Journal hooks invalid on a desktop belonging to a non-interactive WindowStation.");

        return NULL;
    }

#if 0
    /*
     * Is this a journal hook?
     */
    if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
        /*
         * Is a journal hook of this type already installed?
         * If so it's an error.
         * If this code is enabled, use PhkFirstGlobalValid instead
         *  of checking phkStart directly
         */
        if (ptiCurrent->pDeskInfo->asphkStart[nFilterType + 1] != NULL) {
            RIPERR0(ERROR_JOURNAL_HOOK_SET, RIP_VERBOSE, "");
            return NULL;
        }
    }
#endif

    /*
     * Allocate the new HOOK structure.
     */
    phkNew = (PHOOK)HMAllocObject(ptiCurrent, ptiCurrent->rpdesk,
            TYPE_HOOK, sizeof(HOOK));
    if (phkNew == NULL) {
        return NULL;
    }

    /*
     * If a DLL is required for this hook, register the library with
     * the library management routines so we can assure it's loaded
     * into all the processes necessary.
     */
    phkNew->ihmod = -1;
    if (hmod != NULL) {

#if defined(WX86)

        phkNew->flags |= (dwFlags & HF_WX86KNOWNDLL);

#endif

        phkNew->ihmod = GetHmodTableIndex(pstrLib);

        if (phkNew->ihmod == -1) {
            RIPERR0(ERROR_MOD_NOT_FOUND, RIP_VERBOSE, "");
            HMFreeObject((PVOID)phkNew);
            return NULL;
        }

        /*
         * Add a dependency on this module - meaning, increment a count
         * that simply counts the number of hooks set into this module.
         */
        if (phkNew->ihmod >= 0) {
            AddHmodDependency(phkNew->ihmod);
        }
    }

    /*
     * Depending on whether we're setting a global or local hook,
     * get the start of the appropriate linked-list of HOOKs.  Also
     * set the HF_GLOBAL flag if it's a global hook.
     */
    if (ptiThread != NULL) {
        pphkStart = &ptiThread->aphkStart[nFilterType + 1];

        /*
         * Set the WHF_* in the THREADINFO so we know it's hooked.
         */
        ptiThread->fsHooks |= WHF_FROM_WH(nFilterType);

        /*
         * Set the flags in the thread's TEB
         */
        if (ptiThread->pClientInfo) {
            BOOL fAttached;

            /*
             * If the thread being hooked is in another process, attach
             * to that process so that we can access its ClientInfo.
             */
            if (ptiThread->ppi != ptiCurrent->ppi) {
                KeAttachProcess(&ptiThread->ppi->Process->Pcb);
                fAttached = TRUE;
            } else
                fAttached = FALSE;

            ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks;

            if (fAttached)
                KeDetachProcess();
        }

        /*
         * Remember which thread we're hooking.
         */
        phkNew->ptiHooked = ptiThread;

    } else {
        pphkStart = &ptiCurrent->pDeskInfo->aphkStart[nFilterType + 1];
        phkNew->flags |= HF_GLOBAL;

        /*
         * Set the WHF_* in the SERVERINFO so we know it's hooked.
         */
        ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(nFilterType);

        phkNew->ptiHooked = NULL;
    }

    /*
     * Does the hook function expect ANSI or Unicode text?
     */
    phkNew->flags |= (dwFlags & HF_ANSI);

    /*
     * Initialize the HOOK structure.  Unreferenced parameters are assumed
     * to be initialized to zero by LocalAlloc().
     */
    phkNew->iHook = nFilterType;

    /*
     * Libraries are loaded at different linear addresses in different
     * process contexts.  For this reason, we need to convert the filter
     * proc address into an offset while setting the hook, and then convert
     * it back to a real per-process function pointer when calling a
     * hook.  Do this by subtracting the 'hmod' (which is a pointer to the
     * linear and contiguous .exe header) from the function index.
     */
    phkNew->offPfn = ((ULONG_PTR)pfnFilterProc) - ((ULONG_PTR)hmod);

#ifdef HOOKBATCH
    phkNew->cEventMessages = 0;
    phkNew->iCurrentEvent  = 0;
    phkNew->CacheTimeOut = 0;
    phkNew->aEventCache = NULL;
#endif //HOOKBATCH

    /*
     * Link this hook into the front of the hook-list.
     */
    phkNew->phkNext = *pphkStart;
    *pphkStart = phkNew;

    /*
     * If this is a journal hook, setup synchronized input processing
     * AFTER we set the hook - so this synchronization can be cancelled
     * with control-esc.
     */
    if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
        /*
         * Attach everyone to us so journal-hook processing
         * will be synchronized.
         * No need to DeferWinEventNotify() here, since we lock phkNew.
         */
        ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
        if (!zzzJournalAttach(ptiCurrent, TRUE)) {
            RIPMSG1(RIP_WARNING, "zzzJournalAttach failed, so abort hook %#p", phkNew);
            if (ThreadUnlock(&tlphkNew) != NULL) {
                zzzUnhookWindowsHookEx(phkNew);
            }
            return NULL;
        }
        if ((phkNew = ThreadUnlock(&tlphkNew)) == NULL) {
            return NULL;
        }
    }

    UserAssert(phkNew != NULL);

    /*
     * Later 5.0 GerardoB: The old code just to check this but
     *  I think it's some left over stuff from server side days.
    .* Let's assert on it for a while
     * Also, I added the assertions in the else's below because I reorganized
     *  the code and want to make sure we don't change behavior
     */
    UserAssert(ptiCurrent->pEThread && THREAD_TO_PROCESS(ptiCurrent->pEThread));

    /*
     * Can't allow a process that has set a global hook that works
     * on server-side winprocs to run at background priority! Bump
     * up it's dynamic priority and mark it so it doesn't get reset.
     */
    if ((phkNew->flags & HF_GLOBAL) &&
            (abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {

        ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER;
        KeSetPriorityThread(&ptiCurrent->pEThread->Tcb, LOW_REALTIME_PRIORITY-2);

        if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
            ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
            /*
             * If we're changing the journal hooks, jiggle the mouse.
             * This way the first event will always be a mouse move, which
             * will ensure that the cursor is set properly.
             */
            zzzSetFMouseMoved();
            phkNew = ThreadUnlock(&tlphkNew);
            /*
             * If setting a journal playback hook, this process is the input
             *  provider. This gives it the right to call SetForegroundWindow
             */
            if (nFilterType == WH_JOURNALPLAYBACK) {
                gppiInputProvider = ptiCurrent->ppi;
            }
        } else {
            UserAssert(nFilterType != WH_JOURNALPLAYBACK);
        }
    } else {
        UserAssert(!(abHookFlags[nFilterType + 1] & HKF_JOURNAL));
        UserAssert(nFilterType != WH_JOURNALPLAYBACK);
    }



    /*
     * Return pointer to our internal hook structure so we know
     * which hook to call next in CallNextHookEx().
     */
    DbgValidateHooks(phkNew, phkNew->iHook);
    return phkNew;
}

음......단순한 구조체 조작만 하는 것 같지는 않아보이고

AddHmodDependency(), HMAllocObject(), ... 등이 뭔가를 하는 것 같은데 말이죠.

음 어쨌거나, SetWindowsHookEx()는 유저 모드에서는 물론, 커널 모드에서도 객체를 직접 수정하거나 Win32K의 또다른 내부함수를 호출하는 것이었기 때문에

SetWindowsHookEx()를 막을 수 없었던 것 같습니다.

HOOK 구조체를 초기화해도, 이미 Inject된 DLL이 언로드될것 같지는 않고 말이죠.

또한, 후킹되었다면 WIN32K!zzzUnhookWindowsHookEx()를 호출하는 방법도 있겠지만, 이 방법은 일단

zzzUnhookWindowsHookEx의 주소를 얻어와야 하므로, 굉장히 어렵습니다.

지금으로서는 Win32K!NtUserSetWindowsHookEx를 후킹하거나, Win32K!zzzSetWindowsHookEx를 후킹하는 수밖에 없는 것 같습니다.

(UserMode Hook은 제외)


Win32K!NtUserSetWindowsHookEx에서 Win32K!zzzSetWindowsHookEx를 호출하므로, 주소를 얻기는 쉽습니다.

실제로 제가 Win32K!zzzSetWindowsHookEx를 후킹해서 쓰고 있으나, 아직까지 별 문제는 일어나지 않았습니다.

Posted by skensita
Programming/Win32 API2008. 12. 3. 10:05

DLL Injection - 참고

Posted 2007/06/19 14:16, Filed under: Study/Computer Science
================================================================================
Title : DLL Injection

Author : proXima
Contact : proxima ]at[ postech.ac.kr
Date : 2006/11/15 - 2006/11/15
================================================================================

2부에서 DLL Injection에 대해 간단하게 언급했었다. 이번 글에서는 DLL Injection이 무엇인지, 어떤 방법으로 이루어질 수 있는지를 알아보도록 하자.

일단, DLL Injection이 무엇인지부터 간단하게 알아보는 것이 좋을 것 같다.
단어 그대로 해석해 보자면, DLL Injection이라는 것은 'DLL 주입'이라는 뜻을 가지는데, 그냥 이 말만 가지고는 뜻이 명확하지가 않다. DLL을 주입한다고? 그게 무슨 뜻이지?

1. DLL을 주입하다
다들 알고 있겠지만(몰랐다면 앞으로 알면 좋다), 프로세스는 실행파일 하나만 메모리에 달랑 올린다고 실행이 되는 것이 아니라, 실행에 필요한 동적 라이브러리들을 같이 메모리에 올려야만 실행이 가능하다. DLL이라는 것은 Windows에서 제공하는 동적 라이브러리 형태로서, 리눅스에서의 .so 파일과 같은 역할을 한다고 보면 된다.
방금 설명했듯이, 동적 라이브러리도 프로세스의 메모리 공간에 올라가게 되는데, 디버거를 통해 확인해 보면 아마 보통 ntdll.dll, kernel32.dll, user32.dll 등의 dll 파일들이 프로세스의 실행파일 이미지와 같이 메모리에 올라가 있는 것을 확인할 수 있을 것이다.
DLL은 실행파일과 비슷하다. 그 자체로 실행파일이 되지 않는다는 것을 제외한다면, 파일의 내부는 실행파일과 다른 점이 없다. (.exe 파일도 함수를 export할 수 있다.)
그러면 DLL을 다른 프로세스에 주입한다는 게 무슨 뜻인가 하면... 다른 프로세스의 메모리 공간에 내가 만든 DLL을 매핑한다는 뜻이다. 하지만 단순이 메모리에 DLL을 매핑하는 것이 다는 아니다. DLL이 로드될 때 자동으로 실행되는 DllMain 에서 내가 원하는 코드를 '다른 프로세스 내에서' 실행시킬 수도 있다. 바로 이게 핵심이다.
DLL Injection을 통해 내가 원하는 코드를 '다른 프로세스 내에서' 실행시킬 수 있다.


2. 이게 어째서 가능한가
앞에서 이것저것 설명했었지만, 이게 가능한 이유는 Windows가 다음와 같은 환경을 제공해 주기 때문이다.
1) 런타임에 동적으로 DLL 파일을 로드할 수 있다.
2) 다른 프로세스의 메모리 공간에 데이터를 쓸 수 있다.
3) 다른 프로세스에서 함수를 실행시킬 수 있다
4) DLL이 로드될 때 로드된 DLL 파일 내의 DllMain 함수가 실행된다!


3. DLL Injection을 발생시키는 과정
Windows에서는 프로세스가 런타임에 동적으로 DLL 파일을 로드하도록 해 주는 LoadLibrary라는 함수가 있다. 그리고 다른 프로세스에서 그 LoadLibrary를 실행할 수가 있다. 그렇다면 내가 DLL을 만든 뒤에 그 DLL을 다른 프로세스에서 LoadLibrary를 통해 로드하도록 하면, 내가 만든 DLL을 주입할 수 있는 것이다.
그렇다면 어떻게 다른 프로세스에서 함수를 실행시킬 수 있을까? 바로 CreateRemoteThread라는 함수를 이용하는 것이다. CreateRemoteThread는 다른 프로세스 내에 새로운 쓰레드를 생성시키는데, 그 생성된 쓰레드로 하여금 어떤 함수를 실행하게 할 것인지를 인자로 넘길 수 있다.
그렇기 때문에 CreateRemoteThread를 이용하여 LoadLibrary를 실행하게 한다면, 내가 만든 DLL의 DllMain이 실행된다.
하지만 넘어가야 하는 난관이 조금 있는데, 일단 CreateRemoteThread와 LoadLibrary의 프로토타입을 보자.


HANDLE CreateRemoteThread(  HANDLE hProcess,
 LPSECURITY_ATTRIBUTES lpThreadAttributes,
 SIZE_T dwStackSize,
 LPTHREAD_START_ROUTINE lpStartAddress,
 LPVOID lpParameter,
 DWORD dwCreationFlags, 
 LPDWORD lpThreadId
);

HMODULE LoadLibrary(
  LPCTSTR lpFileName
);

 
프로토타입을 보면서 조금 더 설명을 하자면, DLL Injection이 가능한 이유는, LoadLibrary가 인자를 '단 하나'만 받기 때문이다. CreateRemoteThread는 쓰레드를 새로 만들면서 쓰레드로 실행되는 함수에게 인자를 하나 넘길 수 있는데, LoadLibrary는 아주 다행스럽게 인자를 '단 하나'만 받는다.

하지만, LoadLibrary를 실행하기 위해서는 '로드할 DLL 파일명'을 LoadLibrary의 인자로 넘겨주어야 한다. 그렇다면 다른 프로세스의 메모리 공간에 내가 원하는 '로드할 DLL 파일명'을 써 넣어야 하는데, 그것은 이 전에 쓴 글인 'Controlling Memory Space of Other Process'에서 언급했듯이, Windows 에서 제공해 주는 API들을 사용해서 가능하게 할 수 있다.

그렇다고 하더라도 가장 큰 문제가 있는데, 그게 무엇인가 하면 바로 LoadLibrary의 주소는 '타겟 프로세스'의 가상메모리 상에서의 주소여야 한다는 점이다. 알고 있다시피, DLL들은 프로그램이 실행될 때마다 다른 메모리 주소에 매핑될 수 있는데, 그것 때문에 타겟 프로세스의 LoadLibrary가 어떤 주소에 위치하는지를 모른다면 이것은 말짱 꽝이 될 수도 있다.
그래도 이걸 해결하는 방법이 있다. 다른 프로세스의 메모리를 읽어 kernel32.dll 이 그 프로세스의 어느 메모리 주소에 위치하는지 알아내고, 그 kernel32.dll 이 export하는 함수들을 읽으면서 LoadLibrary의 주소가 어디 있는지 알아낼 수 있다. 뭐 그 밖에도 다른 방법도 있을 수 있지만, 지금은 이게 문제가 되지 않는다. 왜냐하면...

서로 다른 프로세스라도 kernel32.dll 은 가상메모리 상에서 거의 항상 같은 메모리 주소에 매핑되기 때문에, 그냥 내 프로세스의 LoadLibrary 주소를 넘기면 된다.


4. 코드를 짜 보자


HANDLE hProcess;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

LPVOID pFileName;
pFileName = HeapAlloc(NULL, strlen(fileName)+1, MEM_COMMIT, \
                             PAGE_READWRITE);

WriteProcessMemory(hProcess, pFileName, fileName, strlen(fileName)+1, \
                   &nWritten);

HANDLE hRemoteThread;
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, \
                                   (LPTHREAD_START_ROUTINE)LoadLibrary,
                                   pFileName, 0, NULL);

CloseHandle(hRemoteThread);
CloseHandle(hProcess);
 

이제 DLL Injection 을 할 수가 있게 됐다. 내가 DLL을 만들어서 다른 프로세스에 넣어보자. 실제로 스피드핵의 경우 timeGetTime 함수를 뺏는 역할을 하는 DLL을 프로세스에 주입함으로써 빠르게 돌아가게 하는 방법도 사용하는 프로그램도 있다.

다른 프로세스의 내부에 내가 원하는 코드를 집어넣는다는 것은 굉장히 강력할 수 있다. 때문에 악용될 경우 큰 피해가 날 수도 있는데, 아무쪼록 좋은 곳에 사용하길 바란다.
Posted by skensita
Programming/Win32 API2008. 11. 15. 19:20

DLL Injection Howto

 

 

 

 Table Of Contents

 

 

1.  개요

2.  Method1: Using Registry

3.  Method1: Using Windows Hook Function

4.  Method2: VirtualAllocEx & CreateRemoteThread

5.  Method3: CreateRemoteThread & WriteProcessMemory

 

 

  

1. 개요

 

- DLL Injection 이란?

 

‘DLL Injection’ 은 이미 실행되어 있는 프로세스로 DLL Code 를 삽입하는 기법들을 말한다. 이 방법은 말 그대로 특정 프로세스를 실행하기에 앞서, 필요한 코드를 프로세스를 실행하는 지점에서 가로챈 뒤 자신의 코드를 먼저 주입(inject, 실행) 하고 제어권을 다시 돌려주는 방법을 뜻하며, 이러한 방법은 ‘Code Injection’ 이라 불리우기도 한다. 이미 실행되어 있는 Process – 현재 Runtime 에서 동작되고 있는 Process - 를 변경하여 사용하기도 하며, 특정 API 를 실행하고자 할 때 Code 를 먼저 Process 상에 Injection 하여 사용하기도 한다. ‘DLL Injection’ 을 통해 구현할 수 있는 기능들은 다음과 같다. 특정 함수를 실행하기에 앞서 필요한 작업을 하는데 사용하거나, 기존 함수들의 동작을 Monitoring 하고자 할 때 사용하며, 또는 기존의 기능을 확장하고자 할 때도 사용할 수 있을 것이다.

 

이 문서에서는 ‘DLL Injection’ 을 구현하고자 할 때 사용하는 여러 가지 방법들을 소개할 것이다.

 

- DLL Injection 을 위해 필요한 기반 지식

 

 WIN32 API 에 대한 기본 지식, DLL Process, Thread 의 동작원리를 알고 있으면 DLL Injection 을 하는데 큰 어려움이 없다. 이 외에도 Windows 에서 Process 를 실행 할 때 DLL 을 호출하는 원리를 알면 된다.


  

일반적으로 DLL 은 한 프로세스 프로그램 - 이 시작될 때 프로그램의 address 영역으로

Mapping 이 이루어 진다. Mapping 이 이루어 질 경우, DLL explicit 하게 호출이 되는지, implicit 하게 호출이 되는지 여부와 상관없이 entry point 함수인 DllMain 을 호출하며 cleanup, initialize 동작을 수행할 것이다. DLL 자체는 DLL 을 호출한 (프로세스의) 쓰레드 address 공간에 자신의 주소를 가지게 될 것이며, 이로 인해 DLL 코드가 프로세스 상의 내부 영역 프로세스가 사용하는 영역 에서 Dispatch 를 수행한다든지, API 를 가로채는 등의 기능을 할 수 있게 되는 것이다.

 

사용자 삽입 이미지


2.
Method1: Using Registry

- Registry: AppInit_DLLs (http://support.microsoft.com/kb/197571)

: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

 

Registry 를 이용하여 모든 프로세스에서 DLL 을 로드할 수 있다. 위에 언급한 레지스트리 경로에 있는 AppInit_DLLs subkey 에 삽입하고자 하는 DLL 의 경로와 이름을 지정해 놓으면, Windows 에서 USER32.DLL 을 사용하는 모든 프로세스는 이 DLL 을 자동으로 LoadLibrary(…)를 이용하여 실행한다.

 

실제로 이 방법을 사용할 경우는 없을 것이다. 효율적이지 못한 것 – 모든 프로세스가DLL 을 로드하게 되는 부하가 생길 것이며, DLL 코드 내부에서 특정 상태에 따른 처리를 해 주어야 하는 문제가 생기는 것 - 이 첫째 이유라면 그럴 것이고, DLL 코드 내부에서 사용할 수 있는 함수도 KERNEL32.DLL 에서 export 되는 함수에 국한될 것이기 때문이다.

 

만약 테스트를 해 보고자 한다면 Registry 에 값을 쓰고 시스템을 리부팅을 꼭 해야 적용이 될 것이다.

 

 

3. Method2: Using Windows Hook Function

- SetWindowHookEx, UnhookWindowsHookEx

 

HHOOK SetWindowsHookEx(

  int idHook,        // type of hook to install

  HOOKPROC lpfn,     // address of hook procedure

  HINSTANCE hMod,    // handle to application instance

  DWORD dwThreadId   // identity of thread to install hook for

);

 

BOOL UnhookWindowsHookEx(
  HHOOK hhk   // handle to hook procedure to remove
);

 

사용자 삽입 이미지
 

 

 Windows 에서 Hook 함수를 사용하여 DLL Inject 하게 되면, 내부적으로는 Hook Procedure 만이 아니라, Hook Procedure 가 들어 있는 DLL 코드 전체가 프로그램의 코드 영역에 mapping 되기 때문에, DLL 코드가 실행되는 영역이 결국 DLL 을 호출한 프로그램의 내부 영역이 된다. 내부 메시지를 hook 을 걸거나, window procedure hook 을 걸어 필요한 작업을 진행하면 될 것이고 SetWindowLong(…) 을 이용하여 관련 함수를 Dispatch 하는 방법을 사용하면 될 것이다.

 

Windows Hook 을 사용하고자 할 때 코드 작성 방식은 다음과 같다.

 

- A) DLL Shared 영역 지정

: Global 변수들을 Shared 로 지정하여 DLL 을 사용하는 모든 프로그램에 대해 DLL 이 로드되는 시간 동안 DLL 간의 공유 가능한 영역을 지정한다.

 

#pragma data_seg(".hkshared")

HHOOK   gHook   = NULL;        // global hook

HWND    gHwnd   = NULL;        // for subclassing (dll injection window)

UINT    gMsg    = 0;           // hook msg

#pragma data_seg()

#pragma comment(linker, "/SECTION:.hkshared,RWS") // for linker option

 

- B) DLL Code 선언부

: Hook 을 설치, 제거하는 함수와 실제 hook 으로 데이터를 얻었을 때 이를 처리하는 함수를 구현한다.

// install Hook Function

HOOKDLL_API int fnInjectDll(HWND hWnd);

// remove Hook Function

HOOKDLL_API int fnRemoveDll();

 

// Hook Procedure

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

);

 

- C) DLL Main Code

: Dll implicit, explicit 와 상관 없이 항상 호출되는 start code 이다. DLL_PROCESS_ATTACH 일 경우에 Hook procedure 에서 사용할 Message 를 등록하는 코드를 작성한다.

 

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                       )

{

        if( ul_reason_for_call == DLL_PROCESS_ATTACH )

        {

               hDll = (HINSTANCE) hModule;   // dll module

               ::DisableThreadLibraryCalls( hDll );

 

               if(gMsg == NULL)

                       gMsg = ::RegisterWindowMessage( "WM_HOOKDLLMSG" );                 

    }

       

    return TRUE;

}

 

- C) Hook Install Code

 

HOOKDLL_API int InjectDll (HWND hWnd)

{

     gHwnd = hWnd;  // get window to hook

     gHook = SetWindowsHookEx (

                  WH_KEYBOARD,   // hook (message) type; others->WH_MOUSE, WH_CBT…

                  HookProc,      // hook procedure

                  hInstance,     // dll instance handle

                  GetWindowThreadProcessId (hWnd, NULL)

);

    

}

 

- D) Hook Uninstall Code

 

HOOKDLL_API void RemoveHook (HWND hWnd)

{

    

     UnHookWindowsHookEx(gHook);

    

}

 

- E) Hook Procedure Code

: Hook Procedure 로서, 기본 Procedure Message 를 처리하기 전에 먼저 호출되는 부분이다. 이곳에서 Hook 메시지를 찾아내어 기존 호출 방법을 변경한다.

 

#define pCW ((CWPSTRUCT*)lParam)

HOOKDLL_API LRESULT CALLBACK HookProc (

int nCode,

     WPARAM wParam,

     LPARAM lParam

)

{

If ((pCW->message == WM_HOOKEX) && pCW->lParam) 

        {

              

               // change dll owner (process)’s procedure

               OldProc = (WNDPROC) SetWindowLong(

gHwnd, GWL_WNDPROC, (LONG)NewProc

);

              

        }

return (CallNextHookEx(gHook,nCode,wParam,lParam));

}

 

 

 

 

- F) New Procedure Code

: 기존 Procedure 를 실행하기에 앞서 실행되는 부분이다. 이 부분은 DLL Injection 되는 영역 상에서 동작한다.  

 

LRESULT CALLBACK NewProc(

HWND hwnd,     

UINT uMsg,

WPARAM wParam, 

LPARAM lParam  

)

{

       

        Switch (uMsg)

{

Case WM_LBUTTONDOWN:

        MessageBox(NULL, “[Injected Code] Left Button”, “MSG”, MB_OK”);

        break;

        }

       

        // call original window procedure

        return CallWindowProc(OldProc,hwnd,uMsg,wParam,lParam);

}

 

 

4. Method3: VirtualAllocEx & CreateRemoteThread

- VirtualAllocEx, CreateRemoteThread

 

 

LPVOID VirtualAllocEx(

  HANDLE hProcess,  // process within which to allocate memory

  LPVOID lpAddress, // desired starting address of allocation

  DWORD dwSize,      // size, in bytes, of region to allocate

  DWORD flAllocationType, // type of allocation

  DWORD flProtect   // type of access protection

);

 

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

 

이미 존재하는 프로세스 상에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 앞서 Method1 에서 사용한 DLL 코드를 새로 생성한 thread 에서 load 하기 위해 LoadLibrary 를 실행한 뒤 필요한 작업을 수행하면 된다.

 

 

사용자 삽입 이미지



- A) Open Process (in App)

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

 

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

if (GetCurrentProcessId() == dwProcessId)

               return;

        // open remote process

        HANDLE hProcess = OpenProcess(         PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

);            

// inject dll

// reject dll

}

 

- B) Dll Injection Code (in App)

: remote thread 를 생성하고 LoadLibrary 를 호출한 뒤, thread 에서 dll 코드가 종료될 때까지 기다린다. Dll 코드는 remote thread, 즉 외부 Process 영역에서 동작하며, 필요한 작업을 한 뒤 return 되는데, return 되고 난 뒤에는 만들어 놓은 thread 를 종료하면 된다.

 

int InjectDll( HANDLE hProcess )

{

       

        // 1: get kernel address, this will be used to launch LoadLibrary

HMODULE hKernel32 = GetModuleHandle("Kernel32");

       

        // 2: get full path of dll

        Char szDllPath[MAX_PATH] = {255, 0};

        if(!GetModuleFileName(hInst, szDllPath, MAX_PATH))

               return 0;

        strcpy(strstr(szDllPath,".EXE"), ".dll");

       

        // 3: allocate dll length in the remote thread

        pLibRemote = VirtualAlloc(hProcess,

NULL, sizeof(szDllPath),

MEM_COMMIT, PAGE_READWRITE

);

        // 4: write dll code to the remote thread

        WriteProcessMemory(hProcess, pLibRemote, (void*)szDllPath,

               sizeof(szDllPath), NULL

               );

 

        // 5: call DLL code in the remote thread (LoadLibrary)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “LoadLibraryA”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 6: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- C) Dll Rejection Code (in App)

: remote thread 를 생성하고 FreeLibrary 를 실행하여 DLL unload 한다.

 

void RejectDll (HANDLE hProcess)

{

       

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, “FreeLibrary”)

        , pLibRemote, 0, NULL

);

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// Close handle, and free dll

CloseHandle(hThread);

}

 

 

 

 

 

5. Method4: CreateRemoteThread & WriteProcessMemory

- CreateRemoteThread, WriteProcessMemory

 

HANDLE CreateRemoteThread(

  HANDLE hProcess,        // handle to process to create thread in

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes

  DWORD dwStackSize,      // initial thread stack size, in bytes

  LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

  LPVOID lpParameter,     // argument for new thread

  DWORD dwCreationFlags,  // creation flags

  LPDWORD lpThreadId      // pointer to returned thread identifier

);

 

BOOL WriteProcessMemory(

  HANDLE hProcess,             // handle to process whose memory is written to

  LPVOID lpBaseAddress,        // address to start writing to

  LPVOID lpBuffer,             // pointer to buffer to write data to

  DWORD nSize,                 // number of bytes to write

  LPDWORD lpNumberOfBytesWritten   // actual number of bytes written

);

 

사용자 삽입 이미지



프로세스의 address 영역에 thread 를 외부에서 생성하여, 이 쓰레드가 DLL 코드를 실행하도록 동작하는 방법이다. 이 방법은 Method3 에서 사용한 방법과 비슷한 구조를 가지지만, 앞서의 경우와는 달리 DLL 을 외부 프로세스가 Load 하도록 동작하는 것이 아니라, DLL 에서 사용한 코드 자체를 외부 프로세스의 address 영역에다 Write 하고 이를 호출하여 코드를 실행하는 방법이다.

 

 - A) WinMain

: OpenProcess 함수를 사용하여 기존 프로세스를 open 한다.(정확한 의미로는 기존 Process handle 을 가져온다)

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow

)

{

       

        // get remote process’s id

GetWindowThreadProcessId (hWnd, &dwProcessId);

 

// inject dll

bRet = InjectCode(hWnd);

MessageBox(NULL, “Code Injected. Press OK to reject!”,

                “Injection Sample”, MB_OK

               );

// reject dll

bRet = RejectCode(hWnd);

}

 

 

- B) INJDATA

: 실제 Injection 을 할 때 사용할 구조체이다. 이 구조체를 remot 프로세스의 address 영역에 write 하여 사용할 것이고, 여기에 저장된 내용은 Dispatch , 사용할 프로시저나 함수의 포인터, 내부 변수등을 저장한다.

 

 

typedef struct __INJDATA {

        // pointer to the function used within injected code

        SETWINDOWLONG  fnSetWindowLong;

        CALLWINDOWPROC fnCallWindowProc;     

        //

        HWND hwnd;

        WNDPROC fnNewProc;

        WNDPROC fnOldProc;    

} INJDATA, *PINJDATA;

 

- C) Remote Code (Inject, Reject)

: Remote thread 영역에서 사용할 코드

DWORD WINAPI InjectFunc (INJDATA *pData)

{

        // change old procedure to the new one

        pData->fnOldProc = (WNDPROC) pData->fnSetWindowLong(

               pData->hwnd, GWL_WNDPROC, (long)pData->fnNewProc

               );     

        return (pData->fnOldProc != NULL);

}

 

DWORD WINAPI EjectFunc (INJDATA *pData)

{

        // restore to the old procedure

        return (pData->fnSetWindowLong(pData->hwnd, GWL_WNDPROC,

               (long)pData->fnOldProc) != NULL);

}

 

- D) InjectCode

: 실제 Injection 에 사용될 코드

 

int InjectCode(HWND hWnd)

{

       

        // 1: get user module address, this will be used with dispatch function

HMODULE hKernel32 = GetModuleHandle("user32");

       

        // 2: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

        // 3: allocate space for remote procedure(function) and data

        pDataRemote = (BYTE*) VirtualAllocEx(hProcess, 0, size,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE

               );

        pNewProcRemote = pDataRemote + sizeof(INJDATA);

       

        // 4: write pNewProcRemote to the process memory (address space)

        WriteProcessMemory(hProcess, pNewProcRemote,

               &NewProc, cbNewProc, &dwNumBytesXferred

               );     

 

        // 5: initialize INJDATA

        injData.fnSetWindowLong = (SETWINDOWLONG)

               GetProcAddress(hUser32, “SetWindowLongA”);

        injData.fnCallWndProc   =  (CALLWNDPROC)

               GetProcAddress(hUser32, “CallWndProcA”);

        injData.fnNewProc = (WNDPROC) (pNewProcRemote);

 

        // 6: write injData to the process memory           

        WriteProcessMemory(hProcess, pDataRemote,

               &DataLocal, sizeof(INJDATA), &dwNumBytesXferred

               );

       

        // 7: allocate space for remote function (InjectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbInjectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 8: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &InjectFunc, cbInjectFunc, &dwNumBytesXferred

               );

       

        //  9: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 10: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

 

- D) RejectCode

: 실제 Injection 에 사용될 코드

 

int RejectCode(HWND hWnd)

{

       

        // 1: open remote process

        hProcess = OpenProcess(               PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|

PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, dwProcessId

                );

       

        // 2: allocate space for remote function (RejectFunc)

        pCodeRemote = (PDWORD) VirtualAllocEx( hProcess, 0, cbRejectFunc,

               MEM_COMMIT, PAGE_EXECUTE_READWRITE
               );

       

        // 3: write function to the remote thread   

        WriteProcessMemory( hProcess, pCodeRemote,

               &RejectFunc, cbRejectFunc, &dwNumBytesXferred

               );

       

        //  4: call function in the remote thread (pCodeRemote)

        hThread = CreateRemoteThread(hProcess, NULL, 0,

        (LPTHREAD_START_ROUTINE) pCodeRemote, pDataRemote,

                0 , &dwThreadId);

 

WaitForSingleObject(hThread, INFINITE); // wait! while DLL code execute

       

// 5: get loaded module

GetExitCodeThread(hThread, &hLibModule);

 

// Close handle, and free dll

CloseHandle(hThread);

VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);

       

}

Posted by skensita