Global API Hooking 예제 코드를 분석하면서 해당 기술에 대한 이해를 돕습니다.



본 내용을 읽기 전에 이전 포스트를 참고하세요. 같이 이어지는 내용입니다.



* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows 7 32bit & IE 8 에서 테스트 되었습니다.

전체 소스 코드는 아래 첨부된 파일을 참고하시기 바랍니다.

주요 함수에 대해 설명 드리겠습니다. 설명의 편의를 위해서 에러 처리 관련 코드는 제거하였습니다. 에러 처리 코드가 포함된 원본 함수의 전체 코드는 첨부된 파일에서 확인하시기 바랍니다.



DllMain()


먼저 DllMain() 함수를 살펴보겠습니다.

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char            szCurProc[MAX_PATH] = {0,};
    char            *p = NULL;

    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            GetModuleFileName(NULL, szCurProc, MAX_PATH);
            p = strrchr(szCurProc, '\\');
            if( (p != NULL) && !_stricmp(p+1, "iexplore.exe") )
            {
                // wininet!InternetConnectW() API 를 후킹 하기 전에
                //   미리 wininet.dll 을 로딩 시킴
                LoadLibrary("wininet.dll");
            }

            // hook
            hook_by_code("ntdll.dll", "ZwResumeThread", 
                         (PROC)NewZwResumeThread, g_pZWRT);
            hook_by_code("wininet.dll", "InternetConnectW", 
                         (PROC)NewInternetConnectW, g_pICW);
            break;

        case DLL_PROCESS_DETACH :
            // unhook
            unhook_by_code("ntdll.dll", "ZwResumeThread", 
                           g_pZWRT);
            unhook_by_code("wininet.dll", "InternetConnectW", 
                           g_pICW);
            break;
    }

    return TRUE;
}

DllMain() 함수의 핵심 기능은 ntdll!ZwResumeThread() 와 wininet!InternetConnectW() API 의 hook/unhook 입니다. 

한가지 특이한 코드는 실행 프로세스 이름이 iexplorer.exe 인 경우 wininet.dll 을 로딩 시키는 코드입니다. iexplore.exe 프로세스가 정상적으로 실행되면 기본적으로 wininet.dll 을 로딩하고 있는데 왜 굳이 추가적으로 로딩을 시키는 걸까요? 그 이유는 Global API Hooking 때문입니다. 

ntdll!ZwResumeThread() 를 이용한 API 후킹은 해당 프로세스의 메인 스레드가 시작되기 전에 제
어를 가로채기 때문에 우리가 후킹하려는 wininet.dll 모듈이 아직 로딩되어 있지 않는 상황이 발생할 수 있습니다. 이를 방지 하기 위해서 iexplore.exe 프로세스인 경우 wininet!InternetConnectW() API 를 후킹하기 전에 무조건 wininet.dll 을 로딩시키는 것입니다. 



NewInternetConnectW()


wininet!InternetConnectW() 의 후킹 함수인 NewInternetConnectW() 함수에 대한 설명입니다. 이 함수는 IE 의 접속 주소를 모니터링 하면서 특정 사이트에 접속을 시도할 때 원하는 사이트로 접속을 돌리는 역할을 수행합니다.

HINTERNET WINAPI NewInternetConnectW
(
    HINTERNET hInternet,
    LPCWSTR lpszServerName,
    INTERNET_PORT nServerPort,
    LPCTSTR lpszUsername,
    LPCTSTR lpszPassword,
    DWORD dwService,
    DWORD dwFlags,
    DWORD_PTR dwContext
)
{
    HINTERNET hInt = NULL;
    FARPROC pFunc = NULL;
    HMODULE hMod = NULL;

    // unhook
    unhook_by_code("wininet.dll", "InternetConnectW", g_pICW);

    // call original API
    hMod = GetModuleHandle("wininet.dll");
    pFunc = GetProcAddress(hMod, "InternetConnectW");

    if( !_wcsicmp(lpszServerName, L"www.naver.com") ||
        !_wcsicmp(lpszServerName, L"www.daum.net") ||
        !_wcsicmp(lpszServerName, L"www.nate.com") || 
        !_wcsicmp(lpszServerName, L"www.yahoo.com") )
    {
        hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
                                           L"www.reversecore.com",
                                           nServerPort,
                                           lpszUsername,
                                           lpszPassword,
                                           dwService,
                                           dwFlags,
                                           dwContext);
    }
    else
    {
        hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
                                           lpszServerName,
                                           nServerPort,
                                           lpszUsername,
                                           lpszPassword,
                                           dwService,
                                           dwFlags,
                                           dwContext);
    }

    // hook
    !hook_by_code("wininet.dll", "InternetConnectW"
                    (PROC)NewInternetConnectW, g_pICW;

    return hInt;
}

위 함수의 코드는 매우 단순합니다. 함수의 2 번째 파라미터인 lpszServerName 문자열이 바로 접속 주소입니다. 이 접속주소를 모니터링 하여 우리나라 4 대 포탈 사이트(Naver, Daum, Nate, Yahoo)인 경우, 제 블로그(ReverseCore)로 연결을 바꿔버립니다.

hook_by_code() / unhook_by_code() 에 대한 설명은 아래 포스트를 참고하시기 바랍니다.




NewZwResumeThread()


ntdll!ZwResumeThread() 의 후킹 함수인 NewZwResumeThread() 함수입니다.

NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount)
{
    NTSTATUS status, statusThread;
    FARPROC pFunc = NULL, pFuncThread = NULL;
    DWORD dwPID = 0;
    static DWORD dwPrevPID = 0;
    THREAD_BASIC_INFORMATION tbi;
    HMODULE hMod = NULL;
    char szModPath[MAX_PATH] = {0,};

    // call ntdll!ZwQueryInformationThread()
    hMod = GetModuleHandle("ntdll.dll");
    pFuncThread = GetProcAddress(hMod, "ZwQueryInformationThread");
    statusThread = ((PFZWQUERYINFORMATIONTHREAD)pFuncThread)
                   (ThreadHandle, 0, &tbi, sizeof(tbi), NULL);

    // Dll Injection to the new child process
    dwPID = (DWORD)tbi.ClientId.UniqueProcess;
    if ( (dwPID != GetCurrentProcessId()) && (dwPID != dwPrevPID) )
    {
        dwPrevPID = dwPID;

        // change privilege
        SetPrivilege(SE_DEBUG_NAME, TRUE);

        // get injection dll path
        GetModuleFileName(GetModuleHandle(STR_MODULE_NAME), 
                            szModPath, 
                            MAX_PATH);

        // call InjectDll()
        InjectDll(dwPID, szModPath);
    }

    // unhook
    unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT);

    // call ntdll!ZwResumeThread()
    pFunc = GetProcAddress(hMod, "ZwResumeThread");
    status = ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount);

    // hook
    hook_by_code("ntdll.dll", "ZwResumeThread"
                   (PROC)NewZwResumeThread, g_pZWRT);

    return status;
}

NewResumeThread() 함수의 첫 번째 파라미터는 resume 시킬 스레드의 ThreadHandle 입니다. 지난번 설명에서 이 스레드는 바로 자식 프로세스의 메인 스레드라고 설명 드렸습니다.


따라서 NewResumeThread() 함수 초반부의 ZwQueryInformationThread() API 를 호출하는 이유는 바로 ThreadHandle 이 가리키는 스레드(자식 프로세스의 스레드) 가 소속된 자식 프로세스의 PID 를 얻기 위함입니다.

이렇게 ThreadHandle 파라미터를 이용하여 (지금 막 생성된) 자식 프로세스의 PID 를 얻어 내었습니다. 이 PID 를 이용하여 redirect.dll (후킹 DLL) 을 인젝션 시켜 줍니다. 해당 자식 프로세스는 메인 스레드가 실행되기도 전에 이미 redirect.dll 이 인젝션 되면서 자동으로 API 후킹이 걸리게 됩니다.

마지막으로 ntdll!ZwResumeThread() API 를 정상적으로 호출하여 자식 프로세스의 메인 스레드를 resume 시킵니다. 이제 자식 프로세스는 API 가 후킹된 채로 정상 실행됩니다. 



High-Level API Hooking vs Low-Level API Hooking


위의 ntdll!ZwResumeThread() API 후킹 방법은 단순히 kernel32!CreateProcess() API 를 후킹하는 것보다 더 강력하고 편리한 방법입니다. 왜냐하면 CreateProcess() 는 내부적으로 CreateProcessInternal() 을 호출합니다. 만약 프로그램에서 CreateProcessInternal() 을 직접 호출한다면 정상적인 후킹이 되지 않습니다. (차라리 CreateProcessInternal() 을 후킹하는 것이 더 좋은 방법입니다. – 좋은 방법을 가르쳐 주신 iwillhackyou 님께 감사드립니다.)

이런 식으로 Low-Level API (ntdll.dll 에서 제공되는 API) 를 후킹할수록 더 강력합니다. 하지만 대부분의 Low-Level API 들은 undocumented 되어 있으며, OS 버전에 따라 변경될 가능성이 존재합니다. 반면에 High-Level API (kernel32.dll 레벨 - documented) 들은 변경될 가능성이 없고 잘 문서화 되어 있기 때문에 안정적인 후킹이 가능합니다. 대신에 후킹 성능이 좀 떨어집니다.

따라서 High-Level API 후킹과 Low-Level API 후킹은 서로 일장 일단이 있기 때문에 상황에 맞게 적절히 선택하여 구현하시는 것이 현명한 방법입니다.


+---+

다음 강좌는 Code Injection 을 통한 API 후킹에 대한 내용입니다. 지금까지는 DLL 을 인젝션 시켜서 API 후킹을 진행하였지만, 짤막한 코드를 삽입시켜 동일한 기능을 구현하는 것입니다. DLL Injection 방법과 많은 차이점이 있으며, 이 또한 흥미로운 주제가 될 것입니다.


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

Trackback Address :: http://www.reversecore.com/trackback/80 관련글 쓰기

  1. Ezbeat 2010/05/17 11:07 댓글주소 | 수정 | 삭제 | 댓글

    어느정도 dll injection과 api hooking은 개념이 제대로 잡혀서 보는데 별 무리는 없네요.
    Code Injection!! 기대하고있습니다 ^^

    한가지 질문드려봐도 될련지.. ;;ㅠㅠ
    대부분 dll injection을 하는 바이러스들을 보면 exe파일 하나에서 내부적으로 dll을 생성합니다.
    CreateFile을하고.. WriteFile을 하구요. 그런데 저도 한번 구현해 볼까 하고 쭉 만들던 중
    dll의 헥사 값을 넣어야되는데 문자열 크기가 너무 크다고 안들어가더라구요.
    "\x11\x22\x33~~" 이런식으로 넣었거든요.ㅠㅠ

    큰 dll파일의 모든 헥사값을 exe파일에 넣을려면.. 어떠한 방법이 있을까요??

    • reversecore 2010/05/17 22:31 댓글주소 | 수정 | 삭제

      Ezbeat님, 안녕하세요.

      악성코드 중에 내부에 또다른 PE 파일을 가지고 있다가 생성시키는 경우가 있습니다. AV 에서는 Dropper 라고 하는데요...

      이를 만들어 보신다는 얘기신지요???

      제가 방법을 알고는 있는데요, 제가 직접 그런 설명을 하기는 어려운 처지라는 것을 잘 이해해 주시리라 생각합니다. ^^

      감사합니다.

  2. iwillhackyou 2010/05/17 16:54 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    좋은 글 잘 보았습니다.

    그런데 코드에 한 가지 문제가 있어 보입니다. ^^

    Dll 엔트리 함수인 DllMain() 내부에서 DLL_PROCESS_ATTACH
    분기문 내부에 LoadLibrary를 사용하셨는데요...
    진입 함수 내부에서 다시 LoadLibrary를 호출하면 DeadLock에 걸릴 수 있습니다. 만약 위 코드가 정상적으로 수행되었다면 wininet이 이미 로드되어 있었기 때문인 것으로 추측됩니다.

    대부분 엔트리 함수에서는 동기화 오브젝트도 사용안하고 가능한 최소의 코드만 작성해야 합니다.
    저 같은 경우에는 위와 같이 별도의 dll을 로드해야 할 경우에는 대부분 스레드를 생성(dllmain 엔트리 함수가 종료된 뒤 실행되도록)해서 해당 로직을 진입함수 이후에 처리되도록 합니다.^^

    • iwillhackyou 2010/05/17 16:51 댓글주소 | 수정 | 삭제

      DLL Loader Lock에 관해서 잘 설명된 문서의 링크입니다.

      http://download.microsoft.com/download/a/f/7/af7777e5-7dcd-4800-8a0a-b18336565f5b/DLL_bestprac.doc

      엔트리함수에서 주의해야할 부분들이 상세히 나와있습니다.

    • reversecore 2010/05/22 20:55 댓글주소 | 수정 | 삭제

      iwillhackyou님, 안녕하세요.

      훌륭한 조언에 감사드립니다. ^^
      소개해 주신 문서도 잘 읽어봤습니다.

      과연 데드락 가능성이 있을 수 있겠네요. 거기까지는 미처 생각하지 못했습니다...

      사실 대부분의 악성코드는 DllMain() 에서 하고 싶은 짓을 다합니다. ^^ 물론 잘 작동하지요. 저도 그렇다는 것을 보여드리고 싶었던 것이구요.

      하지만 세심한 개발자는 언제나 만에 하나의 가능성도 고려하는 법이므로... 알려주신 대로 별도 스레드로 처리하는 것이 더욱 좋은 방법이 되겠습니다.

      제가 블로그를 운영하면서 좋은 점 중에 하나가 바로 이처럼 다른 훌륭한 분들의 조언을 듣고 미처 생각 못했던 내용들에 대해서 배우는 것입니다.

      감사합니다. ^^~

  3. xss 2010/06/28 18:24 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 올려주셔서 감사합니다. 그런데 윈도우 XP에서 실행해보니 인젝션된 프로세스에서 프로그램을 실행시키면 (예를들어 explorer.exe에 인젝트 한다음에 시작메뉴의 실행에서 notepad.exe를 실행시킴) 프로그램이 아예 실행이 되지 않네요. 참고로 비스타와 윈도7에선 아주 정상적으로 작동합니다. 참고로 CreateRemoteThread 이후 WaitForSingleObject를 주석처리하면 프로그램은 정상적으로 실행되나 새로 실행된 프로그램에 DLL이 인젝션 되지 않네요.

    • reversecore 2010/06/29 22:59 댓글주소 | 수정 | 삭제

      네, 위 예제 코드는 Windows 7 에서 정상적을 실행되도록 되어있습니다. XP 에서 잘 돌기 위해서는 코드를 좀 수정해야 합니다. 위 내용에는 그런 디테일한 설명을 생략하였네요.

      제 기억이 맞다면 NewZwResumeThread() 함수 내부에서 InjectDll() 호출이 ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount) 호출 이후로 와야 할겁니다.

      이렇게 되면 XP 에서는 상대방 프로세스의 메인 스레드가 실행된 직후 인젝션이 되는 것입니다. 위의 Win7 예제에서는 상대방 프로세스의 메인 스레드 실행 전에 인젝션 하는 것과 차이가 있지요.

      테스트 해보시고 잘 안되시면 다시 질문 올려주세요~ 제가 해드릴께요~

      감사합니다.

  4. Sale 2010/07/06 23:38 댓글주소 | 수정 | 삭제 | 댓글

    When redirect.cpp is compiled in VC++ 6.0 it won't inject using dllinj.exe.. It says FAILURE!!! every time.. While your compiled redirect.dll works just fine.. What could be the reason???

  5. xss 2010/07/07 00:08 댓글주소 | 수정 | 삭제 | 댓글

    답변 감사합니다 말씀하신대로 해보았으나 이상한 현상이 발생하였습니다. explorer.exe에만 inject시키면 (injdll explorer.exe -i dll.dll) 모든것이 정상적으로 작동하나 모든 프로세스에 inject시키면(injdll * -i dll.dll) 괴현상이 일어납니다. 바탕화면에 있는 프로그램을 클릭하여 실행시키면 대부분의 프로그램은 실행이 되지 않거나 어떤 프로그램은 실행은 되나 화면의 일부분이 랜더링되지 않습니다. 그런데 신기한것은 그 문제가 있는 프로그램을 다시 실행시키면 정상적으로 작동됩니다. 바탕화면에서 더블클릭하여 실행되는 프로그램은 모두 explorer.exe의 child process로 생성되는데 어떻게 이러한 일이 일어날수 있는지 정말 신기하네요.

    • xss 2010/07/07 04:02 댓글주소 | 수정 | 삭제

      문제점을 찾아보았더니 services.exe와 svchost.exe 같은 시스템 프로세스에 인젝션이 되면 위와같은 현상이 일어나네요. 신기한점은 비스타에서는 문제가 안되는데 XP에서만 문제가 됩니다.

    • reversecore 2010/07/10 14:28 댓글주소 | 수정 | 삭제

      global API Hooking 을 하실때는 매우 주의하셔야 합니다.
      DLL 에 문제가 있다면 시스템 전체가 먹통이 되지요.

      만약 아직도 문제가 해결되지 않았다면, dll.dll 파일을 저에게 보내주세요~ 제가 한번 봐드리겠습니다.

      감사합니다.

  6. Sale 2010/07/07 00:43 댓글주소 | 수정 | 삭제 | 댓글

    I did compile in Release configuration.. But every time it says failure!!!
    Here is the complete project file for VC6.0

    http://www.sendspace.com/file/jk2xwn

  7. 으덩 2010/07/15 12:23 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요~ 여기서 많이 배워가는 꼬꼬마 학생입니다^^;
    간단한 질문을 하고 싶어서요 ㅠ_ㅠ
    예제에서, NewInternetConnectW() 에서 4개의 사이트에만 접근을 못하게 하셨잖아요~
    근데 모~~~든 사이트에 못가게 하려면 어떻게 적어야 하나요?ㅠ_ㅠ

    교수님께서 사용하실 프로그램을 만들려고 하거든요~~
    컴퓨터로 시험볼때 학생들이 자기 홈페이지 말고는 다른곳으로 접근을 할 수 없게 하는 기능을
    넣고 싶어용..
    알려주세요 ㅠ0ㅠ))~~~

    • reversecore 2010/07/15 23:40 댓글주소 | 수정 | 삭제

      안녕하세요~

      그냥 리턴해버리시면 됩니다. ^^

      즉, InternetConnectW() 호출을 무시해버리는 거지요.

      근데 실전에서 사용하기는 좀 빈약한 프로그램인데요... 파폭, 크롬만 써도 접속 가능할텐데 말입니다. ^^

      사용하시기 전에 충분한 테스트가 필요할 것입니다.

      감사합니다.

    • 으덩 2010/07/16 16:17 댓글주소 | 수정 | 삭제

      아!! 그렇군요 !!!
      그런점은 생각도 못했네요 ^^aa
      덕분에 좀더 생각이 넓어지는것 같아용 ~~^^
      고맙습니다!!!

  8. somma 2010/07/29 17:50 댓글주소 | 수정 | 삭제 | 댓글

    구글 검색하다가 우연히 들어왔습니다.
    좋은글들 많이 읽었습니다.
    가볍지 않은 내용들인데도.. 참 쉽게 글을 잘쓰시는것 같아요.
    좋은글들 잘 보고 갑니다. :-)

    • reversecore 2010/08/01 22:45 댓글주소 | 수정 | 삭제

      안녕하세요. ^^

      somma 님 블로그는 저도 자주 들르면서 정보를 많이 참고하고 있습니다.

      방문 감사드립니다.

  9. shed00 2010/08/13 10:55 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 좋은 내용 올려주셔서 열심히 공부하고 있습니다 감사합니다 ^^

    다름이 아니라.. 위에 다른분께서도 질문하신 내용인데요..

    XP 에서 테스트중인데 위에서 말씀하신데로 이리저리 해봐도

    도저히 해결을 할수가 없네요 T^T

    XP 에서 explorer.exe 에 인젝션 시키고

    인터넷익스플로러를 실행시키면 redirect.dll 은 인젝션이 되어 있고

    주소창이나 메뉴, 탭 같은 부분은 렌더링이 되질 않구요.

    explorer 의 자식프로세스로 실행되는 다른프로그램들은

    실행이 되지 않는게 많습니다..

    몇몇 프로그램들은 실행이 되긴 하는데 redirect.dll 이 인젝션이 안되어 있구요..

    염치없지만 XP 버전좀 부탁드려도 될까요?

    좋은하루 되세요~

    • reversecore 2010/08/15 22:37 댓글주소 | 수정 | 삭제

      API 후킹은 (특히 Global API Hooking) OS, 사용자 환경을 많이 탑니다. 많은 테스트가 필요하지요.

      지금 쓰고 있는 책의 예제들은 XP 와 7 에서 잘 동작할 수 있도록 테스트 중입니다.

      그게 완성되면 이 블로그에도 올리겠습니다.

      감사합니다.

  10. J 2010/11/15 18:52 댓글주소 | 수정 | 삭제 | 댓글

    VC 2008 에서 빌드시
    error C2664: 'wvsprintfw' : 매개 변수 1을 'char [512]'에서 'LPWSTR'로 변환할 수 없습니다.
    변환하려면 reinterpret_cat, C 스타일 캐스트 또는 함수 스타일 캐스트가 필요합니다.
    라고 에러창이 뜹니다. 20개 가량 뜨는군요. ㅠㅠ;; 어떻게 해야하는지...
    검색해보니 char 를 wchar_t 로 변경해주라는데 고쳐봐도 안되는군요... 쩝.. ㅠㅠ;;;

    VC++ 2006을 깔고 해보니
    타입오류는 안나는데요
    syntax error 가 납니다
    indentifier 'DWORD_PTR' 뭔가 매우 초보적인 문제인듯한데
    모르겠습니다. ^^ㅎ

    • reversecore 2010/11/18 15:58 댓글주소 | 수정 | 삭제

      안녕하세요.

      프로젝트 설정 문제인데요...

      제가 Multibyte 로 세팅해서 예제 코드를 작성하다보니 많은 분들께서 빌드시 어려움을 호소하고 계십니다. (기본은 Unicode)

      마침 제가 책을 작업하면서 예제를 Unicode 로 전부 바꿔 버렸습니다. 이메일 주소를 남겨주시면 위 예제 프로젝트를 보내드리도록 하게습니다.

      감사합니다.

  11. 2011/01/30 15:26 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다


◀ PREV : [1] : ... [12] : [13] : [14] : [15] : [16] : [17] : [18] : [19] : [20] : ... [91] : NEXT ▶