전역 후킹 관련 document. api 함수 후킹

Win32 Global API Hook - 1 Win32 API 후킹의 기본


자신이 만약 어느정도의 레벨을 가진 윈도우즈 프로그래머라면 이런 생각을 한번쯤 해보았을 것이다.


"만약 Windows API를 후킹할 수 있다면 재미있는 것을 많이 해볼 수 있을텐데..."


그리고 의욕과 시간이 있었다면 아마 도전해본 사람도 꽤 있었을것이다. 그러나 실제로 이것을 성공한 사람은 그리 많지 않았을것으로안다. 여러분이 만약 어플리케이션 레벨에서만 프로그래밍했다면 이것은 불가능해보였을지도 모른다. 시스템 레벨 프로그래머라면 이것이제법 까다롭고 다루기 힘든 주제라는것을 알았을 것이다. 자 여기서 필자는 많은 사람들이 궁금해하는 이 비밀스런 작업을 하나하나풀어가려고 한다. 그리고 의외로 간단한 곳에 해답이 있었음을 이 강좌가 끝날때쯤 알게될 것이다.


자, 서론을 접고 본론으로 들어가자. 우리가 하려는 일은 다음과 같다.


Win32 API를 후킹해서 내가 원하는 작업을 수행하거나, 작업의 흐름을 원하는대로 제어할 수 있다.


단지 이것이다. 무슨 설명이 더 필요한가? 실제로 우리가 하려는 것은 이것이 전부이다. 예를 들어서 쉽게 말하자면CreateProcess() 라는 API를 후킹하면 내가 원하지않는 프로그램의 실행을 막을수도 있고, 윈속함수인 send()나recv()를 후킹하면 나가고 들어오는 패킷을 훔쳐보거나 조작할 수 있다. 느낌이 팍 오지 않는가? 느낌이 오지않는 사람은아마도 아직 이 강좌를 들을만한 수준이 아니거나 해커(순수한 의미의)의 기질이 없는 사람일 수도 있다.


일단 오늘은 첫날이니 그동안 많은 사람들이 제시했던 Windows API 후킹방법에 대해서 먼저 얘기해보자.


1. exe 파일헤더의 import descriptor table을 변경하는 방법


가장 쉬운 방법이 되겠다. 물론 쉬운만큼 문제점이 적지 않다. 일단 Win32 응용프로그램은 PE(PortableExecutable)이라는 형식으로 바이너리화 되어있다. 실행가능한 바이너리 파일구조에는 POSIX, COFF, PE 형식등등이있는데, 윈도는 PE형식을 사용한다. 따라서 PE형식을 알면 윈도실행파일구조를 분석할 수 있겠다. 실제 윈도실행파일은 크게헤더, 리소스, 임포트테이블, 익스포트테이블, 데이터, 코드 등등의 영역으로 나누어지는데 여기서 임포트테이블이 이름 그대로임포트된 라이브러리의 함수에 관한 정보가 담겨졌있다. 따라서 정적으로 링크된 모든 함수는 이곳에서 볼수 있게된다. 그러면이부분의 임포트된 함수의 주소들을 내가 만든 후킹함수주소로 바꿔치기해주면 간단할 것이다. 아래에 리스트된 코드를 보자.


// 포인터 변환 및 연산 매크로

#ifndef MakePtr

#define MakePtr(cast, ptr, addValue) (cast)((DWORD)(ptr)+(DWORD)(addValue))

#endif // MakePtr


PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor(HMODULE hMod,        // 모듈

                                        LPCTSTR pszModName)// 모듈이름

{

    TRACE("[FIND IMPORT DESCRIPTOR] \n");


    // 매개변수 유효성 검사

    ASSERT(!IsBadReadPtr(hMod, sizeof(IMAGE_DOS_HEADER)));

    ASSERT(NULL != pszModName);


    // DOS 헤더

    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;

    if(IMAGE_DOS_SIGNATURE/*0x5A4D*/ == pDosHdr->e_magic)

    {

        // NT 헤더

        PIMAGE_NT_HEADERS pNtHdr = MakePtr(PIMAGE_NT_HEADERS,

            pDosHdr, pDosHdr->e_lfanew);

        if(!IsBadReadPtr(pNtHdr, sizeof(IMAGE_NT_HEADERS))

            && IMAGE_NT_SIGNATURE/*0x00004550*/ == pNtHdr->Signature)

        {

            // image descriptor

            PIMAGE_IMPORT_DESCRIPTOR pImpDesc =

                MakePtr(PIMAGE_IMPORT_DESCRIPTOR,

                    pDosHdr,

                    pNtHdr->OptionalHeader.

                    DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].

                    VirtualAddress);

            if(NULL != pImpDesc)

            {

                while(NULL != pImpDesc->Name)

                {

                    PSTR pszName = MakePtr(PSTR, pDosHdr, (DWORD)pImpDesc->Name);

                    TRACE(" %s ", pszName);


                    if(stricmp(pszName, pszModName) == 0)

                    {

                        // 찾았다 !!

                        TRACE(": Found It !! \n");


                        return pImpDesc;

                    }


                    TRACE("\n");

                    pImpDesc++;

                }

            }

        }

    }


    return NULL;

}


PROC WINAPI HookImportFunction(HMODULE hMod,// Hooking 할 모듈

                     PSTR pszModName,        // Hooking 함수가 위치한 모듈이름

                     PSTR pszFuncName,        // Hooking 할 함수

                     PROC pfnNewProc)        // Hooking 함수

{

    // 매개변수 유효성 검사

    ASSERT(!IsBadReadPtr(hMod, sizeof(IMAGE_DOS_HEADER)));

    ASSERT(NULL != pszModName);

    ASSERT(NULL != pszFuncName);

    ASSERT(NULL != pfnNewProc);

    ASSERT(!IsBadCodePtr(pfnNewProc));


    // 반환값 (원래 함수)

    PROC pfnOrgProc = NULL;


    // Win9x이고 2GB 이상의 시스템 DLL 이라면

    // 조용히 사라져야 한다.

    if(1 != __GetOsType() && 0x80000000 < (DWORD)hMod)

    {

        return NULL;

    }


    // import descriptor 를 얻는다.

    PIMAGE_IMPORT_DESCRIPTOR pImpDesc = GetImportDescriptor(hMod, pszModName);

    if(NULL == pImpDesc)

        return NULL;


    // 원본 thunk

    PIMAGE_THUNK_DATA pOrgThunk = MakePtr(PIMAGE_THUNK_DATA,

        hMod, pImpDesc->OriginalFirstThunk);


    // 실제 thunk (for Hooking)

    PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA,

        hMod, pImpDesc->FirstThunk);


    // 루프를 돌면서 Hooking 할 함수를 찾는다.

    TRACE("[FIND IMPORT FUNCTION] : %s \n", pszModName);

    while(NULL != pOrgThunk->u1.Function)

    {

        // 이름으로 import된 함수만 검색

        if(IMAGE_ORDINAL_FLAG !=

            (pOrgThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))

        {

            // import된 함수 이름

            PIMAGE_IMPORT_BY_NAME pByName =

                MakePtr(PIMAGE_IMPORT_BY_NAME,

                hMod, pOrgThunk->u1.AddressOfData);


            // 이름이 NULL로 시작되면 넘어간다.

            if(0 == pByName->Name[0])

                continue;


            TRACE(" %s ", (PSTR)pByName->Name);


            if(pszFuncName[0] == pByName->Name[0]

                && 0 == stricmp(pszFuncName, (PSTR)pByName->Name))

            {

                // 찾았다 !!

                TRACE(": Found It !! \n");


                MEMORY_BASIC_INFORMATION mbi;

                VirtualQuery(pRealThunk, &mbi, sizeof(mbi));


                // 가상 메모리의 보호속성 변경

                if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize,

                    PAGE_READWRITE, &mbi.Protect))

                {

                    // VirtualProtect() fail !!

                    ASSERT(0);

                    return NULL;

                }


                // 원래 함수(반환값) 저장

                pfnOrgProc = (PROC)pRealThunk->u1.Function;


                // 새로운 함수로 덮어쓴다.

                TRACE("** old function : 0x%08X \n", (DWORD)pRealThunk->u1.Function);

                pRealThunk->u1.Function =

                    (DWORD)pfnNewProc;

                TRACE("** new function : 0x%08X \n", (DWORD)pRealThunk->u1.Function);


                // 가상 메모리의 보호속성 원래대로 되돌림

                DWORD dwTmp;

                VERIFY(VirtualProtect(mbi.BaseAddress, mbi.RegionSize,

                    mbi.Protect, &dwTmp));


                // 세상을 다 가져라 !!

                TRACE("** Hook OK !! What a wonderful world !! \n");

                return pfnOrgProc;

            }


            TRACE("\n");

        }


        pOrgThunk++;

        pRealThunk++;

    }


    return NULL;

}


GetImportDescriptor()함수는 모듈의 임포트디스크립터를 얻어내는 함수이고, HookImportFunction()함수는 실제로 임포트된 함수를 후크하는함수이다. 이 두 함수를 이용해서 MessageBox API를 후킹해보자.


// MessageBox API 원형

typedef int (WINAPI *PROC_MESSAGEBOX)(HWND, PSTR, PSTR, UINT);


PROC_MESSAGEBOX pfnOrgMessageBox = NULL;


// MessageBox를 대체할 훅함수

int WINAPI MyMessageBoxA(HWND hWnd, PSTR pszText, PSTR pszTitle, UINT uType)

{

    ASSERT(NULL != pfnOrgMessageBox);


    return pfnOrgMessageBox(NULL, "Hooked MessageBox !!", "Hooked !!", MB_ICONINFORMATION);

}


// Main 프로세스

int APIENTRY WinMain(HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,

                     LPSTR     lpCmdLine,

                     int       nCmdShow)

{

     // TODO: Place code here.


    MessageBox(NULL, "Default MessageBox", "Windows 98", MB_ICONINFORMATION);


    // hook !!

    pfnOrgMessageBox = (PROC_MESSAGEBOX)HookImportFunction(

        GetModuleHandle(NULL), "user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);

    if(NULL == pfnOrgMessageBox)

        return 0;


    MessageBox(NULL, "Default MessageBox", "Windows 98", MB_ICONINFORMATION);


    // unhook !!

    pfnOrgMessageBox = (PROC_MESSAGEBOX)HookImportFunction(

        GetModuleHandle(NULL), "user32.dll", "MessageBoxA", (PROC)pfnOrgMessageBox);

    if(NULL == pfnOrgMessageBox)

        return 0;

    TRACE("replace orginal function \n");


    MessageBox(NULL, "Default MessageBox", "Windows 98", MB_ICONINFORMATION);


    return 0;

}


MyMessageBoxA라는 함수를 보면 타이틀에 Hooked !! 라는 캡션을 가진 Hooked MessageBox !! 메시지를 출력하는 메시지박스를 보여주는 함수이다. 후킹이 성공적으로 이루어지면 MessageBox는 무조건 위와같은 형태로 보여지게 될것이다.MessageBoxA의 A 문자는 ANSI 문자열 버전으로서 Windows 9x 계열에서 주로 사용되며 1바이트 문자를사용한다. 반대로 W가 붙은 API는 Wide Charactor로서 2바이트 문자를 사용하는 유니코드 사용 API이다. 실제로Win9x 계열에서는 생략할경우 A문자 버전의 API가 호출되도록 재지정되어 있으며, NT4/W2K 계열에서는 내부적으로는W문자 버전의 API가 호출된다. 문자열을 인자로 사용하는 대부분의 API가 이와같이 두가지 버전으로 나뉘어지며 만약 훅함수를설치한다면 정확한 버전을 사용해야 할것이다.


실행시켜보면 실제로 중간의 MessageBox(NULL, "Default MessageBox", "Windows 98",

MB_ICONINFORMATION); 함수는 우리가 지정한 후킹함수로 대체되어 Hooked ... 라는 메시지 박스가 출력될것이다.


자, 원리를 알고 실행이 제대로 되는것을 확인했다면 문제점에 대해서 알아보자.

위의 방법으로는 현재 내 프로그램만이 후킹될수 있다. 이유인즉슨 임포트 디스크립터 테이블이라는것이 프로그램마다 가지고 있는것이기때문에 당연히 내 프로그램만이 적용되어진다. 그렇다면 다른 프로그램들도 모조리 임포트 디스크립터 테이블을 변경하면 될것이아닌가? 라고 생각하는 사람이 있을지 모르겠지만, 그건 불가능하다. 왜냐하면 Win32 응용프로그램들은 각각 독립적인주소공간에서 실행되기때문에 기본적으로 서로 다른 프로그램의 영역을 침범할수 없다. 그러면 어떻게 다른 응용프로그램(정확히 말하면프로세스)의 주소공간을 볼수있을까? 이 문제에 관해서는 다음 강좌에서 다루어 보도록 하자.


끝으로...오늘은 Win32 API 후킹에 대한 맛보기였다. 사실 오늘 제시한 방법으로는 우리가 생각하는 것들을 하기에는 턱없이모자란다. 그렇지만 모든것은 순서가 있듯이 가장 기본적인 방법론부터 제시해보았다. 사실 오늘 강좌에도 따라오는 부수적인 내용은상당히 방대하다. 일단 Windows 실행파일구조인 PE구조에 대한 이해와 Windows 프로세스간 메모리 관리 또한 매우중요한 내용이다. 지면상(또는 시간상 ^^) 여기서 다 다룰수는 없지만 참고할만한 서적을 소개하면 PE구조에 관해서는 MSJ나마이크로소프트웨어 잡지를 검색하면 찾을 수 있을것이다. 실제로 PEDUMP 같은 유틸리티를 작성해본다면 더없이 좋을것이다.Win32 메모리관리에 관해서는 Jeffry Richter의 Advanced Windows 라는 책을 추천한다. 좀 오래된책인데 필자가 공부할때는 가장 훌륭했다고 여겨지는 책이다. 아마 다른 시스템 프로그래밍을 다루는 책에서도 자료를 얻을 수있을것이다. 또한 위 소스코드의 모체가된 John Robbins의 Debugging Applications라는 책을 참고하는것도 좋을 것이다.


노파심에서 사족을 달자면, 혹시 C/C++와 Windows 시스템과 메카니즘에 익숙하지 않다면 과감히 강좌를 보는것을 포기하길권유한다. 적어도 위의 소스코드정도는 쉽게 이해할 수 있어야 할것이다. 또한 필자의 사정상 빨라야 일주일에 두번정도 강좌를올릴수 있을것 같다. 그래서 전체적인 커리를 다음과 같이 제시하니 필요한 부분은 미리미리 공부한다면 빨리 따라올 수 있을것이다.


1. Win32 API 후킹의 기본

2. 다른 프로세스의 주소공간으로 들어가자 !!

3. Win32 어셈블리 프로그래밍

4. Win9x 디바이스 드라이버(VxD) 모델

5. 기계어 프로그래밍 - Shell Code 작성

6. Win9x Global API Hooking

7. WinNT/2000 디바이스 드라이버 모델

8. WinNT/2000 Global API Hooking


앞으로 전개될 강좌의 주요 테마이다. 하나같이 만만하지 않은 주제들로 이루어져있으며 한두회의 강좌로는 턱없이 부족한 주제도대부분일 것이다. 그러나 차근차근 따라오다보면 어느새 자신의 실력이 부쩍 향상되어 있음을 피부로 느낄것이다. 이것은 필자가이름을 걸고 맹세할 수 있다. 실제로 디바이스드라이버 프로그래밍과 어셈블리 프로그래밍에 대한 경험이 있거나, 얼마전까지 유행하던해킹기법인 Stack Overflow의 익스플로잇과 쉘코드를 직접 제작할 수 있는 능력이 있다면 강좌를 따라오기가 한결 수월할것이다.



출처 : http://pmguda.com/73


그 외 관련된것들dll 임포트함수 GetProcAddress 직접구현하기 : http://blog.naver.com/kimgudtjr?Redirect=Log&logNo=140098108264
http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=64685582&qb=7KCE7JetIO2bhO2CuQ==&enc=utf8&section=kin&rank=2&sort=0&spq=0
http://hemesis.tistory.com/29
Write File API 후킹하기 : http://www.reversecore.com/57
저널 후킹 : http://blog.nul.kr/38

요새 후킹쪽을 제대로 체험중이다. 이미 전역후킹은 성공했다. 궁금하면 본진블로그에서 찾아보기 바란다.


.. 아아 그나저나 SDL쪽도 구현해야 되는데 이거 제대로 피곤하다. 쿨쿨쿨..

by 쿠나 | 2010/01/17 19:50 | 트랙백 | 덧글(1)

임시파일임다.

_t.rar

평화님 잘 쓰세요 ㅋ_ㅋ

by 쿠나 | 2010/01/05 08:15 | 트랙백 | 덧글(0)

아무쨩과_함께하는_생일.jpg



출처 : http://zerode.egloos.com/2503320

아아앜ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
내가 해보고 싶은 짓 중 하나였는뎈ㅋㅋㅋㅋ ㅠㅠㅠㅠㅠ

by 쿠나 | 2010/01/05 07:05 | 트랙백 | 덧글(8)

Synth'N으로 DJMAX 트릴로지가 구동될까?

http://euijin-blog.tistory.com/41

보니까 DJMAX_NMS_DIAMOND가 문젠데... 팩 자체는 구할 수 잇을까?

아.. 아무래도 pt파일쪽은 아직 아닌 것 같다 ㅡㅅㅡ... 원리가 있을텐데.. 음.. 좋은 수확을 얻을 수 있을지도 모르겟다. 좀 찾아보면 말이지.

것보다 이거 DJMAX 스킨 굉장히 마음에 든다. 동방 BMS 구해서 하는 게 훨씬 낫다는 생각!

by 쿠나 | 2010/01/01 19:59 | 트랙백 | 덧글(1)

◀ 이전 페이지          다음 페이지 ▶