후킹 코드를 디버깅하면서 후킹된 IAT 메모리 영역을 확인해 보겠습니다.

또한 프로세스에 인젝션(Injection)된 DLL 파일을 디버깅하는 법을 살펴보도록 하겠습니다.



<IAT Hooking 코드>

본 내용은 이전 포스트에서 이어지는 내용입니다.

API Hooking – 계산기, 한글을 배우다. (1)
API Hooking – 계산기, 한글을 배우다. (2)
API Hooking – 계산기, 한글을 배우다. (3)



인젝션(Injection)된 DLL 의 디버깅


계산기(calc.exe) 프로세스에 인젝션(Injection)된 hookiat.dll 을 디버깅 하겠습니다.

DLL Injection 에 대한 더 자세한 내용은 아래 글을 참고해 주세요.
DLL Injection – 다른 프로세스에 침투하기 (1)

인젝션(Injection)된 DLL 의 디버깅 방법에 대한 더 자세한 내용은 아래 글을 참고해 주세요.
키로거(KeyLogger) 분석 (2)

계산기 프로그램을 실행하신 후 Process Explorer 로 PID 값을 확인합니다.

<Fig. 1>

이제 OllyDbg를 calc.exe 프로세스에 attach 시킵니다.

<Fig. 2>


* 참고!
OllyDbg 2.0 beta 2 를 사용합니다. 기존 1.10 버전은 Injection DLL을 디버깅 할 때 약간의 버그가 있어서 debuggee 프로세스가 종료되는 경우가 있습니다.


Attach 가 잘 되었으면 아래 그림과 같이 OllyDbg 의 옵션을 수정하여 DLL 파일(hookiat.dll)이 Injection 될 때 제어가 디버거에게 넘어오도록 합니다.

<Fig. 3>

"Pause on new module (DLL)" 옵션을 체크하면 debuggee 프로세스에 DLL 이 로딩(injection 포함) 될 때마다 제어가 debugger 에게 넘어옵니다.

이제 준비를 마쳤으니 OllyDbg 를 Run [F9] 시켜서 계산기 프로세스가 정상적으로 실행되도록 합니다.

InjectDll.exe 에 파라미터를 적절히 주고 실행하면 계산기 프로세스에 hookiat.dll 이 인젝션 됩니다. (아래 그림 참고)


<Fig. 4>


calc.exe 프로세스에 “DLL Load” 이벤트가 발생하였으므로, OllyDbg 에 해당 이벤트가 통지되고 <Fig. 3> 의 옵션에 따라서 아래 그림처럼 hookiat.dll 의 EP(EntryPoint) 에서 실행이 멈추게 됩니다.


<Fig. 5>


* 로딩된 DLL 의 EP 에 자동으로 멈추는 것은 OllyDbg 2.0 에서 지원되는 기능입니다. 이전 버전에서는 EP 가 아닌 다른 코드 위치(ntdll.dll 영역)에서 실행이 멈춥니다.

이제 <Fig. 3> 의 “Pause on new module (DLL)” 옵션을 해제(uncheck)하고 DllMain() 코드를 찾아 보겠습니다.
 
디버거에서 hookiat.dll 의 DllMain() 함수로 쉽게 찾아가는 방법은 DllMain() 에서 사용되는 문자열 혹은 API 를 검색하는 것입니다. (물론 하나 하나 StepIn 명령으로 따라가셔도 좋습니다.)

아래에 DllMain() 코드를 표시하였습니다.
(자세한 코드 설명은 API Hooking – 계산기, 한글을 배우다. (3) 포스트를 참고하세요.)

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            // original API 주소 저장
            g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
                                                        "SetWindowTextW");
            // # hook
            //   user32!SetWindowTextW() 를 hookiat!MySetWindowText() 로 후킹

            hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
            break;

        case DLL_PROCESS_DETACH :
            // # unhook
            //   calc.exe 의 IAT 를 원래대로 복원

            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
            break;
    }

    return TRUE;
}

<Code 1>

코드에서 사용되는 "user32.dll" 과 "SetWindowTextW" 문자열로 DllMain() 을 찾아보겠습니다.

<Fig. 6>

위 그림을 보시면 "user32.dll" 문자열은 2 군데가 있고, "SetWindowTextW" 문자열은 1 군데가 있습니다. "SetWindowTextW" 문자열이 참조되는 코드 주소 1000113E 로 가봅니다.

<Fig. 7>

위 그림에서 빨간 테두리의 디스어셈 코드는 <Code 1> 의 C 언어 코드와 정확히 일치합니다. 따라서 이 부분이 DllMain() 입니다. (참고로 DllMain() 의 시작 주소는 10001130 입니다.)

여기까지가 프로세스에 인젝션된 DLL 을 디버깅하는 방법에 대한 설명입니다. (이외에도 여러 가지 꼼수가 있습니다만 위에 소개한 방법이 정석이라고 할 수 있습니다.)



DllMain()


앞에서 구한 DllMain() 함수 위치부터 디버깅을 시작해 보겠습니다. (위의 <Code 1> 과 비교하면서 보시면 좀 더 쉽게 파악할 수 있습니다.)

DllMain() 에서 조금만 디버깅해 내려가면 아래와 같은 코드가 나타납니다.

<Fig. 8>

10001160 주소의 CALL 10001090 명령이 바로 hook_iat() 함수 호출 부분입니다. 함수 파라미터는 역순으로 스택에 저장되므로 각 파라미터의 의미는 주석으로 쓰여진 부분 그대로 입니다. (<Code 1> 과 <Fig. 8> 를 함께 비교해서 보세요.)

한가지 특이한 사항은 <Code 1> 에서 보면 hook_iat() 함수는 파라미터가 3 개입니다. 그러나 <Fig. 8> 에서 hook_iat() 는 파라미터가 2 개뿐입니다. (Stack Window 참조)

잘 보시면 hook_iat() 의 첫 번째 파라미터인 "user32.dll" 문자열이 생략된 걸 알 수 있습니다. 이것은 VC++ 2008 컴파일러의 코드 최적화 기능이 적용되어서 문자열 주소 (4 byte 상수) 는 함수 파라미터로 넘기지 않고 hook_iat() 함수 내에 하드코딩 해버린 것입니다.

앞으로 여러분이 직접 작성한 프로그램을 디버깅 하실 때 위와 같은 코드 최적화 기능을 자주 보게 되실 겁니다.



hook_iat()


실제로 IAT 를 후킹하는 핵심 함수인 hook_iat() 입니다.


# user32.dll 의 IMAGE_IMPORT_DESCRIPTOR 찾기

<Fig. 9>

위 그림에서 빨간색으로 표시된 부분의 코드는 PE Header 에서 어떻게 IMAGE_IMPORT_DESCRIPTOR Table (이하 IID Table) 을 찾아내는지 보여줍니다. (IID 를 PEView 에서는  Import Directory Table 로 표시합니다.)

위 코드(100010A1 ~ 100010AD)에서는 어셈블리 명령어 4 개만을 써서 IID Table 을 따라갑니다.

아직 PE Header 구조에 익숙하지 않으신 분들 또는 위와 같은 어셈블리 코드를 처음 접하신 분들께서는 코드의 내용이 잘 이해되지 않으실 겁니다.

이건 숙련도의 문제이기 때문에 계속해서 보시면 점차 나아집니다. 나중에는 [EDI+3C], [EDI+EAX+80] 등의 코드만 봐도 이게 대충 IID Table 을 따라가는 코드라는걸 알 수 있게 됩니다.


100010CA 주소의 CALL 100075CA 명령어는 stricmp() 함수 호출 코드입니다. IID Table 을 훑으면서 IID.Name 항목과 "user32.dll" 문자열을 비교하여 user32.dll 에 해당하는 IID 를 찾습니다.


# IAT 에서 SetWindowTextW API 위치 찾기

user32.dll 에 해당하는 IID 를 찾았다면 이제는 IAT(Import Address Table) 에서 SetWindowTextW API 를 찾는 코드가 이어집니다. (아래 그림 참고)

우리는 그 위치의 내용을 바꿔 쳐서 API 를 후킹할 겁니다. 

<Fig. 10>

100010E0 주소의 CMP DWORD PTR DS:[ESI], EBP 명령어에서 ESI 값은 user32.dll 의 IAT(Import Address Table) 의 시작 주소(010010A4) 입니다. 그리고 EBP 의 값은 SetWindowTextW 의 주소(77D0960E) 입니다.

즉, <Fig. 10> 의 코드는 루프를 돌면서 IAT 를 따라 내려가다가 01001110 주소의 SetWindowTextW 주소값(77D0960E) 을 찾는 내용입니다.

* 참고!
OllyDbg 의 Memory Window (<Fig. 10> 의 빨간 테두리 부분) 의 보기 옵션을 “Integer -> Address” 로 변경하면 <Fig. 10> 과 같이 [Address & API Name] 형식으로 볼 수 있습니다.


# IAT Hooking

이제 실제로 IAT 를 후킹하는 코드 입니다.

<Fig. 11>

10001117 주소의 MOV DWORD PTR DS:[ESI], EDX 명령이 바로 앞에서 구한 IAT 에서 SetWindowTextW 위치(01001110)에 후킹 함수 MySetWindowTextW 주소(10001000)를 덮어쓰는 코드입니다.

01001110 주소는 calc.exe 프로세스에서 user32.dll 의 IAT 영역입니다. 원래 이 위치에는 SetWindowTextW 주소값(77D0960E) 이 저장 되어 있었습니다. (<Fig. 10> 참고)

10001117 주소의 명령어에 의해서 user32!SetWindowTextW 주소값(77D0960E)이 hookiat!MySetWindowTextW 주소값(10001000)으로 변경됩니다. (<Fig. 11> 참고)

이제부터 calc.exe 프로세스내의 코드에서 (IAT 를 통해) user32!SetWindowTextW() API 를 호출하면 실제로는 hookiat!MySetWindowTextW() 가 호출됩니다.



MySetWindowTextW()


앞에서 IAT 후킹이 완료되었으므로 OllyDbg 를 실행[F9] 시켜서 계산기(calc.exe) 프로세스가 정상적으로 동작하게 만듭니다.

calc.exe 프로세스에서 user32!SetWindowTextW() API 를 호출하는 코드에 BP를 설치하여 실제로 hookiat!MySetWindowTextW() 함수가 호출되는 상황을 디버깅 해보겠습니다.

먼저 user32!SetWindowTextW() 호출 코드에 BP 를 설치합니다. OllyDbg 의 “Search for – All intermodular calls” 기능을 쓰면 아래와 같은 다이알로그가 나타납니다.

<Fig. 12>

calc.exe 프로세서에서 user32!SetWindowTextW() API 를 호출하는 위치는 총 2 곳입니다. 2 곳 모두 BP 를 설치합니다. (실제로는 01002628 주소의 명령어가 우리가 찾는 위치입니다.)

그리고 계산기 프로그램에 숫자 '1' 을 입력하면 (위에서 설치한) 01002628 주소의 BP 에 걸립니다. (아래 그림 참고)

<Fig. 13>

01001110 주소에는 원래 user32!SetWindowTextW() 주소(77D0960E)가 있었으나, 후킹된 이후에는 <Fig. 13> 에서 보듯이 hookiat!MySetWindowTextW() 주소(10001000)가 있습니다.

MySetWindowTextW() 함수 안으로 디버깅해 들어가면 아래 그림과 같은 코드가 나타납니다.

<Fig. 14>

MySetWindowTextW() 함수의 기능은 "숫자" 문자열을 "한글" 문자열로 변환한 후 원래 함수인 user32!SetWindowTextW() API 를 호출하는 것입니다.

1000107D 주소의 CALL DWORD PTR DS:[1000B6B8] 명령어가 바로 user32!SetWindowTextW() API 를호출하는 코드입니다. 1000B6B8 주소는 hookiat.dll 에서 .data 섹션의 전역변수(g_pOrgFunc) 를 의미합니다. DllMain() 에서 이곳에 미리 SetWindowTextW 주소를 저장해 두었습니다. (<Code 1> & <Fig. 8> 참고)

이것으로써 calc.exe 프로세스에 인젝션 된 hookiat.dll 을 디버깅을 완료하였습니다.

+---+

총 4 회에 걸쳐서 API Hooking 기법 중에서 "Dll Injection" 과 "IAT Hooking" 을 사용해서 계산기 프로세스를 후킹해 보았습니다.

다음 번에는 API Code 를 직접 패치하는 후킹 기법에 대해서 알아보겠습니다. 또한 시스템에서 실행되는 모든 프로세스들과 이후에 실행되는 모든 프로세스들에 대해서 전부 API Hooking 시키는 기술인 global Hooking 기법에 대해서 살펴보겠습니다.

재미 있으실 겁니다. 많이 기대해 주세요~ ^^

감사합니다.

ReverseCore

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

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

  1. 김대성 2009/11/27 02:27 댓글주소 | 수정 | 삭제 | 댓글

    정말 재밌게읽었습니다.... 아래서 부터 읽고 올라오다보니 새로운글이있네요 ㅎ 시간가는줄모르고 읽었습니다. 리버싱에 관심갖고 리버스엔지니어링비밀을파헤치다 책을 샀는데 초심자에게는 어려운중급책이더군요.. 그래서 실망하고있던차에 이렇게 좋은 사이트를 찾게되어서 너무기쁩니다 ^^

  2. 냥냥 2009/11/27 07:03 댓글주소 | 수정 | 삭제 | 댓글

    요즘... 낮생활로 바꿔가고 있어요 ㅠ_ㅠ... 1등은 힘드네요..ㅎㅎㅎ
    항상좋은 자료감사합니다~..~ 저도 블로그 만들었어요 .. ㅋㄷ

  3. Sun2Day 2009/11/27 13:45 댓글주소 | 수정 | 삭제 | 댓글

    항상 재미있는 글 감사합니다 '-'/

    다음 글도 기대하고 있습니다 +_+(번뜩)

  4. 늅늅 2009/11/30 09:36 댓글주소 | 수정 | 삭제 | 댓글

    아 맨날 늦어 -_-; 오늘도 한주의 시작은 이걸로 시작합니다 ㅎㅎ
    주인장님 어서 이 내용들로 책 내주세요~ 너무 잘 나와있는데..많은 이들에게 도움이 될 거 같아요~

  5. mAn1aS 2009/12/23 10:48 댓글주소 | 수정 | 삭제 | 댓글

    아 그런데 궁금한게 있습니다.
    rand 같은경우는 IAT에 포함되어 있지 않더라구여..
    detour에서도 찾질 못하는데, rand 같은 api는 어떻게 찾아서 후킹해야 할까요..
    메모리에 call 되는부분 주소를 수동으로 직접 알아서 코드 패치 할 순 있지만, 범용적이지 않은 것 같구..

    범용적으로 rand 를 후킹할 방이 있을까요?

    rand 쓰는 테스트 프로그램에 아래와 같이 rand의 주소를 얻어모면 정상적으로 얻어오는데...
    static int (WINAPIV * TrueRand)(void) = rand;

    dll에 위 주소를 얻게하고, rand 테스트 프로그램에 삽입하면, 엉뚱한 주소가 나오네요-0-

    • ReverseCore 2009/12/24 02:14 댓글주소 | 수정 | 삭제

      mAn1aS님, 안녕하세요.

      IAT 에 없는 API 를 후킹하는 방법은 지금 진행되고 있는 강좌 - "API Hooking - '스텔스' 프로세스" 를 참고하시면 될것 같습니다.

      API 시작 코드를 직접 패치하는 방식입니다.

      msvcrt!rand() API 를 후킹 하시면 되겠네요.

      그리고 API 주소를 얻을 때는 GetProcAddress() 를 이용하시면 됩니다.

      다른 질문 있으시면 올려주세요~

      감사합니다.



앞에서 소개해 드렸던 예제 프로그램(hookiat.dll)의 소스 코드를 상세히 분석하여 IAT Hooking 의 동작 원리를 점검하고 실제 구현 방법에 대해서 공부합니다.


<hookiat!MySetWindowTextW() 함수>

본 내용은 이전 포스트에서 이어지는 내용입니다.

API Hooking – 계산기, 한글을 배우다. (1)
API Hooking – 계산기, 한글을 배우다. (2)



소스 코드 분석



* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition 으로 개발 되었으며, Windows XP SP3 환경에서 테스트 되었습니다.

InjectDll.cpp 소스는 예전에 설명 드렸던 내용과 기본적인 구조는 비슷합니다. 대신 약간의 업그레이드를 하였는데요.

실행 파라미터를 늘려서 Injection 과 Ejection 을 동시에 지원하도록 수정하였습니다. 또한 권한 상승 함수를 추가시켜 (간혹 발생할 수 있는) OpenProcess() 에러를 방지 하였습니다. (관련 설명은 생략하겠습니다. 더 자세한 내용은 MSDN 을 찾아보시기 바랍니다.)

InjectDll.cpp 에 대한 더 자세한 설명은 아래 링크를 참고해 주세요.

DLL Injection – 프로세스에 침투하기
DLL Ejection - 침투시킨 DLL 빼내기


# DllMain()

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{   
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            // original API 주소저장
            g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), 
                                        "SetWindowTextW");

            // # hook
            //   user32!SetWindowTextW() 를 hookiat!MySetWindowText() 로 후킹
            hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
            break;

        case DLL_PROCESS_DETACH :
            // # unhook
            //   calc.exe 의IAT 를원래대로복원
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
            break;
    }

 return TRUE;
}

늘 그렇듯이 예제 코드의 메인 함수는 간단합니다.

중요 코드들을 한 라인씩 살펴보겠습니다.

case DLL_PROCESS_ATTACH :
    g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");


먼저 DLL_PROCESS_ATTACH 이벤트에서 user32!SetWindowTextW() API 주소를 구합니다. 이는 나중에 unhook 작업에서 사용되기 때문에 전역변수(g_pOrgFunc)에 잘 저장해 놓습니다.

hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);

그리고 hook_iat() 함수를 호출하여 IAT 를 후킹(hooking)합니다. (user32!SetWindowTextW() 주소를 hookiat!MySetWindowTextW() 주소로 변경)

case DLL_PROCESS_DETACH :
    hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);


DLL_PROCESS_DETACH 이벤트에서 IAT 후킹을 풀어(unhook)줍니다. (hookiat!MySetWindowTextW() 주소를 user32!SetWindowTextW() 주소로 변경)


# MySetWindowTextW()

BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"영일이삼사오육칠팔구";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;

    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        // '수'문자를 '한글'문자로 변환
        //   lpString 은 wide-character (2 byte) 문자열

        if( L'0' <= lpString[i] && lpString[i] <= L'9' )
        {
            temp[0] = lpString[i];
            nIndex = _wtoi(temp);
            lpString[i] = pNum[nIndex];
        }
    }

    // user32!SetWindowTextW() API 호출
    //   (위에서 lpString 버퍼 내용을 변경하였음)

    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}


IAT 가 후킹된 계산기(calc.exe) 프로세스는 코드 내에서 user32!SetWindowTextW() 함수를 호출할 때마다 사실은 위의 hookiat!MySetWindowTextW() 함수가 호출됩니다.

함수의 파라미터 lpString 은 출력할 (wide-character) 문자열 버퍼입니다. 따라서 이 lpString 을  조작하면 사용자가 원하는 문자열을 출력시킬 수 있습니다.

nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
    if( L'0' <= lpString[i] && lpString[i] <= L'9' )
    {
        temp[0] = lpString[i];
        nIndex = _wtoi(temp);
        lpString[i] = pNum[nIndex];
    }
}

위의 for 루프에서 lpString 에 저장된 ‘수’ 문자열을 ‘한글’ 문자열로 교체합니다.

아래는 lpString 버퍼의 변경 전/후의 그림입니다.

<Fig. 1>

숫자 "123" 에 대해서 한글은 "일이삼" 으로 표시할 수 있습니다. 즉, 숫자 1 글자당 한글 1 글자로 1:1 대응이 가능하다는 것이죠. 이런 특성은 기존 버퍼를 그대로 사용할 수 있다는 장점이 있습니다. 위 코드를 보시면 lpString 문자열 버퍼에 직접 변환된 문자열(한글)을 기록합니다.

참고로 만약 영어라면 "ONETWOTHREE" 와 같이 길게 표시해야 하기 때문에 기존 버퍼("123")에 덮어쓸 수 없고, 새롭게 버퍼를 할당 받아서 사용한 후 그 버퍼 주소를 original API 에 넘겨야 합니다.

return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);

for 루프가 종료하면 마지막으로 함수 포인터 g_pOrgFunc 를 호출합니다.

g_pOrgFunc 는 DllMain() 에서 미리 구해놓은 user32!SetWindowTextW() API 시작 주소입니다.
즉, 원본 함수를 호출해서 계산기의 에디트 윈도우에 (변환된) ‘한글’ 문자열을 출력시킵니다.


# hook_iat()

BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
    HMODULE hMod;
    LPCSTR szLibName;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
    PIMAGE_THUNK_DATA pThunk;
    DWORD dwOldProtect, dwRVA;
    PBYTE pAddr;

    // hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
    hMod = GetModuleHandle(NULL);
    pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
    pAddr += *((DWORD*)&pAddr[0x3C]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&pAddr[0x80]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    for( ; pImportDesc->Name; pImportDesc++ )
    {
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
        szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
        if( !stricmp(szLibName, szDllName) )
        {
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
            pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
                                         pImportDesc->FirstThunk);

            // pThunk->u1.Function = VA to API
            for( ; pThunk->u1.Function; pThunk++ )
            {
                if( pThunk->u1.Function == (DWORD)pfnOrg )
                {
                    // 메모리 속성을 E/R/W 로변경
                     VirtualProtect((LPVOID)&pThunk->u1.Function,
                                    4,
                                    PAGE_EXECUTE_READWRITE,
                                    &dwOldProtect);

                    // IAT 값을 변경 (후킹)
                    pThunk->u1.Function = (DWORD)pfnNew;

     
                    // 메모리 속성 복원
                    VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   dwOldProtect,
                                   &dwOldProtect);      

                    return TRUE;
                }
            }
        }
    }

    return FALSE;
}


IAT Hooking 을 수행하는 함수입니다.
코드 자체는 짧은데 주석이 많아서 전체적으로 길어 보입니다.

hook_iat() 함수의 초반부는 PE Header 정보를 읽어서 IAT 를 따라가는 내용입니다. 위 코드를 이해하시려면 꼭 IAT 구조를 알고 계셔야 합니다.

IAT 에 대한 상세한 정보는 아래 링크를 참고해 주세요.
PE(Portable Executable) File Format (6) - PE Header

코드를 보면 PE Header 를 따라가다가 아래 코드에서 우리가 원하는 Import Directory Table 의 시작 주소를 구합니다.

pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

Import Directory Table 은 IMAGE_IMPORT_DESCRIPTOR 구조체 배열로 이루어져 있습니다. IAT 를 구하려면 먼저 이곳을 찾아야 합니다.

위의 코드에서 pImportDesc 에는 01012B80 값이 들어옵니다. 이 주소를 PEView 로 보면 아래 그림과 같습니다.

<Fig. 2>

위 그림이 계산기 프로세스의 IMAGE_IMPORT_DESCRIPTOR 테이블(배열) 입니다. PEView 에서는 Import Directory Table 라고 표시하는 곳이죠. 우리가 목표로 하는 user32.dll 은 <Fig. 2> 의 아래쪽에 위치하는군요. 저곳을 찾아가 봅시다.

for( ; pImportDesc->Name; pImportDesc++ )
{
    // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
    szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
    if( !stricmp(szLibName, szDllName) )
    {

위의 for 루프 내에서 pImportDesc->Name 과 szDllName("user32.dll") 을 비교해서 user32.dll 의 IMAGE_IMPORT_DESCRIPTOR 구조체 주소를 찾아냅니다.

결국 pImportDesc = 01012BE4 가 됩니다. (<Fig. 2> 참고)

이제 user32.dll 에 대한 IAT 로 가야 합니다. pImportDesc->FirstThunk 멤버가 바로 IAT 를 가리키는 멤버입니다.

// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
//        = VA to IAT(Import Address Table)

   pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + 
                                pImportDesc->FirstThunk);

위 코드에서 pThunk 가 바로 user32.dll 에 대한 IAT (010010A4) 입니다. (<Fig. 2> 참고)
이 주소를 PEView 로 보면 아래 그림과 같습니다.

<Fig. 3>

User32.dll 의 IAT 에 상당히 많은 함수들이 import 되어 있는걸 보실 수 있습니다. 우리가 찾는 SetWindowTextW 는 01001110 주소에 있으며 값은 77CF61C9 입니다.

// pThunk->u1.Function = VA to API

for( ; pThunk->u1.Function; pThunk++ )
{
    if( pThunk->u1.Function == (DWORD)pfnOrg )
    {

위의 for 루프에서 pThunk->u1.Function 과 pfnOrg (77CF61C9 <– SetWindowTextW 시작주소) 를 비교하여 정확히 SetWindowTextW 의 IAT 주소(01001110)를 찾아 냅니다. (이제 pThunk = 01001110 이고 pThunk->u1.Function = 77CF61C9 입니다.)

여기까지의 코드가 계산기 프로세스의 ImageBase 에서부터 user32!SetWindowTextW 의 IAT 주소를 찾아가는 과정입니다.

이후부터는 위에서 구한 IAT 주소의 값을 바꿔 치는 (후킹) 코드가 이어집니다.

// IAT 값을 변경
pThunk->u1.Function = (DWORD)pfnNew;
     
pThunk->u1.Funcion 에는 원래 77CF61C9 (SetWindowTextW 주소) 값이 있었지만, pfnNew (10001000 <- hookiat!MySetWindowTextW 주소) 값으로 변경합니다.

이제 계산기 코드에서 user32!SetWindowTextW() API 를 호출하면 hook_iat!MySetWindowTextW() 가 호출되는 것입니다.


* 참고!
저 위쪽의 hook_iat() 함수 코드를 보시면 후킹 하기 전에 VirtualProtect() 함수를 이용해서 해당 IAT 메모리 영역 을 "읽기|쓰기" 모드로 변경하고, 후킹 한 후에 다시 원래 모드로 되돌리는 코드가 존재합니다. 계산기 프로세스의 IAT 메모리 영역이 "읽기" 전용이기 때문에 미리 이러한 작업을 해주는 것입니다.


이상으로 hook_iat.cpp 의 코드 설명을 마치겠습니다.

다음 포스트에서는 OllyDbg 를 이용하여 후킹 코드를 디버깅하면서 후킹된 IAT 메모리 영역을 확인해 보겠습니다.

많이 기대해 주세요~


API Hooking – 계산기, 한글을 배우다. (4)


+---+


ReverseCore

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

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

  1. 오호^^ 2009/11/21 01:25 댓글주소 | 수정 | 삭제 | 댓글

    드디어 저도 눈팅만 하다 1등으로 댓글 남기네요

  2. 냥냥 2009/11/21 03:38 댓글주소 | 수정 | 삭제 | 댓글

    아니되!!! 이럴수가!! ㅋㅋ

  3. Pdm1n 2009/11/21 18:29 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다 잘보았습니다. 사랑합니다.

  4. 늅늅 2009/11/23 09:00 댓글주소 | 수정 | 삭제 | 댓글

    출근과 동시에 왔건만...오늘 오전도 이거 보면서..하하;;

    • reversecore 2009/11/23 10:47 댓글주소 | 수정 | 삭제

      늅늅님, 안녕하세요.
      출근과 동시에 제 블로그에 들러주시다니...
      정말 감사합니다. ^^
      좋은 하루 되세요~

  5. nettok 2009/11/24 09:41 댓글주소 | 수정 | 삭제 | 댓글

    항상잘 보고있습니다^-^ 나중에 출판하셔도 될듯

  6. 돈 없는 1人 2009/11/25 08:28 댓글주소 | 수정 | 삭제 | 댓글

    책 내셔도 이 홈페이지 닫으시면 안되욥 ㅠㅠ (ㅋㅋ)

  7. 쵸밥 2009/11/26 04:01 댓글주소 | 수정 | 삭제 | 댓글

    어제도 밤새서 아침 8시까지 읽다 잤는데 ㄷㄷ;;
    PE header 읽다보니 벌서 오늘 새벽 4시네요 ;;

    어제 pm4시 부터 하루종일 PE header .. 페이징 .. 가상주소 .. 이리저리 싸우는중 . . ㅋㅋ
    영어실력이 안되서 구글링은 못하고 이런 한글사이트 돌아다니고 책뒤지고 늅늅 ㅠㅠ
    학교 다닐때 영어 안한게 MSDN 부터 구글링 까지 이리 치명적으로 발목을 잡을 줄이야 늅늅 ㅠㅠ

    늅늅님 아이디 우는모양 같네요 ㅋㅋ

    아 댓글중에 cOrE 님 이라고 써놓은거 Hellow World! 보다가 궁금해져서 질문 써놓은건데 ㄷㄷ;;

    • reversecore 2010/02/02 00:23 댓글주소 | 수정 | 삭제

      쵸밥님, 안녕하세요.

      제 블로그 글들을 정리하는 중에 제가 미처 읽지 못한 댓글이 있었네요.

      밤새서 리버싱 공부를 하셨나 보네요.
      열정이 정말로 대단하세요~

      자주 들러주세요~

      감사합니다.

  8. 뭉이 2010/01/04 22:29 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 잘 읽고 있습니다. 설명도 쉽게 잘해주시고 깔끔하고 머리에 속속 들어옵니다..^^

    그런데 소스가...예외처리가 미흡한것 같습니다.
    dll인젝션 소스가 적어도 try catch정도는 묶어줘여 하지 않을까 생각이 듭니다.
    드라이버단 후킹도 아니고 인젝션이라 많이 크리티컬하지는 않습니다만...;
    처음 공부하는 사람입장에서.. 소스 참고하여 사용하다가 작은 실수로 프로그램 뻑날 수도..???

    심플하게 소스 이해하는데는 어려움이 없지만..그래도 기본적이고 필요한건 빠지지 않았으면 하는 생각이 듭니다.

    그리고 한가지 더...
    OS 테스트 환경에 vista, win7도 했으면 하는 바램입니다....

    바쁘실텐데...이런 좋은 내용 써주셔서 너무너무 감사합니다..^^;;

    • reversecore 2010/02/02 00:31 댓글주소 | 수정 | 삭제

      뭉이님, 안녕하세요.

      제 블로그 글들을 정리하는 중에 제가 미처 읽지 못한 댓글이 있었네요.

      제 예제 소스에는 리턴값 체크, 예외 처리 등이 미흡한게 사실입니다. 코드의 간결성을 위해서 그렇게 한것인데요.

      말씀하신대로 초보자 분들께서는 이를 활용할 때 분명 어려움을 겪으실 것 같네요. 좀 더 방법을 보완해 보도록 하겠습니다.

      가령 설명에 쓰이는 코드는 간단히 개념위주로... 제공되는 소스 파일에는 완벽한 에러처리 코드를 추가하는 방법으로 말이죠.

      그리고 최근에 저도 Windows 7 환경을 갖췄습니다. 앞으로는 XP 와 7 에서 동시에 테스트해서 제공할 예정입니다.

      좋은 의견 주셔서 감사합니다.

      또 한번 제가 성장할 수 있는 소중한 기회가 되었습니다.

      감사합니다.

  9. 비셔스 2010/02/06 22:33 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요~ 공부하던 중에 궁금한게 생겨서 질문을 남깁니다.

    GetModuleHandle(NULL)은 결국 프로세스의 핸들값을 가져오는것이던데.

    (pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); 을 보면
    hMod는 ImageBase값 같은데 맞나요?

    그렇다면 프로세스의 핸들값이라는게 ImageBase의 값을 말하는건가요?

    ImageBase의 값이 맞다면 PE파일에 적힌것하고는 틀리게 되는데 왜 그런지 궁금합니다.

    • reversecore 2010/02/08 23:48 댓글주소 | 수정 | 삭제

      비셔스님, 안녕하세요.

      1)
      GetModuleHandle(NULL) => ImageBase 맞습니다. ^^

      이 값은 '프로세스 핸들' 과는 다릅니다.
      (용어가 헷갈리긴 합니다만...)

      보통 프로세스 핸들이라 하면, CreateProcess(), OpenProcess() 의 리턴값을 말하지요.

      2)
      실제 프로세스 메모리에 로딩된 base 주소가 파일의 ImageBase 주소와 틀리다면, VISTA 이상의 OS 에서 ASLR 기능이 동작한 것입니다.

      아래 글을 참고하시기 바랍니다.

      http://www.reversecore.com/69

      다른 질문 있으시면 올려주세요~ ^^

      감사합니다.

  10. graythief 2010/03/11 21:18 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요~~지금 IAT HOOK를 공부하고 있습니다 그런데
    제가 나름 책이나 인터넷 보고 짜고 있는데요
    pThunk->u1.Function = (DWORD)pfnNew;
    저 위에 있는 붉은 부분 형변환이 정말 되는지 알고 싶습니다 proc 형인 pfnNew형이 DWORD로 변환 시키려고하면 cannot convert from 'unsigned long' to 'unsigned long *' 이런 오류가 생기는데요 혹시나 알고 계신가 해서 여쭈어봅니다
    이런식으로 (DWORD*)pfnNewProc 하면 당연히 가능하긴 합니다만 아직 코드가 완성되지 않는상태에서 에러를잡다보니 제값이 들어가는지도 확인이 불가능해서요..
    물론 저코드는 아니구요 그냥 제가 짜고 필요한 코드는 찾아서 짜는 -_-;;코드입니다 쿨럭;;;

    • reversecore 2010/03/11 22:12 댓글주소 | 수정 | 삭제

      graythief님, 안녕하세요.

      pThunk->u1.Function = (DWORD)pfnNew;
      형변환을 문의하셨네요.

      형변환 잘 됩니다. (VC++ 2008 기준)

      (DWORD*)pfnNewProc 으로 하면 오히려 에러가 납니다.
      L-value 가 DWORD 타입이거든요.

      혹시 자꾸 에러가 발생하면 그 부분의 소스를 올려주시면 제가 봐드릴께요~

      감사합니다.

  11. 롤키 2010/03/24 13:36 댓글주소 | 수정 | 삭제 | 댓글

    맨 마지막에 함수로 복귀할때
    캐스팅을 pfsetwindowtextW로 하셨는데.. 무슨타입인지 궁금합니다.

    • reversecore 2010/03/25 23:36 댓글주소 | 수정 | 삭제

      롤키님, 안녕하세요~

      혹시 아래 코드를 말씀하시는 건지요?
      return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);

      본문에 첨부된 hookiat.cpp 에 보시면 아래와 같이 함수 포인터를 정의하였습니다.

      typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

      즉, 원본 SetWindowTextW() 함수입니다.

      감사합니다.

  12. Ezbeat 2010/04/19 11:09 댓글주소 | 수정 | 삭제 | 댓글

    소스코드를 보다가 궁금한 점이 있는데요.
    __cdecl, __stdcall 이 두개의 차이점은 알고 있는데요.
    MySetWindowTextW 함수를 __stdcall로 하는지 이유를 모르겠어서요 ㅜㅜ

    • reversecore 2010/04/19 23:26 댓글주소 | 수정 | 삭제

      calling convention 에 대해서 질문을 하셨네요.

      마침 제 블로그에 그에 대해 설명한 글이 있습니다.

      http://www.reversecore.com/13
      아마 여기에 원하시는 설명이 있을것입니다.

      또한 Win32 API 는 모두 __stdcall 형식이므로 후킹함수인 MySetWindowTextW 또한 __stdcall 형식으로 해줘야 하는것입니다.

      감사합니다.

  13. 보름♬달★콤 2010/06/13 22:46 댓글주소 | 수정 | 삭제 | 댓글

    계속 질문 드리게 되네요.

    IAT의 함수 주소를 바꿔치기할 함수 주소로 바꾸기 전에
    VirtualProtect를 통해 속성을 변경하는 코드가 있는데요,

    "Windows 시스템 실행파일의 구조와 원리/이호동(한빛미디어)" 책에 보면 위의 과정이 빠져있습니다.
    실제로 제가 책대로 테스트를 해보니 자꾸 실패하더라고요.

    그래서 reversecore님 코드를 따라서 VirtualProtect 코드를 삽입하니까 되네요.
    원래 IAT의 경우 r/w속성 아닌가요? 그런데 왜 VIrtualProtect로 속성 변경하는 과정이 필요한가요?

    제가 테스트 하는 환경은 windows7에서 notepad.exe인데요,
    windows7에 포함된 notepad.exe PE파일 자체가 IAT가 포함된 섹션의 write속성을 빼버린 건가요?

    • reversecore 2010/06/15 00:25 댓글주소 | 수정 | 삭제

      안녕하세요.

      VirtualProtect() 로 메모리 속성을 변경해주는 것이 맞습니다.

      calc.exe 또는 notepad.exe 의 예를 들면 IAT 는 ".text" 섹션에 있는데, 이 ".text" 섹션은 PAGE_EXECUTE_READ (0x20) 속성입니다. 이를 PAGE_EXECUTE_READWRITE(0x40) 속성으로 변경해야 IAT 에 값을 쓸 수 있습니다.

      그렇다면 시스템은 어떻게 IAT 에 정확한 API 의 주소를 써넣을 수 있었을까 하는 질문이 남습니다.

      이 질문은 더 나아가 ".text" 섹션의 메모리 영역이 읽기 속성인데 어떻게 코드를 써 넣을수 있었을까 하는 질문으로 확장시킬 수 있습니다.

      시스템은 프로세스를 생성할때 필요한 메모리를 확보하고, 파일 이미지를 메모리에 로딩시킨 후, IAT 를 세팅한 다음, 각 섹션의 메모리 속성을 세팅하는 것입니다. 그 이후부터는 함부로 쓸 수 없지요.

      감사합니다.

  14. Usemap 2010/10/04 16:52 댓글주소 | 수정 | 삭제 | 댓글

    글 감사히 읽었습니다.

    그런데 저기 위에 있는 DllMain 프로시저는 Dll이 인젝션 되었을 그 때부터 실행되는데..

    그런데 비주얼베이직 6.0 에서는 클래스 모듈로 dll을 컴파일 했습니다.

    그런데 DllMain 이라고 프로시저 이름을 설정하거나, Class_Initialize 라는 이벤트를 넣어도 인젝션

    되었을 그 시점에서도 실행이 안되도라구요. c++ 에서는 자동으로 DllMain이 실행되도록 설정하

    는건가요?

    • reversecore 2010/10/05 21:06 댓글주소 | 수정 | 삭제

      안녕하세요.

      Win32 API 로 제작하는 DLL 파일은 로딩되면 무조건 DllMain() 이 실행됩니다.

      VB 로 만드는 DLL 이라고 해서 구조가 다르지 않을것 같은데요...
      해당 파일을 제 이메일로 보내주시면 봐드리겠습니다.

      파일 확장자를 dllxx 등으로 변경하신 후 아래 계정으로 보내주세요.

      reversecore@gmail.com

      감사합니다.

  15. Usemap 2010/11/13 11:36 댓글주소 | 수정 | 삭제 | 댓글

    으음... 아무리 DLLMain을 찾아봐도 없네요.

    Win32 API 는 VB에서 선언해야 쓸 수 있는데

    DllMain 은 선언형도 없는데 이걸 어떻게 쓸지 고민이네요.

    VB 6.0에서는 어떻게 DllMain을 쓸수 있는거죠?

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

      안녕하세요.

      제가 VB 개발 경험이 없어서 큰 도움이 못되어 드려 죄송합니다.

      아마 VB 에서 DLL 프로젝트 생성하는 법이 따로 있을 것입니다.

      * DllMain() 이 겉으로 드러나지는 않고 VB 내부에서 사용하겠지요.

      감사합니다.

  16. 이형우 2010/12/23 10:29 댓글주소 | 수정 | 삭제 | 댓글

    DLL 소스를 VS 6.0 에ㅓ 컴파일 하려고하니

    f( pThunk->u1.Function == (DWORD)pfnOrg )
    : no conversion from 'unsigned long' to 'unsigned long * 언사인 필요라고 나오네요
    형을 제맘대로 바꾸고싶지만 에러날까봐 여쭙습니다

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

      안녕하세요.

      구조체 정의에 따르면 pThunk->u1.Function 는 DWORD 타입입니다.

      typedef struct _IMAGE_THUNK_DATA32 {
      union {
      DWORD ForwarderString; // PBYTE
      DWORD Function; // PDWORD
      DWORD Ordinal;
      DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
      } u1;
      } IMAGE_THUNK_DATA32;
      typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

      만약에 잘 안되시면 컴파일러가 원하는 대로 타입케스팅 해서 컴파일 해보시기 바랍니다.

      메일 남겨주시면 VC++ 6.0 프로젝트 파일을 보내드리겠습니다.

      감사합니다.

  17. 팔극진 2011/12/14 11:33 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요~

    강의 보면서 궁금한 부분이 있어서 질문드립니다.

    아래 보면은 hmod 인 image base 주소를 dwRVA에 더해서 pImportDesc에 넣어줬는데요~

    그 아래 table찾아가는 부분에서 왜 또 hMod를 더해 주는건가요?

    이미 pImportDesc 여기에는 image base + dwRVA해서 import address table까지 찾아간거 아닌가요? 또 더해주면 이상한 주소로 가야되는게 정상 아닌가요?

    아직 허접해서 이해가 안되네요 ㅜㅠ

    보시고 답변 부탁드립니다. 수고하세요~!!

    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
    ....
    ....
    szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

    • reversecore 2011/12/21 01:28 댓글주소 | 수정 | 삭제

      안녕하세요.

      szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

      위 코드 말씀이시죠?

      szLibName 에 VA(Virtual Address = 32bit 가상 메모리 주소) 저장하기 위해서 인데요.

      pImportDesc->Name 의 값이 실제로는 RVA 이기 때문에 hMod(ImageBase) 를 더해줘야 제대로 VA 값이 되는 것입니다.

      감사합니다.

  18. 팔극진 2012/01/16 15:46 댓글주소 | 수정 | 삭제 | 댓글

    언제나 친절한 답변 감사드립니다. ^^
    이해가 쉽게 되었습니다.~!



API Hooking 기법 중에서 DLL Injection 기법을 설명 드리겠습니다.

프로세스에 Injection 된 DLL 파일은 IAT(Import Address Table)후킹(hooking)하여 프로세스에서 호출 되는 특정 API 의 기능을 변경시킵니다.

Windows 계산기(calc.exe) 프로세스에 dll 을 삽입시켜 IAT의 user32!SetWindowTextW() API 주소를 후킹합니다. SetWindowTextW() API 가 후킹당한 계산기는 계산결과를 숫자가 아닌 한글로 출력하게 됩니다.


<SetWindowTextW() 가 후킹된 계산기 프로세스>

* 본문 내용과 관련된 정보는 아래와 같습니다.

PE(Portable Executable) File Format (1)
- PE(Portable Executable) File Format (6)
DLL Injection – 다른 프로세스에 침투하기
API Hooking – 리버싱의 ‘꽃’



Tech Map



<Fig. 1>

위 그림은 API Hooking TechMap 에서 "DLL Injection 을 통한 IAT 후킹 기법"빨간색으로 보여줍니다.

이 방식의 장점은 동작 원리와 구현이 비교적 간단하다는 것입니다. (원하는 API 를 사용자 DLL 에 재정의 하고 프로세스에 Injection 시키면 됩니다.)

단점으로는 후킹을 원하는 API 가 대상 프로세스의 IAT 에 존재하지 않다면 사용할 수 없다는 것입니다. 즉, 프로그램 코드 상에서 동적으로 DLL 을 로딩해서 사용하는 API 의 경우는 이 방법으로 후킹할 수 없습니다.



대상 API 선정


작업 목표를 설정한 후 API 후킹 기법을 사용하겠다고 결정하였다면, 그 다음으로 중요한 작업은 후킹 대상 API 를 선정하는 작업입니다.

초보자 분들께서는 어쩌면 이 부분에 어려움을 겪으실 수 있겠습니다.
왜냐하면 후킹을 원하는 기능을 제공하는 API 가 뭔지 알아야 하기 때문입니다.

예를 들면 파일 생성은 kernel32!CreateFile() API, 레지스트리 생성은 advapi32!RegCreateKeyEx() API, 네트워크 접속은 ws2_32!connect() API 등에서 담당합니다.

개발/리버싱 경험이 많다면 원하는 API 를 쉽게 떠올릴 수 있지만, 그렇지 않다면 검색이 필요합니다. 공개된 API 말고 공개되지 않은 undocumented API 를 후킹해야 하는 상황도 있기 때문에 검색은 필수입니다. 검색이 잘 안될 때는 일단 경험(또는 직관)에 의존하여 선택한 후 검증 작업을 거치면 됩니다.

적절한 API 를 선택하기 전에 먼저 작업 목표를 설정합니다.
이번 포스트의 실습 목표는 "계산기의 텍스트 에디터에 표시되는 모든 숫자를 한글로 변경" 하기입니다.

PE View 등의 유틸리티를 이용해서 계산기(calc.exe)에서 import 하는 API 를 확인합니다.

<Fig. 2>

위 그림을 보시면 2개의 API 가 눈에 띕니다. 바로 SetWindowTextW(), SetDlgItemTextW() 입니다.

두 API 모두 텍스트 에디터에 글씨를 써주는 역할을 합니다. 그런데 SetDlgItemTextW() 는 내부적으로 다시 SetWindowTextW() 를 호출하기 때문에 여기서는 SetWindowTextW() 를 후킹하면 될 것이라고 일단 가정합니다.

SetWindowTextW() API 의 정의를 보시겠습니다.

BOOL SetWindowText(     
    HWND hWnd,
    LPCTSTR lpString
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms633546(VS.85).aspx

이 API 는 파라미터로 윈도우 핸들(hWnd)과 문자열 포인터(lpString)를 받습니다.
우리가 딱 원하는 파라미터(lpString)가 있네요. 실제 후킹할 때는 저 문자열(lpString)을 살펴보고 숫자를 한글로 변경하면 될 것 같습니다.

*참고!
API 이름 뒤 ‘W’ 의 의미는 해당 API 의 “Wide character” 버전을 의미합니다. 이와 대응해서 같은 이름으로 끝에 ‘A’ 가 붙은 API 들이 있는데 이는 “ASCII character” 버전을 의미합니다.
예) SetWindowTextA(), SetWindowTextW()

OllyDbg 로 위 가정을 "검증" 해보겠습니다.

<Fig. 3>

위 그림과 같이 Search for - All intermodular calls 명령을 사용하여 계산기(calc.exe) 코드 내에서 호출되는 모든 SetWindowTextW() API 에 대해서 BP 를 설치한 후 실행합니다.

실행시키자마자 아래 그림과 같이 BP 에 걸립니다.

<Fig. 4>

SetWindowTextW() API 의 lpString 파라미터는 스택(ESP+4) 에 있는 7FB5C 입니다. (OllyDbg 에서는 "Text" 라고 표시해 주는군요.)

7FB5C 주소에 가면 "0. " 문자열이 wide character 형식으로 저장되어 있습니다. 이 문자열은 계산기에 있는 텍스트 창의 초기값입니다.

이 상태에서 그대로 실행해 보겠습니다.

<Fig. 5>

위 <Fig. 5> 그림과 같이 계산기(calc.exe)가 정상적으로 실행되고 텍스트 창에 <Fig. 4> 에서 보았던 "0. " 문자열이 나타납니다. (". " 은 계산기에서 자동으로 붙여주는 문자열입니다.)

좀 더 디버깅을 하기 위해서 계산기에 숫자를 입력해 보겠습니다.
숫자 7 을 입력 하였습니다. 이미 BP 가 걸려있기 때문에 <Fig. 4> 와 동일한 위치에서 멈춥니다.

<Fig. 6>

위의 그림에서는 Text 파라미터의 값이 7F978 로 <Fig. 4> 의 7FB5C 값과는 틀립니다만, 7F978 주소에 지금 입력한 “7“ 이 보입니다. (끝에 있는 “. “ 문자열은 계산기에서 자동으로 추가해주는 문자열입니다.)

테스트를 위해서 이 숫자 “7” 을 한글 “칠” 로 바꿔 보겠습니다.

한글 “칠” 에 해당하는 wide character 는 0xCE60 입니다.

이 값을 아래와 같이 7F978 주소에 덮어쓰겠습니다.

<Fig. 7>

Intel Architecture 계열은 Little Endian 표기법에 따라서 역순(60CE)으로 써줘야 한다는 걸 기억하세요.

위와 같이 SetWindowTextW() API 의 lpString(또는 Text) 파리미터 내용을 변경한 후 실행하면 아래 그림과 같이 숫자 "7" 이 한글 "칠" 로 변경됩니다.

<Fig. 8>

이것으로써 SetWindowTextW() API 에 대한 검증이 완료되었습니다.


다음 포스트에서 IAT 후킹의 동작 및 구현 원리를 알아보고, 계산기의 SetWindowText() API 의 IAT 후킹 소스 코드에 대해서 살펴보도록 하겠습니다.


API Hooking - 계산기, 한글을 배우다. (2)


+---+

ReverseCore


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

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

  1. 냥냥 2009/11/10 01:37 댓글주소 | 수정 | 삭제 | 댓글

    이번에도 제가 1등인가요..?! > <// ㅎㅎ
    항상 감사합니다~

  2. 늅늅 2009/11/10 09:53 댓글주소 | 수정 | 삭제 | 댓글

    2등이군요.

    이런데서 등수놀이 할 줄이여 -.-;;

    text 로만 보면 어려울 수 있는 내용을 그림과 함께 잘 설명해 주셔서 감사합니다 ^^

  3. 이승철 2009/11/10 17:38 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요!@ 이승철입니다.

    다름이 아니라 리플을 읽어 보니깐 오프라인 강의나 책을 출판 하신다고 하시는데

    혹시 어디에 사세요? 서울 사시나요? 전 부산이라서 ㅜㅜ

    출판 하게 되신다면 윤성우강사님처럼 친철한 강의 원하는데

    • ReverseCore 2009/11/10 22:39 댓글주소 | 수정 | 삭제

      이승철님, 안녕하세요.

      전 서울입니다. ^^

      출판, 강연은 아직 먼 미래의 일이구요...

      음... 저도 친절하고 싶어요 ^^

  4. safscx 2009/11/10 21:52 댓글주소 | 수정 | 삭제 | 댓글

    굿

  5. 2009/11/11 20:57 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  6. 뿡뿡이 2009/11/12 00:25 댓글주소 | 수정 | 삭제 | 댓글

    우와...

    리버싱 공부 시작한지 이제 3일된 초보자 입니다....

    관련 자료들 찾던중...이렇게 재미있고 괜찮은 강좌가 있눈 사이트를 발견한게

    정말 행운이라고 생각합니다!!!! 아직 초보라 강좌 내용은 잘모르지만..

    그래도!! 1시간 가량 정말 잼있게 읽었습니다..ㅎㅎㅎ

    앞으로도 자주 들러볼께요!!

    좋은 강좌 많이 해주세용 ㅋㅋㅋ

  7. 부자봉스 2009/11/12 00:36 댓글주소 | 수정 | 삭제 | 댓글

    참 잘 보고 있습니다.. 지금 시작이지만 좋은 공부가 될거 같네요

  8. 부자봉스 2009/11/13 02:00 댓글주소 | 수정 | 삭제 | 댓글

    일일이 답변주시고 감사합니다

  9. 쭈욱 2010/01/20 16:51 댓글주소 | 수정 | 삭제 | 댓글

    정성껏 작성하신 글 잘 봤습니다. 감사합니다~
    앞으로도 계속 도움이 될거 같네요. 후킹은 첨이라서 얼떨떨하네요.

  10. 경서기 2010/02/19 17:29 댓글주소 | 수정 | 삭제 | 댓글

    후킹... 다른 글은 어려운 말로 나를 괴롭혔는데...
    여기 글 보고... 한번 해봐야겠다는 의욕이 생기네요 ^^
    좋은 감사합니다.

    • reversecore 2010/02/19 23:52 댓글주소 | 수정 | 삭제

      안녕하세요.

      제 글을 읽고 의욕이 생기셨다니 기쁘네요. ^^

      후킹을 공부하시다가 질문이 있으시면 올려주세요~

      감사합니다.

  11. 시간의흔적 2010/03/26 15:53 댓글주소 | 수정 | 삭제 | 댓글

    정말 글을 잘 읽고 있습니다 ^^ 넘넘 감사드리구요..
    눈으로만 보다가 실제로 해보니 궁금한 점이 생겨서 질문을 들려요..
    만약 칠. -> HEX code로 바꿀때.. 다른 툴을 쓰시는 건지 아님 OllyDbg를 쓰시는건지 ^^;;
    넘 기본적인 질문인가요?? OllyDbg를 첨써봐서 ^^
    지금 열심히 따라하고 있습니다 ㅎㅎ..

    • reversecore 2010/03/28 00:06 댓글주소 | 수정 | 삭제

      시간의흔적님, 안녕하세요.

      OllyDbg 는 아니구요.
      예전에 프로그래머로 일할때 이미 알고 있던 내용입니다.

      간단히 VC++ 에서 아래와 같이 프로그래밍하신 후 디버깅 하시면 해당 값을 알 수 있습니다.

      wchar_t wc = L"칠";

      따라 하시다가 다른 궁금한 내용 있으시면 질문 올려주세요.

      감사합니다.

    • Ezbeat 2010/04/11 14:12 댓글주소 | 수정 | 삭제

      저도 그거때문에 고생한 적이 있어요~!
      댓글 보고 바로 프로그램으로 만들어봤어요 ^^;

      http://ezbeat.tistory.com/174
      항상 디버거로 열어서 보기 귀찮으시다면.. 써주세요
      ㅜ ㅜㅋㅋ

    • reversecore 2010/04/11 23:05 댓글주소 | 수정 | 삭제

      Ezbeat님, 안녕하세요.

      답변 감사드리고요,
      좋은 프로그램 만들어 주셔서 감사합니다.

  12. 시간의흔적 2010/03/29 11:40 댓글주소 | 수정 | 삭제 | 댓글

    아 그렇군요 ^^ OllyDbg에서 안되길래.. 다른 프로그램을 쓰시는줄 알았습니다.
    감사합니다~

  13. 안정현 2010/06/28 11:59 댓글주소 | 수정 | 삭제 | 댓글

    search-무슨무슨 모듈콜 이거있자나요 ㅋ 아무것도안뜨는건 무슨현상이죵 ㅜ

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

      음... 질문내용이 잘 이해가 안되는데요...
      OllyDbg 에서 해당 명령을 내리면 아무런 창이 안보인 다는 말씀이신가요?
      해당 윈도우가 작게 숨어있던지 다른 윈도우 밑에 깔려있는것은 아닐까요?

  14. 궁금이 2011/01/26 06:39 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요. 작년에 reversecore.com에서 크랙미 리버싱 강좌를 보다가 어셈블리가 어렵고 눈에 안들어와서 접었다가

    각종 후킹 방법들을 배우다 보니 다시 이곳에 오게되었습니다. 아직 몇개 읽어보지는 못했지만 이 곳 만큼 자료가 많고 정리 잘 된곳도 없다는 생각이 드네요. 감사드립니다..

    사실 처음에 어셈블리만 거의 나오길래 쉽게 포기했었는데 나중에보니까 제 좁은소견으로는 주로 쓰이는 후킹 기법 몇가지만 알아도 제가 원하는 바를 이룰수가 있는 것 같더라구요.(맞나요?)

    TCP/IP프로그래밍과 MS에서 출판한 Windows Internals를 보려고 하고 있습니다만 Windows Internals를 보는 것이 리버싱에 좀 도움이 될까요? 고맙습니다..

    • reversecore 2011/01/31 12:01 댓글주소 | 수정 | 삭제

      안녕하세요.

      칭찬 감사합니다.

      Windows Internals 요? 리버서에게 그만큼 좋은 책은 없을겁니다. 끝까지 읽기가 너무 힘들어서 문제이지요.

      서점에서 한번 훑어 보신 후에 너무 어렵다고 생각되시면 나중에 실력이 좋아진 후 구입하셔도 될 것 같습니다.

      제 주변에 드라이버 개발자들도 고개를 설레설레 젓는 책입니다. ^^

      감사합니다.

  15. Ch3ongDY 2011/06/27 19:32 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 감사합니다.



hookdbg.cpp 의 DebugLoop() 함수에서 호출하는 세가지 Debug event 핸들러에 대해서 설명드리겠습니다.


<hookdbg.cpp 의 DebugLoop() 함수>

이전 설명은 아래 링크에서 보실 수 있습니다.

API Hooking - 메모장 WriteFile() 후킹 (1)
API Hooking - 메모장 WriteFile() 후킹 (2)



코드 설명


앞서 설명드린 DebugLoop() 함수에서는 세가지 Debug event 를 처리합니다. (위의 코드 그림 참고)

- CREATE_PROCESS_DEBUG_EVENT
- EXIT_PROCESS_DEBUG_EVENT
- EXCEPTION_DEBUG_EVENT

하나씩 살펴보겠습니다.


- EXIT_PROCESS_DEBUG_EVENT

Debuggee 프로세스가 종료될 때 발생하는 이벤트입니다.
위 소스 코드에서는 이 이벤트가 발생하면 Debugger 도 같이 종료하도록 하였습니다.


- CREATE_PROCESS_DEBUG_EVENT -> OnCreateProcessDebugEvent()

CREATE_PROCESS_DEBUG_EVENT 이벤트 핸들러인 OnCreateProcessDebugEvent()를 살펴보겠습니다. 이 함수는 Debuggee 의 프로세스가 시작(혹은 Attach)될 때 호출됩니다.

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
    // WriteFile() API 주소구하기
    g_pfWriteFile = GetProcAddress(GetModuleHandle("kernel32.dll"),
                                   "WriteFile");

    // API Hook - WriteFile()
    //   첫번째 byte 를 0xCC (INT3)로 변경
    //   (orginal byte 는 백업 – g_chOrgByte)
    g_cpdi = pde->u.CreateProcessInfo; // CREATE_PROCESS_DEBUG_INFO

    ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                      &g_chOrgByte, sizeof(BYTE), NULL);

    WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                       &g_chINT3, sizeof(BYTE), NULL);

    return TRUE;
}

먼저 WriteFile() API 의 시작 주소를 구합니다.

주목할 점은 Debuggee 프로세스의 메모리 주소가 아니라 Debugger 프로세스의 메모리 주소를 얻어서 사용한다는 것입니다. Windows OS 에서 System DLL 인 경우 모든 프로세스에서 동일한 주소(가상 메모리)에 로딩 되므로 이렇게 해도 문제없습니다. (참고 : DLL Injection)

g_cpdi 는 CREATE_PROCESS_DEBUG_INFO 구조체 변수입니다.

typedef struct _CREATE_PROCESS_DEBUG_INFO {
  HANDLE                 hFile;
  HANDLE                 hProcess;
  HANDLE                 hThread;

  LPVOID                 lpBaseOfImage;
  DWORD                  dwDebugInfoFileOffset;
  DWORD                  nDebugInfoSize;
  LPVOID                 lpThreadLocalBase;
  LPTHREAD_START_ROUTINE lpStartAddress;
  LPVOID                 lpImageName;
  WORD                   fUnicode;
}CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;

* 출처 : http://msdn.microsoft.com/en-us/library/ms679286(VS.85).aspx

CREATE_PROCESS_DEBUG_INFO 구조체 hProcess 멤버(Debuggee 프로세스 핸들)를 이용하여 WriteFile() API 를 후킹 할 수 있습니다. (Debug Method 가 아니라면 OpenProcess() API 를 통해서 해당 프로세스의 핸들을 얻어야 합니다.)

Debug Method 에서 후킹 방법은 아주 간단합니다.

API 시작 위치에 “BreakPoint 를 설치” 하는 것입니다.

Debuggee 의 프로세스 핸들(Debug 권한을 가짐)을 가지고 있기 때문에 ReadProcessMemory(), WriteProcessmemory() API 를 이용하여 Debuggee 의 프로세스 메모리 공간에 자유롭게 읽기/쓰기 작업을 할 수 있습니다.

위 함수들을 이용해서 Debuggee 에 BreakPoint (INT3 – 0xCC)를 설치할 수 있습니다.

ReadProcessMemory() 를 이용해서 WriteFile() API 의 첫 바이트를 읽어서 g_chOrgByte 변수에 저장합니다. 아래 그림을 보시면 WriteFile() API 의 첫 바이트는 0x6A 입니다.

 
<Fig. 4>

g_chOrgByte 변수에 첫 바이트를 저장하는 이유는 나중에 후킹을 해제(Unhook) 할 때 필요하기 때문입니다.

그 후 WriteProcessMemory() 를 이용해서 이 값을 0xCC 로 바꿔버립니다. (아래 그림 참조)

 
<Fig. 5>

0xCC 는 "INT3" 를 뜻하는 OP code 입니다. 즉, BreakPoint 입니다.

CPU 는 INT3 명령을 만나면 프로그램 실행을 멈추고 예외를 발생시킵니다. 만약 해당 프로그램이 디버깅 중이라면 디버거에게 제어를 넘겨서 처리하도록 합니다.

이것이 일반적으로 디버거에서 BreakPoint 를 설치하는 기본 원리입니다.

이제 Debuggee 프로세스에서 WriteFile() API 가 호출되면 Debugger 에게 제어권이 넘어오게 됩니다.


- EXCEPTION_DEBUG_EVENT -> OnExceptionDebugEvent()

이번에는 EXCEPTION_DEBUG_EVENT 이벤트 핸들러인 OnExceptionDebugEvent()를 살펴보겠습니다. 이 함수가 바로 Debuggee 의 INT3 명령을 처리하게 될 함수입니다.

가장 핵심적인 내용이라 설명을 자세히 해보겠습니다.

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
    CONTEXT ctx;
    PBYTE lpBuffer = NULL;
    DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
    PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

    // BreakPoint exception (INT 3) 인 경우

    if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
    {
        // BP 주소가 WriteFile() API 주소인 경우
        if( g_pfWriteFile == per->ExceptionAddress )
        {
            // #1. Unhook
            //   0xCC 로 덮어쓴 부분을 original byte 로 되돌림
            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                                  &g_chOrgByte, sizeof(BYTE), NULL);

            // #2. Thread Context 구하기

            ctx.ContextFlags = CONTEXT_CONTROL;
            GetThreadContext(g_cpdi.hThread, &ctx);

            // #3. WriteFile() 의 param 2, 3 값 구하기

            //   함수의 파라미터는 해당 프로세스의 스택에 존재함
            //   param 2 : ESP + 0x8
            //   param 3 : ESP + 0xC
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
                              &dwAddrOfBuffer, sizeof(DWORD), NULL);
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
                              &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

            // #4. 임시 버퍼 할당

            lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
            memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

            // #5. WriteFile() 의 버퍼를 임시 버퍼에 복사
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
                              lpBuffer, dwNumOfBytesToWrite, NULL);
            printf("\n### original string : %s\n", lpBuffer);
 
           // #6. 소문자 -> 대문자 변환

            for( i = 0; i < dwNumOfBytesToWrite; i++ )
            {
                if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
                    lpBuffer[i] -= 0x20;
            }

            printf("\n### converted string : %s\n", lpBuffer);

            // #7. 변환된 버퍼를 WriteFile() 버퍼로 복사

            WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
                               lpBuffer, dwNumOfBytesToWrite, NULL);

            // #8. 임시 버퍼 해제

            free(lpBuffer);

            // #9. Thread Context 의 EIP 를 WriteFile() 시작으로 변경

            //   (현재는 WriteFile() + 1 위치 <– INT3 명령 이후)
            ctx.Eip = (DWORD)g_pfWriteFile;
            SetThreadContext(g_cpdi.hThread, &ctx);

            // #10. Debuggee 프로세스를 진행시킴

            ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
            Sleep(0);

            // #11. API Hook

            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                               &g_chINT3, sizeof(BYTE), NULL);

            return TRUE;

        }
    }
    return FALSE;
}

코드 양이 좀 많습니다. 하나씩 설명해 보겠습니다.

처음 if 문에서 EXCEPTION_BREAKPOINT 예외인지 체크합니다. (이 외에도 약 19개의 EXCEPTION 이 더 존재합니다. 참고 : API Hooking - 메모장 WriteFile() 후킹 (1) <list 2>)

그 다음 if 문에서 BreakPoint 가 발생한 주소가 kernel32!WriteFile() 시작 주소와 같은지 체크합니다. (WriteFile() 시작 주소는 OnCreateProcessDebugEvent() 에서 미리 얻어 놓았습니다.)

조건이 만족되면 아래 코드가 실행됩니다.

#1. Unhook

//   0xCC 로 덮어쓴 부분을 original byte 로 되돌림
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                   &g_chOrgByte, sizeof(BYTE), NULL);

먼저 Unhook 을 하는데요, 이유는 소문자->대문자 작업 이후에 WriteFile() 을 정상적인 상태로 호출 시키기 위해서 입니다. ( API Hooking - 메모장 WriteFile() 후킹 (2) 의 "동작 원리 – Unhook & Hook" 설명을 참고하세요.)

Unhook 방법은 Hook 과 마찬가지로 아주 간단합니다. 원래 바이트(g_chOrgByte) 를 써주면 됩니다.

* Unhook 과정이 반드시 필요한 것은 아닙니다. 작업 내용에 따라서 해당 API 호출을 취소할 수 도, 사용자 정의 함수 MyWriteFile() 을 호출할 수 도 있습니다. 상황에 따라서 적절히 변형해서 사용하시기 바랍니다.

#2. Thread Context 구하기

Thread Context 는 제가 블로그에서 처음으로 소개하는 내용인데요, 간단히 설명하면 이렇습니다.
 
모든 프로그램은 프로세스 단위로 실행됩니다. 그리고 프로세스의 실제 명령어 코드는 스레드 단위로 실행됩니다. Windows OS 는 multi-thread 기반이기 때문에 하나의 프로세스에서 여러 스레드가 동시에 실행될 수 있습니다.

멀티 테스킹(multi-tasking)이라는 개념이 결국은 CPU 자원을 시분할(time-slice) 해서 모든 스레드들을 (우선 순위를 고려하여) 하나씩 골고루 실행해 주는 것이지요.

CPU 가 하나의 스레드를 실행하다가 (일정 시간 후) 다른 스레드를 실행하고자 할 때 기존 스레드에서 작업하던 내용을 잘 백업해 두어야 다음 번 실행할 때 제대로 실행할 수 있을 것입니다.

기존 스레드를 실행 하면서 중요한 (다음 실행에 필요한) 정보라면 바로 CPU 레지스터 값입니다. 이 값이 유지되어야 다음 실행에서 정확히 작업을 이어서 할 수 있습니다. (메모리 정보 – 스택 & 힙은 해당 프로세스의 가상 메모리 공간에 있으므로 따로 보호할 필요가 없지요.)

그 스레드의 CPU 레지스터 정보를 저장하는 구조체가 바로 CONTEXT 구조체 입니다. (스레드 하나당 CONTEXT 구조체 하나입니다.)

CONTEXT 구조체 정의를 보겠습니다.

typedef struct _CONTEXT {
    DWORD ContextFlags;

    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;

    FLOATING_SAVE_AREA FloatSave;

    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;

    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;
    DWORD   EFlags;
    DWORD   Esp;
    DWORD   SegSs;

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

* 출처 : MS VC++ winnt.h
 
아래는 스레드의 CONTEXT 를 구하는 코드입니다.

// Thread Context 구하기
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);

위와 같이 GetThreadContext() API 를 호출하면 ctx 구조체 변수에 해당 스레드(g_cpdi.hThread)의 CONTEXT 를 저장합니다. (g_cpdi.hThread 는 Debuggee 의 메인 스레드 핸들입니다.)

BOOL WINAPI GetThreadContext(
    HANDLE hThread,
    LPCONTEXT lpContext
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679362(VS.85).aspx

#3. WriteFile() 의 param 2, 3 값 구하기

WriteFile() 호출 시 넘어온 파라미터 중에서 param 2("쓰기버퍼주소"), param 3(버퍼크기) 를 알아내야 합니다.
함수의 파라미터는 스택에 저장되므로 #2 에서 구한 CONTEXT.Esp 멤버를 이용해서 각각의 값을 구합니다.

// 함수의 파라미터는 해당 프로세스의 스택에 존재함
//   param 2 : ESP + 0x8
//   param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
                  &dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), 
                  &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

* 주의! : dwAddrOfBuffer 에 저장되는 "쓰기버퍼" 주소는 Debuggee(notepad.exe) 의 가상메모리 공간의 주소입니다.

* param 2 와 param 3 가 각각 ESP+0x8, ESP+0xC 인 이유는  Stack Frame 을 참고하시기 바랍니다.

#4 ~ #8 소문자 -> 대문자 변환 후 덮어쓰기

"쓰기버퍼" 주소와 크기를 알았기 때문에 이를 Debugger 메모리 공간으로 읽어 들인 후 [소문자 -> 대문자] 변환합니다. 그리고 다시 원래 위치 (Debuggee 의 가상메모리) 에 덮어 써주는 작업입니다.

어렵지 않은 코드이므로 주석을 보시면 쉽게 이해할 수 있을 겁니다.

// #4. 임시 버퍼 할당
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

// #5. WriteFile() 의 버퍼를 임시 버퍼에 복사
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                  lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string : %s\n", lpBuffer);

// #6. 소문자 -> 대문자 변환
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
    if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
        lpBuffer[i] -= 0x20;
}
printf("\n### converted string : %s\n", lpBuffer);

// #7. 변환된 버퍼를 WriteFile() 버퍼로 복사

WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                   lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 임시 버퍼 해제

free(lpBuffer);

#9. Thread Context 의 EIP 를 WriteFile() 시작으로 변경

위의 #2 에서 구한 CONTEXT 에서 Eip 멤버를 WriteFile() 시작 위치로 변경합니다. (EIP 현재 위치는 WriteFile() + 1 입니다. API Hooking - 메모장 WriteFile() 후킹 (2) "# 실행흐름" 설명 참고)

CONTEXT.Eip 멤버를 변경한 후 SetThreadContext() API 를 호출합니다.

//   (현재는 WriteFile() + 1 위치 <– INT3 명령 이후)
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);

SetThreadContext() API 입니다.

BOOL WINAPI SetThreadContext(
    HANDLE hThread,
    const CONTEXT *lpContext
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms680632(VS.85).aspx

#10. Debuggee 프로세스를 진행시킴

모든 준비는 끝났습니다.
이제는 정상적인 WriteFile() API 를 호출해야 할 때입니다.

ContinueDebugEvent() API 를 호출하여 Debuggee 프로세스의 실행을 재개 시킵니다.
위 #9 에서 CONTEXT.Eip 를 WriteFile() 시작으로 되돌렸으므로 깔끔한 WriteFile() 호출이 진행 됩니다.

ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);

* Sleep(0) 을 해준 이유?
원본 코드 그대로 테스트 해보시고요, Sleep(0) 를 주석 처리한 후 테스트 해보시기 바랍니다. (notepad 에 글을 쓰고 빠르게 반복해서 저장해보세요.)

두 경우 어떤 차이가 있으며, 왜 그런 차이가 발생하는지 생각해 보시기 바랍니다. ^^
이유를 파악하신 분께서는 댓글 남겨주세요~

#11. API Hook

다음 번 후킹을 위하여 다시 API Hook 을 설치합니다.
(이 과정이 생략되면 #1 에서 Unhook 되었기 때문에 WriteFile() API 후킹은 완전히 풀린 상태가 되버립니다.)

WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                   &g_chINT3, sizeof(BYTE), NULL);


여기까지 입니다. ^^
이것으로써 DebugLoop() 함수를 상세하게 살펴봤습니다.

실제로 코드를 디버깅 해가면서 각 구조체에 어떤 값이 들어가는지 확인해 보시기 바랍니다. 몇 번만 디버깅 해보시면 저절로 흐름이 파악되실 것입니다.

수고하셨습니다.


* 참고
Windows XP 이상부터는 DebugSetProcessKillOnExit() 를 호출하여 Debugger 가 종료되는(detach) 순간에 Debuggee 가 종료되지 않게 할 수 있습니다.
이때 조심해야 할 점은 Debugger 가 종료되기 전에 unhook 을 해줘야 합니다.
그렇지 않으면 해당 API 시작 부분의 0xCC 가 남아있기 때문에 API 가 호출 시 EXCEPTION_BREAKPOINT 예외가 발생합니다. 이때는 Debugger 가 없기 때문에 Debuggee 프로세스는 종료됩니다.


+---+

지금까지 Debug Method 을 이용한 API Hooking 에 대해서 공부하였으며, 간단한 예제를 통하여 실습을 해보았습니다.

좀 자세히 설명하기 위해 글이 많이 길어졌습니다. 양해 부탁 드립니다.

다음 번에는 DLL Injection & IAT 변경을 이용한 API Hooking 에 대해서 살펴보도록 하겠습니다.

질문 있으시면 댓글 남겨주세요~


ReverseCore

 

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

  1. 냥냥 2009/11/04 03:10 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다~ ㅎㅎ..
    Sleep() 를 해주는 이유는 동기화가 되지 않아기 때문입니다,
    이것은 반복된 상황이 초래될수 있는것을 의미합니다.
    만약 ContinueDebugEvent(); 를 호출후 NOTEPAD.exe 로 스위칭이 일어나게되면 정상적인 동작을 하게되겠지만, 퀀텀 만큼의 시간은 스레드가 소유할수 있는 최소의 시간이기 때문에, ContinueDebugEvent(); 를 호출후에 곧바로 WriteProcessMemory(); 가 호출된 위험이 존재한다는것을 내포하고 있습니다. 결국 위 함수 2개가 연속으로 실행된다는것은, 또다시 INT 3 이 발생한다는것이고 똑같은 반복작업을 수행하게 된다는것을 의미합니다.
    하지만 Sleep(); 를 쓰게된다면 그 스레드는 즉시 블록킹상태에 들어가게되며 ( 비록 0초라 바로 풀려나오겠지만 ) 이 순간 스위칭이 이루어짐으로서 동기화되지않았기 때문에 생기는 위와 같은 문제는 해결할 수 있습니다.

    가 정답이겠죠? > <//
    항상 좋은 자료 감사드립니다~ ㅎ

    • reversecore 2009/11/04 08:45 댓글주소 | 수정 | 삭제

      냥냥님, 안녕하세요.

      설명을 너무 완벽히 해주셨네요. ^^

      냥냥님께서는 개발 경력이 있는 분 같습니다.
      벌써 구사하시는 용어들에서 개발자의 포쓰가 느껴집니다.
      (사실 더 확실한 증거(?)는 댓글이 달린 시간입니다. ^^)

      감사합니다~

  2. 냥냥 2009/11/04 11:35 댓글주소 | 수정 | 삭제 | 댓글

    아는 잔지식을 총동원했을 뿐이에요 ㅠ_ㅠ.. 전 그저 평범한 학생...
    하하... 다음 강좌가 언제나오나 궁금해서 자꾸 들어와서 그래요..ㅎㅎㅎ;;
    혹시 이번 POC참가하나요..?!

  3. 졸작땜시날새는코더 2009/11/04 14:41 댓글주소 | 수정 | 삭제 | 댓글

    한발늦었네여..
    저가일빠인줄알았더니..

  4. 졸작땜시날새는코더 2009/11/04 14:43 댓글주소 | 수정 | 삭제 | 댓글

    덕분에 어느정도 API후킹부분을 마무리할꺼같습니다.
    dll injection 후 api 후킹이라 dll로 만들어야하는데
    잘되지는 모르겠네여 dll을 이번에 처음접하는거라
    항상감사하고 꾸준히 들리겠습니다.
    신종플루가 유행이던데 조심하시고
    좋은일이 가득하시길 바랍니다 감사합니다^^

  5. 늅늅 2009/11/04 16:30 댓글주소 | 수정 | 삭제 | 댓글

    늘 잘 보고 있습니다~!

    옷..POC 참가하시는가봐요?; 회사가 안보내줘서 못가는데..ㅠ.ㅠ..

    늘 친절한 설명 감사합니다 ^^

    • 냥냥 2009/11/04 17:57 댓글주소 | 수정 | 삭제

      전 학생이라... 가격이 좀 괜찮아서..ㅎㅎㅎ
      처음가보는거라... 경험도 쌓고 하려고요 ㅎㅎ

  6. 몽상가 2009/11/05 10:36 댓글주소 | 수정 | 삭제 | 댓글

    SEH 에 대한 문구에서 안티 디버깅에 대한 내용이 나오는군요. :D
    잘보고있습니다. 수고하세요

    • reversecore 2009/11/05 14:35 댓글주소 | 수정 | 삭제

      몽상가님, 안녕하세요.
      SEH 와 Anti-Debugging 둘 다 흥미로운 주제라서 별도로 포스팅할 예정이구요.

      여러 Anti-Debugging 기법 중에 SEH 를 사용하는 기법이 있는데, 한번 보시면 재미있으실 겁니다.

      감사합니다.

  7. 담배값좀내려 2010/05/05 00:03 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다^^두어번 계속 보니깐 감이 잡히네요 ㅎ
    근데 kernel32.dll에서 지원하는 WriteFile() api 말고 다른 api함수를 후킹하고 싶다면 어떻게 해야 하나요?
    INT3 말고 다른 주소를 지정해야 하나요?

  8. 담배값좀내려 2010/05/05 15:24 댓글주소 | 수정 | 삭제 | 댓글

    위에 글 수정이 안되길래 이어서 달겠습니다.
    파일을 저장할때는 kernel32.dll의 WriteFile() API를 사용하는 것을 보고 파일을 열때가 갑자기 궁금해져서 OllyDbg를 이용해서 확인을 해보니 OpenFile() API가 마침 있더라구요
    근데 OpenFile()를 breakpoint로 실행을 시켜보니 1. 파일을 저장할때도, 2. 파일열기버튼이나 파일열기 단축키눌러 OpenFile다이얼로그가 나올때도, 3. OpenFile다이얼로그에서파일을 선택할때에도 ReadFile() API가 호출이 되버리네요 ㅡㅡ;;

    notepad로 파일을 열때 파일 내용 중간에 개인문자열을 저장할수 있지 않을까 해서 WriteFile과는 다르게 ReadFile은 종료할때의 주소를 swap하면 되지 않을까 해서 계속 해보고 있는데 이렇게 파일을 열어서 불러올때만이 아닌 다른 경우에도 ReadFile() API가 호출될때에는 어떻게 해야 할까요?

    지금 이거 할때가 아닌데 안되는거 계속 붙들고 있는거 보면 저도 IT인으로서 소질이 있나봅니닼ㅋㅋ

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

      안녕하세요.

      제가 질문하신 내용을 잘 이해하지 못하고 있습니다. @@

      원하시는 작업이 기존에 있는 파일을 notepad 로 열때 그 내용의 중간에 임의의 문자열을 추가하고 싶으시다는 것인지요?

      죄송하지만 원하시는 작업을 간략히 설명해 주실 수 있으신가요?

  9. 담배값좀내려 2010/05/05 22:36 댓글주소 | 수정 | 삭제 | 댓글

    제가 말이 너무 길었나보네요 ㅎ
    운영자분께서는 kernel32.dll ! WriteFile()에 Debug 후킹을 가하셨잖아요?
    간단히 말해서 전 반대로 ReadFile()을 Debug 후킹을 할 수 있지 않을까 해서 시도해보고 있습니다.

    파일을 읽어올때마다 맨위에 이름을 단다던지 맨뒤에 파일명을 저장한다던지 하는 식으로요 ㅎㅎ
    근데 위에서 말한것처럼 OllyDBug를 통해서 확인을 해보니 WriteFile과는 다르게 OpenFile은
    파일을 불러오기 할때만 호출되는게 아니라 저장할때도 호출되고 그러더라구요;;

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

      안녕하세요.

      원하시는 작업은 바로 notepad 에서 파일을 읽어들일때 ReadFile() API 등을 후킹하여 읽기 버퍼의 내용을 적절히 가공하고 싶으신 거 맞으시죠?

      중간에 OpenFile() API 를 써주셔서 제가 잠시 헷갈렸습니다.
      * 참고로 OpenFile() 은 이미 예젅에 CreateFile() 로 대체되었지요. MS 에서 하위 호환을 위해서 남겨둔 것이지요.

      문제는 ReadFile() 을 후킹하고 싶지만 이 API 가 생각보다 자주 호출 된다는 것이구요? 이렇게 많은 호출중에서 내가 원하는 호출을 어떻게 구별하느냐가 문제의 핵심이 되겠지요?

      네, 말씀하신대로 ReadFile() 은 시스템 DLL 에서 자주 사용됩니다. 원하는 내용을 구현하시려면 먼저 스택의 리턴 주소를 보고 이번의 ReadFile() 호출이 어디서 호출되었는지 확인하는 방법도 있구요. -> notepad.exe 의 .text 섹션에서 호출된 것만 관심을 가지면 됩니다.

      더 편한 방법은 kernel32!CreateFileW() 를 먼저 후킹하신 다음에 파라미터를 잘 살펴보시면서 특정 텍스트 파일이름을 읽기 모드로 열 때를 파악하신 후 그다음 ReadFile() 호출을 유효한 호출로써 간주하고 읽기버퍼를 신경쓰시면 되겠네요. 파일 크기에 따라서 ReadFile() 이 여러번 호출될 수 있습니다.

      질문에 비해 설명이 너무 장황하네요. 죄송합니다.
      설명 능력이 부족합니다. 백문이 불여일견이라는 말을 정말 백배 공감합니다.

      다른 궁금한점 있으시면 다시 올려주세요.

      감사합니다.

  10. 담배값좀내려 2010/07/26 22:09 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요^^ 몇가지 질문사항이 있어서 다시 댓글을 달아 봅니다.
    디버깅과 리버싱에 관한 지식이 부족하여 여러 책자나 관리자분의 디버깅쪽
    강좌 등 을 보며 조금씩 공부를 해나가는 중입니다 ^^

    위에서 말씀하신대로 ReadFile을 분석하던 중 ReadFile의 내부적으로 ntdll.dll의 ntReadFile을
    통해 메모리로 파일의 내용을 읽어오더군요. 그런데 Drag&Drop을 통해서 파일을 열었을대에는
    ReadFile이 아예 호출되지도 않더라구요....

    그래서 찾아낸 것이 CreateFile인데 파일을 열때나 Drag&Drop을 통해서 파일을 열때의 전달인자를 보니
    세번째전달인자 = FILE_SHARE_READ | FILE_SHARE_WRITE,
    여섯번째전달인자 = FILE_ATTRIBUTE_NORMAL,
    일곱번째전달인자 = NULL,
    일 때에는 파일을 열때더군요....그런데 그 후로 파일의 내용을 메모리로 읽어들인다음
    메모장을 통해 화면으로 뿌려주는 부분을 찾기가 굉장히 어렵네요 ㅎㅎ;;

    이런 경우엔 파일을 열때에 파일의 내용이 메모리로 올라오는 경우는 대체 언제쯤일까요...
    리버싱경험이 전무하다보니 혼자서 알아내기가 생각보다 쉽지 않네요;;
    혹 ReadFile외에 메모장의 Edit부분에 파일의 내용을 표시할수 있는 TextOut이나 SetWindowText같은 다른 API가 없을까요?
    (TextOut나 SetWindowText는 안되더라구요 ㅎㅎ;)

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

      안녕하세요.

      메모장에서 파일을 열때 CreateFile() -> CreateFileMapping() -> MapViewOfFile() API 를 사용합니다. 즉 메모리 맵 파일 기법을 사용하지요. 그래서 ReadFile() 이 호출되지 않고도 내용을 볼 수 있던 것입니다. 참고하시기 바랍니다.

      감사합니다.

  11. 2011/04/29 15:15 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/05/03 00:26 댓글주소 | 수정 | 삭제

      안녕하세요.

      그림 한 두개 정도가 아니라 전부를 가져가시는 것은 안되구요.

      그냥 필요하실 때 방문해 주시면 안될까요? ^^

      감사합니다.

  12. lunaticapple 2011/07/06 16:03 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요? 윈도우 프로그래밍을 처음해봅니다.^^ 제가 위방법으로 readfile함수에 대해
    break를 걸어보았습니다. winamp에서 음악을 플레이할때 readfile을 후킹해서 좀 가져올
    정보가 있어서요^^. 현재 OnExceptionDebugEvent 함수부분은 별도로 아무것도 한거 없이
    print문하나찍고 unhook, 프로세스 재개, hook 의 방식으로 되어있습니다. 그런데 이게,,
    print문 두번정도 찍고(readfile 후킹 두번) 더이상 반응을 하지 않습니다. winamp가 죽은것 같기도 한데 실제 프린트를 찍어보면 OnExceptionDebugEvent함수는 계속 탑니다. 그런데 readfile은 더이상 불리지도 않고 winamp는 소리를 내지 않습니다,, .. 어떻게 디버깅을 해야할지
    몰라서,, 혹시 짐작가시는게 있으시면 조언 부탁드리겠습니다.

    • reversecore 2011/07/14 05:53 댓글주소 | 수정 | 삭제

      안녕하세요.

      위 실습 예제 코드는 동작 원리를 소개하기 위한 목적이기 때문에 코드 가독성을 위해서 예외처리가 빠져 있습니다.

      의심되는 부분이 몇 군데 있기는 한데 비슷한 환경에서 디버깅을 해봐야 정확한 원인을 파악할 수 있을 것 같습니다.

      제가 직접 해보고 다시 댓글 달아드릴께요.

      감사합니다.

  13. LeapOfFaith 2011/07/11 16:43 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요
    조용히 맨날 보면서 따라하다가 막히는 부분이 있어서 여쭤봅니다.
    <fig.4>와 <fig.5>부분인데요.
    notepad에 hookdbg.exe가 붙어있는 상태에서 올리디버거로 notepad의 저기 저 7C7E0E27부분이 바뀌는 것을 확인하려고 했습니다만, 올리디버거가 notepad에 붙질 못하네요.
    아마 hookdbg.exe가 디버거로 이미 notepad에 붙어있어서 올리가 못붙는거 같은데..
    (마찬가지로 올리를 notepad에 먼저 붙인 후, hookdbg.exe를 notepad에 붙이려 시도하면 hookdbg.exe는 DebugActiveProcess<notepad의 PID> failed!!!
    Error Code = 87
    을 출력하고 붙질 못합니다.
    검색해보니 GetLastError() 리턴 값 87은 ERROR_INVALID_PARAMETER라는 군요.)
    어떻게 <fig.4>와 <fig.5>처럼 올리로 지켜보셨는지 궁금합니다..ㅠㅠ

    • reversecore 2011/07/13 21:23 댓글주소 | 수정 | 삭제

      안녕하세요.

      말씀하신 것처럼 하나의 프로세스를 동시에 2개의 디버거로 디버깅을 할 수 없습니다.

      문의하신 <fig. 4> 와 <fig. 5> 는 제가 동작원리를 보여드리기 위해서 OllyDbg 로 notepad 를 디버깅해서 캡쳐한 것입니다. 관련된 설명이 부족해서 착오가 있으셨던것 같습니다.

      감사합니다.

  14. polaris 2011/11/10 00:25 댓글주소 | 수정 | 삭제 | 댓글

    아주 재미나게 잘 보앗습니다. 정말 이런 정보를 공유해 주셔서 감사합니다.
    그런데 한가지 문제가 있어서 이렇게 글을 올립니다.
    Windows7에서 실험해봣는데요 XP에서 할때하고 좀 다른게 있더라구요...
    XP에서는 아무런 문제없이 제대로 되던데 Windows7에서는 Debugger를 실행시킬때 Notepad가 잠시 멈추고 보관할때도 파일보관대화창이 제때에 반응하지 안더니 확인단추를 누르면 아주 응답이 없어집니다.
    Windows7에서는 무엇이 좀 다른거 같은데 여기에 대해서 어떻게 생각하시는지...
    조언부탁드립니다.

  15. 익명 2012/01/21 19:21 댓글주소 | 수정 | 삭제 | 댓글

    thread context에 대해 윗글 링크 따라가 보니 다음과 같이 적혀있더군요
    Protected Processes
    Protected processes enhance support for Digital Rights Management. The system restricts access to protected processes and the threads of protected processes.

    Windows Server 2003 and Windows XP/2000: Protected processes were added starting with Windows Vista.
    The following specific access rights are not allowed from a process to the threads of a protected process:

    THREAD_ALL_ACCESS
    THREAD_DIRECT_IMPERSONATION
    THREAD_GET_CONTEXT
    THREAD_IMPERSONATE
    THREAD_QUERY_INFORMATION
    THREAD_SET_CONTEXT
    THREAD_SET_INFORMATION
    THREAD_SET_TOKEN
    THREAD_TERMINATE
    The THREAD_QUERY_LIMITED_INFORMATION right was introduced to provide access to a subset of the information available through THREAD_QUERY_INFORMATION.

    출처: http://msdn.microsoft.com/en-us/library/ms686769(v=vs.85).aspx



Debug 방식의 API Hooking 실습 예제 코드(hookdbg.cpp)의 동작 원리를 살펴 보고 코드를 자세히 분석해보겠습니다.


<hookdbg.cpp 의 DebugLoop() 함수>

작업 목표 : "notepad 에서 파일 저장할 때 모든 소문자를 대문자로 변경함"

* 참고
API Hooking - 메모장 WriteFile() 후킹 (1)



동작 원리



이해를 돕기 위해 먼저 동작 원리를 설명 드리겠습니다.

notepad 에서 뭔가를 파일에 저장하려면 kernel32!WriteFile() API 를 사용할 거라고 가정합니다. 
(일단 가정이 맞는지 확인을 해봐야겠군요.)


# 스택(Stack)

WriteFile() API 정의를 봐주세요.

BOOL WriteFile(    
    HANDLE hFile,
    LPCVOID lpBuffer, 
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
);

* 출처 : http://msdn.microsoft.com/en-us/library/aa365747(VS.85).aspx

두 번째 파라미터(lpBuffer)가 “쓰기 버퍼” 이고, 세 번째 파라미터(nNuberOfBytesToWrite)가 “써야 할 크기” 입니다. 함수의 파라미터는 스택에 역순으로 저장된다는 사실을 기억해주세요. (-> 참고 : Stack Frame)

OllyDbg 를 이용해 실제로 notepad 를 디버깅 하면서 확인해 보겠습니다.

<Fig. 1>

위 그림처럼 OllyDbg 를 이용해서 notepad 를 열어서 kernel32!WriteFile() API 에 BreakPoint 를 설치한 후 실행[F9] 시킵니다.

그리고 테스트로 아래와 같이 적당한 문자열을 입력한 후 적당한 파일 이름으로 저장합니다.

<Fig. 2>

예상대로 BreakPoint 를 설치한 kernel32!WriteFile() 에 멈춥니다.

이 상태에서 스택을 살펴보겠습니다.


<Fig. 3>

현재 스택(ESP : 7FA7C)에는 리턴 주소(01004C30)가 있고, ESP+8 (7FA84) 에 "쓰기버퍼" 주소(0E7310)가 저장되어 있습니다. 바로 그 "쓰기버퍼" 주소(0E7310)로 가면 notepad 에서 저장하려고 하는 문자열("ReverseCore") 이 보입니다.

따라서 WriteFile() API 를 후킹해서 "쓰기버퍼"를 제가 원하는 문자열로 덮어쓰면 목표 달성입니다.


# 실행흐름

이제 Debuggee 의 프로세스 메모리 어느 부분을 수정해야 하는지 알았습니다.
그 다음은 WriteFile() 을 정상적으로 실행시켜서 제가 수정한 문자열이 파일에 저장되도록 하면 됩니다.

지금 우린 Debug Method 를 사용해서 API 후킹을 하고 있습니다.

이전 포스트에서 소개한 hookdbg.exe 를 이용하여 WriteFile() API 시작 주소에 BP (INT3) 를 설치하면, Debuggee(notepad.exe) 에서 파일을 저장 할 때 Debugger(hookdbg.exe) 에게 EXCEPTION_BREAKPOINT 이벤트가 올 것입니다.

그렇다면 그 순간 Debuggee(notepad.exe) 의 EIP 값은 얼마일까요?

얼핏 생각하면 WriteFile() API 시작주소(7C7E0E27) 라고 생각하기 쉽습니다.
하지만 실제 EIP 는 WriteFile() API 시작주소(7C7E0E27) + 1 = 7C7E0E28 입니다.

그 이유는 이렇습니다.

먼저 BP 를 WriteFile() API 시작 주소에 설치하였지요?
Debuggee(notepad.exe) 내부에서 WriteFile() 이 호출되면 시작주소인 7C7E0E27 에 있는 INT3 (0xCC) 명령어를 만나게 됩니다.
이 명령어(BreakPoint – INT3)를 실행 하면 EIP 는 INT3 명령어 길이(1 byte)만큼 늘어나게 됩니다.
그 후에 제어가 Debugger(hookdbg.exe) 로 넘어오게 되는 것입니다. (Debugger - Debuggee 관계에서 Debuggee 에서 발생한 EXCEPTION_BREAKPOINT 예외는 Debugger 에서 처리하도록 되어 있기 때문입니다.)

따라서 "쓰기 버퍼" 에 있는 내용을 수정해서 덮어쓴 다음에 EIP 를 WriteFile() API 시작 주소로 되돌려서 실행 시켜야 합니다.


# Unhook & Hook

또 하나의 문제는 단순히 실행 흐름을 WriteFile() 시작 주소로 되돌리기만 해서는 똑 같은 INT3 명령을 만나게 되기 때문에 무한루프(EXCEPTION_BREAKPOINT 발생)에 빠지게 됩니다.

이러한 무한루프에 빠지지 않으려면 WriteFile() API 시작 주소에 설치한 BP 를 제거해야 합니다.
즉, 0xCC 를 원래 original byte 인 0x6A 로 변경해줘야 합니다. (original byte 는 API 후킹 전에 미리 저장해 둡니다.)

이것을 Unhook 이라고 합니다. API 후킹을 풀어버리는 것이지요.

"쓰기버퍼" 를 덮어쓰고 WriteFile() API 코드를 정상으로 되돌린 후 EIP 값을 WriteFile() API 로 변경하면 드디어 변경된 문자열이 파일에 저장됩니다. 이것이 hookdbg.cpp 의 동작 원리입니다.

후킹이 1 회성이면 여기서 끝이고요, 지속적인 후킹을 원하시면 다시 BP 를 설치합니다.


설명만 읽어서는 잘 이해되지 않을 수 있습니다.
아래 소스 코드(hookdbg.cpp)를 보면서 설명 드리겠습니다.

* 참고
OllyDbg 같이 범용적인 디버거의 경우는 <Fig. 3> 에서 보듯이 EIP 값이 BP 설치 주소와 같고, INT3(0xCC) 명령어가 보이지 않습니다. 이것은 편리한 사용자 인터페이스를 위하여 OllyDbg 에서 제공하는 기능입니다.

즉, INT3(0xCC) 를 덮어 쓴 후 이 명령어를 실행하게 되면 EIP 가 1 증가합니다. 그때 OllyDbg 에서 0xCC 를 원래 byte 로 복원하고 EIP 도 보정해 주는 것이지요. (결과적으로 구현 알고리즘은 위 설명과 동일합니다.)




코드 설명




첨부된 hookdbg.cpp 의 코드를 살펴보도록 하겠습니다.


# main()

#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

int main(int argc, char* argv[])
{
    DWORD dwPID;

    if( argc != 2 )
    {
        printf("\nUSAGE : hookdbg.exe <pid>\n");
        return 1;
    }

    // Attach Process
    dwPID = atoi(argv[1]);
    if( !DebugActiveProcess(dwPID) )
    {
        printf("DebugActiveProcess(%d) failed!!!\n"
              "Error Code = %d\n", dwPID, GetLastError());
        return 1;
    }

    // 디버거 루프
    DebugLoop();

    return 0;
}


main() 함수의 코드는 간단합니다.

프로그램 실행 파라미터로 API 후킹 하려는 프로세스의 PID 를 받습니다.

그 후 DebugActiveProcess() API 를 통해서 실행중인 프로세스에 attach 하여 디버깅을 시작합니다. (위에서 입력한 PID 를 파라미터로 넘겨줍니다.)

BOOL WINAPI DebugActiveProcess(
    DWORD dwProcessId
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx


그 후 DebugLoop() 함수로 들어가서 Debuggee 로부터 오는 Debug event 를 처리합니다.

* 또 다른 디버깅 시작 방법은 CreateProcess() API 를 사용하여 아예 해당 프로세스를 Debug 모드로 실행시키는 방법이 있습니다. 이와 관련된 설명은 MSDN 을 참고하세요.



# DebugLoop()

void DebugLoop()
{
    DEBUG_EVENT de;
    DWORD dwContinueStatus;

    // Debuggee 로부터 event 가 발생할 때까지 기다림
    while( WaitForDebugEvent(&de, INFINITE) )
    {
        dwContinueStatus = DBG_CONTINUE;

        // Debuggee 프로세스 생성 혹은 attach 이벤트
        if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            OnCreateProcessDebugEvent(&de);
        }
        // 예외 이벤트
        else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
        {
            if( OnExceptionDebugEvent(&de) )
                continue;
        }
        // Debuggee 프로세스 종료 이벤트
        else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            // Debuggee 종료 -> Debugger 종료
            break;
        }

        // Debuggee 의 실행을 재개시킴
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
}

DebugLoop() 함수는 마치 윈도우 프로시저 함수(WndProc)와 유사하게 동작합니다.
Debuggee 로부터 발생하는 event 를 받아서 처리한 후 Debuggee 의 실행을 재개 시키는 역할입니다.

역시 간단한 코드이므로 주석을 보시면 쉽게 이해하실 수 있으실 것입니다.

그 중에서 몇 가지 중요 API 들을 알아보겠습니다.

WaitForDebugEvent() API 는 이름 그대로 Debuggee 로부터 Debug event 가 발생할 때까지 기다리는 함수입니다. (WaitForSingleObject() API 와 비슷하게 동작합니다.)

BOOL WINAPI WaitForDebugEvent(
    LPDEBUG_EVENT lpDebugEvent,
    DWORD dwMilliseconds
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx

Debug event 가 발생하면 WaitForDebugEvent() API 는 첫 번째 파라미터인 de 변수(DEBUG_EVENT 구조체 객체)에 해당 event 에 대한 정보를 설정한 후 즉시 리턴합니다.
DEBUG_EVENT 구조체 정의는 아래와 같습니다.

typedef struct _DEBUG_EVENT {
    DWORD dwDebugEventCode;
    DWORD dwProcessId;
    DWORD dwThreadId;
    union {
        EXCEPTION_DEBUG_INFO       Exception;
        CREATE_THREAD_DEBUG_INFO   CreateThread;
        CREATE_PROCESS_DEBUG_INFO  CreateProcessInfo;
        EXIT_THREAD_DEBUG_INFO     ExitThread;
        EXIT_PROCESS_DEBUG_INFO    ExitProcess;
        LOAD_DLL_DEBUG_INFO        LoadDll;
        UNLOAD_DLL_DEBUG_INFO      UnloadDll;
        OUTPUT_DEBUG_STRING_INFO   DebugString;
        RIP_INFO                   RipInfo;
    } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

* 출처 : http://msdn.microsoft.com/en-us/library/ms679308(VS.85).aspx

이전 포스트에서 Debug event 는 9 가지 종류가 있다고 설명 드렸습니다.
(참고 : API Hooking - 메모장 WriteFile() 후킹 (1) <list 1>)

DEBUG_EVENT.dwDebugEventCode 멤버에 9가지 event 종류 중 하나가 세팅되며, 해당 event 종류에 따라 적절한 DEBUG_EVENT.u (유니온) 멤버가 세팅 됩니다. (DEBUG_EVENT.u 유니온 멤버 역시 event 종류 개수에 맞춰서 내부에 9 개의 구조체로 구성되어 있습니다.)

예) Exception event 인 경우 => dwDebugEventCode 멤버가 EXCEPTION_DEBUG_EVENT 로 세팅 되고, u.Exception 구조체가 세팅됩니다.

ContinueDebugEvent() API 는 Debuggee 의 실행을 재개 시키는 함수입니다.

BOOL WINAPI ContinueDebugEvent(
    DWORD dwProcessId,
    DWORD dwThreadId,
    DWORD dwContinueStatus
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679285(VS.85).aspx

ContinueDebugEvent() API 의 마지막 파라미터인 dwContinueStatus 는 DBG_CONTINUE 또는 DBG_EXCEPTION_NOT_HANDLED 중에서 하나의 값을 가질 수 있습니다.

정상적으로 처리된 경우 DBG_CONTINUE 로 세팅 하고, 처리하지 못했거나 어플리케이션의 SEH(Structured Exception Handler) 에서 처리하길 원할 때는 DBG_EXCEPTION_NOT_HANDLED 로 세팅합니다.

* SEH(Structured Exception Handler) 는 Windows 에서 제공하는 예외 처리 메커니즘입니다. 이를 이용한 예외 처리와 안티 디버깅 기법들에 대해서는 향후 따로 다루어 보도록 하겠습니다.

위의 DebugLoop() 에서는 3 가지의 Debug event 만 처리합니다. (CREATE_PROCESS_DEBUG_EVENT, EXIT_PROCESS_DEBUG_EVENT, EXCEPTION_DEBUG_EVENT)


다음 포스트에서 위 Debug event 에 대한 상세한 코드 설명이 이어집니다.

API Hooking - 메모장 WriteFile() 후킹 (3)


+---+

ReverseCore

 

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

  1. alex 2009/11/12 13:20 댓글주소 | 수정 | 삭제 | 댓글

    좋은 글 잘 보고 갑니다!

  2. reversingk 2011/04/27 17:08 댓글주소 | 수정 | 삭제 | 댓글

    항상 감사 합니다.
    코어님 덕에 리버싱 공부하는 것이 즐겁습니다 ^^



앞서 소개해 드린 각 기법들 중에서 Debug 기법에 대한 설명입니다.

실습으로 메모장의 kernel32!WriteFile() API 를 후킹하여 기존과는 다른 동작을 하도록 만들어 보겠습니다.


<실습 예제 – WriteFile() API hooking>

API Hooking 의 기본 설명은 아래 글을 참고하세요.

API Hooking - 리버싱의 '꽃'
API Hooking - Tech Map



Debug Technique




<Fig. 1>

Debug 방식의 API 후킹을 설명 드리겠습니다. (위의 Tech Map 에서 빨간색 표시 부분을 참고하세요)

이 방식의 장점은 후킹을 위해서 '디버깅'을 사용하므로 좀 더 interactive 한 후킹을 수행할 수 있습니다. 즉, 간단한 GUI 를 제공하여 후킹 대상 프로그램의 실행을 제어하고, 메모리를 자유롭게 사용할 수 있습니다.

하지만 먼저 Debugger 구조에 대한 이해가 필요합니다.



Debugger 설명


# 용어

간단한 용어 정리부터 하겠습니다.

Debugger – debugging tool
Debuggee – application to debug

# 디버거 기능

Debugger 의 기능은 Debuggee 가 올바르게 실행되는지 확인하고 (예상치 못한) 프로그램의 오류를 발견하는 것입니다.

Debugger 는 Debuggee 의 명령어 instruction 을 하나씩 실행 가능하며, 레지스터와 메모리에 대한 모든 접근 권한을 가집니다.

# 디버거 동작 원리

일단 Debugger 프로세스로 등록되면 OS 는 Debuggee 에게 debug event가 발생할 때 Debuggee 의 실행을 멈추고 해당 event 를 Debugger 에게 통보합니다. Debugger 는 해당 event 에 대해 적절한 처리를 한 후 Debuggee 의 실행을 재개합니다.

* 일반적인 예외(Exception)도 Debug event 에 해당합니다.
* 만약 해당 프로세스가 디버깅 중이 아니었다면 debug event 는 자체 예외처리 아니면 OS 의 예외처리 루틴에서 처리됩니다.
* Debugger 는 debug event 중에서 처리할 수 없거나 관심 없는 event 들은 OS 가 처리하도록 만들어 줍니다.

아래 그림은 위 설명을 도식화 한 것입니다.


<Fig. 2>

# Debug event

Debug event 입니다.

EXCEPTION_DEBUG_EVENT     
CREATE_THREAD_DEBUG_EVENT  
CREATE_PROCESS_DEBUG_EVENT 
EXIT_THREAD_DEBUG_EVENT     
EXIT_PROCESS_DEBUG_EVENT   
LOAD_DLL_DEBUG_EVENT       
UNLOAD_DLL_DEBUG_EVENT     
OUTPUT_DEBUG_STRING_EVENT  
RIP_EVENT

* 출처 : http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx

<List 1>

위 Debug event 중에서 Debugging 관련된 event 는 아래에 표시된 EXCEPTION_DEBUG_EVENT 입니다.

EXCEPTION_ACCESS_VIOLATION
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_BREAKPOINT
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
EXCEPTION_FLT_INEXACT_RESULT
EXCEPTION_FLT_INVALID_OPERATION
EXCEPTION_FLT_OVERFLOW
EXCEPTION_FLT_STACK_CHECK
EXCEPTION_FLT_UNDERFLOW
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_IN_PAGE_ERROR
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_NONCONTINUABLE_EXCEPTION
EXCEPTION_PRIV_INSTRUCTION
EXCEPTION_SINGLE_STEP
EXCEPTION_STACK_OVERFLOW

* 출처 : http://msdn.microsoft.com/en-us/library/aa363082(VS.85).aspx

<List 2>

각종 예외(Exception) 중에서 Debugger 가 반드시 처리해야 하는 예외는 바로 EXCEPTION_BREAKPOINT 예외입니다.

BreakPoint 는 어셈블리 명령어 "INT3" 이며, IA-32 Op code 는 0xCC 입니다. 코드 디버깅 중에 INT3 명령어를 만나면 Debugger 에게 EXCEPTION_BREAKPOINT 예외 이벤트가 날아갑니다.

Debugger 에서 BreakPoint 를 구현하는 방법은 간단합니다.

BreakPoint 를 설치하기 원하는 코드의 메모리 시작 주소에서 1 byte 를 0xCC 로 바꿔 치는 것입니다. 디버깅을 계속 진행하고 싶을 때는 다시 원래 값으로 복원시키고 실행해줍니다.

Debug 방식의 API Hooking 은 이와 같은 BreakPoint 의 특성을 이용하는 것입니다.



API Hooking - Debug Method


Debug 기법을 통한 API Hooking 에 대해 좀 더 자세히 설명 드리겠습니다.

기본적인 아이디어는 Debugger-Debuggee 관계를 가진 상태에서 Debuggee 의 API 시작 부분을 0xCC 로 바꿔 제어를 Debugger 로 가져온 상태에서 원하는 작업을 수행한 후 Debuggee 를 다시 실행상태로 바꾸는 것입니다.

작업 흐름은 아래와 같습니다.

- 후킹을 원하는 프로세스에 ‘attach’ 하여 Debuggee 로 만듦
- Hook : API 시작 주소의 첫 바이트를 0xCC 로 변경
- 해당 API 가 호출되면 제어는 Debugger 에게 넘어옴
- 원하는 작업을 수행(파라미터, 리턴 값 조작 등)
- Unhook : Debuggee 의 0xCC 를 원래대로 복원시킴 (<– API 의 정상 실행을 위해)
- 해당 API 실행 (0xCC 가 빠진 정상적인 상태)
- Hook : 다시 0xCC 로 바꿈 (<– 지속적인 후킹을 위해)
- Debuggee 에게 제어를 되돌려줌


위 방식은 가장 간단한 경우를 소개한 것입니다.

이걸 기준으로 다양하게 변형할 수 있습니다. 가령 original API 를 호출 하지 않을 수 도 있고, 사용자가 제공한 custom API 를 호출 할 수 도 있고, 한번만 후킹 할 수도 있고, 여러 번 후킹 할 수도 있습니다.

작업 목적에 따라서 알맞게 변형해서 사용하시면 됩니다.



Notepad.exe 의 WriteFile() API Hooking


지금 까지 공부한 내용을 바탕으로 실제 코드를 보면서 실습을 해보도록 하겠습니다.

작업할 내용은 Notepad.exe 의 WriteFile() API 후킹입니다.
입력된 파라미터를 조작하여 소문자로 입력된 내용을 전부 대문자로 바꿔 보겠습니다.

즉, Notepad 에서 입력된 모든 소문자는 파일로 저장되는 순간에 대문자로 변경되어 저장됩니다.

Notepad.exe 를 실행시킨 후 PID 를 알아냅니다.


<Fig. 3>

첨부된 후킹 프로그램(hookdbg.exe)을 실행합니다.

hookdbg.exe 는 콘솔 기반 프로그램이며 실행 파라미터로 후킹 할 프로세스의 PID 를 넘겨 받습니다.



<Fig. 4>

위 그림과 같이 hookdbg.exe 를 실행하면 PID 1688 에 해당하는 notepad 프로세스의 WriteFile() API 후킹이 시작됩니다.

그리고 notepad 에 아무 글자나 입력해 보세요.


<Fig. 5>

입력을 마치셨으면 저장을 해주세요.


<Fig. 6>

저장을 마치면 notepad 화면에는 아무런 변화가 일어나지 않습니다. (WriteFile() API 만 후킹 했다는 사실을 기억해 주세요.)

notepad 를 종료해 주시고, hookdbg 프로그램을 봐주세요.


<Fig. 7>

위 그림을 보시면 “original string” 에는 제가 입력한 문자열이 나타나고, “converted string” 에는 변경된(소문자 -> 대문자) 문자열이 나타납니다.

이것은 hookdbg.exe 프로그램 내부에서 후킹의 진행과정을 표시하기 위해서 출력하는 문자열입니다.

실제로 대문자로 저장되었는지 파일을 열어서 확인합니다.


<Fig. 8>

정확히 모든 소문자가 대문자로 변경되어서 저장되었습니다.

이 예제는 아주 간단한 기능을 가지고 있지만 Debug Method 에 대한 기본적인 개념을 잘 설명해줄 수 있습니다.

다음 포스트에서 hookdbg.exe 의 실제 코드를 상세히 살펴보도록 하겠습니다.

API Hooking - 메모장 WriteFile() 후킹 (2)


+---+

ReverseCore

 

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

  1. 늅늅 2009/10/28 13:58 댓글주소 | 수정 | 삭제 | 댓글

    언제나 친절하게 설명 되어있는 강좌 잘 보고 있습니다 ^^

  2. 냥냥 2009/10/28 23:59 댓글주소 | 수정 | 삭제 | 댓글

    와... 정말 많은걸 보고 배워가요 ^^*
    좋은글 앞으로도.. 많이 많이 very 많이 많이 부탁드릴께요..ㅎㅎ
    댓글중에 회사원이라고 들었는데요... 하는 일이 무엇인가요..?
    전 학생이라... IT업계의 일에는 무엇이 있는지 궁금해서요... 프라이버시라면... 말 안해주셔도되요~
    히힛~..~ 양질의 좋은 자료 부탁드리겠습니다~ ㅎㅎ.. 근데 퍼가도 되는건가요..?!

    • ReverseCore 2009/10/29 06:14 댓글주소 | 수정 | 삭제

      냥냥님, 안녕하세요.

      도움이 되셨다니 기쁘네요~ ^^

      그냥 평범한 IT 관련 회사원이구요, 개발과 리버싱에 많은 관심을 가지고 있지요.

      퍼가지는 마시구요, 링크만 해주세요~
      (미리 물어봐 주셔서 감사합니다.)

  3. 이승철 2009/10/29 11:07 댓글주소 | 수정 | 삭제 | 댓글

    언제나 잘 보고 있습니다. 감사합니다.

  4. 김형근 2009/10/29 18:05 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 감사합니다.
    궁금한것이 더 많아지네여 ^^
    좋은글 앞으로도 부탁드립니다.

  5. 몽상가 2009/10/30 03:19 댓글주소 | 수정 | 삭제 | 댓글

    저도 요즘 공부 차원에서 다시 보고 있습니다. ^^
    목표는 모 프로그램의 크랙입니다. 물론 나쁜 용도는 아니고, 최소한 내가 의도한 정도까지? 지만, 욕심이 크다보니 할 수 있는데 까지 해보는게 목표입니다.

    근데 일단 가장 기본적인 디버거를 붙이면 강제 종료되는데, 아마 이게 다른 쓰레드에서 디버그가 붙으면 강제종료를 하는 스레드가 있나 봅니다. 이럴땐 어떤 방식으로 해야되는가 궁금합니다.
    아예 실행하고 붙이는게 아니라 실행전에 수정을 해야되는건가요?

    강좌가 계속 나와서 이런 방식까지 나왔으면 좋겠습니다.
    제 블로그 링크도 해주시고 감사합니다. RSS 로 꾸준히 보고있어요 :D

    • reversecore 2009/10/30 07:19 댓글주소 | 수정 | 삭제

      몽상가님, 안녕하세요.

      디버깅 당했다고 판단되면 스스로 종료 <- 안티 디버깅 기법을 사용한 듯 하군요.

      계획에는 일반적인 안티디버깅에 대한 강좌를 하고, 특별한 안티기법은 파일을 분석하는 도중에 따로 정리해보려고 합니다.

      말씀하신 내용도 참고할께요~ (attach 할 수 없는 프로세스)

      감사합니다.

  6. 졸작땜시날새는코더 2009/10/30 13:51 댓글주소 | 수정 | 삭제 | 댓글

    글잘보았습니다 참고많이되었구요
    혹시
    특정 이벤트처리가아니라
    그 해당 프로그램이 사용하는 API함수들을 모두 체크할수있는 방법이없을까요
    변조하는게 아닌 그저 무슨 API함수를 사용했다 정도로만요
    대충 이미지는 나오는데 어떻게해야할지 막막합니다 ㅠㅠ

    • ReverseCore 2009/10/31 12:30 댓글주소 | 수정 | 삭제

      안녕하세요.

      프로그램이 사용하는 모든 API 함수를 확인하고 싶으시다는 말씀이시죠?

      IDAPro, OllyDbg 에서 그런 기능을 지원하는데요, 그 원리는 코드의 CALL, JMP 명령어을 전부 파싱하는 것입니다.

      혹시 이런류의 프로그램이나 OllyDbg 플러그인등이 존재할 수도 있겠네요.

      참고로 예전에 제가 했던 비슷한(?) 방법으로는 관심있는 API 를 전부(약 120여개) 후킹해서 각 파라미터/리턴값을 확인하고 호출 순서등을 확인한 적이 있었습니다.

  7. 냥냥 2009/10/31 03:16 댓글주소 | 수정 | 삭제 | 댓글

    NotePad에 관한 질문드리겠습니다 ㅠ
    제가 더미dll ( Attatch 시 빈 메세지 박스만띄우는 ) 을 만든후 Notepad.exe에 dll injection을 통해서 구현해보려고 했습니다.
    방식은 Notepad를 디버거로서 연다음 Notepad.exe의 메모리공간을 할당받은후 LoadLabrary()를 실행해서 더미dll을 Attatch시키게하는 명령어 코드를 복사한뒤 EIP수정후 실행하게한후, 실행후 다시 EIP를 원래의 EIP주소로 변경하도록 만들었습니다,
    근데... 제대로 동작을안하고 Notepad.exe가 그냥 죽어버리는군요...
    코드가 잘못됐는지 확인하려고... 아무런 보안기능이 없는 전에 만든 툴로 대상을 변경후 실행하니 아무런 이상없이 실행이 잘됐습니다 ( 빈 메세지박스를 확인 )
    Notepad.exe에서는 제대로 실행되지 않는 이유가 무엇을까요..?
    운영체제에서 특별한 알고리즘을 가지고 보호하는건가요..? ( 부팅시 원도우 기본파일들인 calc,notepad 같은 프로그램은 수정되었을시 복원하는 기능은 있다고 들었지만... )

    • ReverseCore 2009/10/31 12:35 댓글주소 | 수정 | 삭제

      안녕하세요

      어떤 OS 를 쓰시나요?

      전 주로 XP 를 쓰는데, DLL Injection 은 아무런 문제가 없습니다.

      다른 프로그램에서는 잘 동작하는데 notepad 만 동작하지 않는다고 하셨는데요, 저도 잘 이해가지 않는군요.

      참고로 DLL Injection 을 위해서 반드시 notepad 를 디버깅 할 필요는 없습니다. 그리고 DLL Injection 의 코드를 확인 해보시는 것이 좋을것 같습니다.

      가능하시면 저에게 보내주시기 바랍니다. 이곳에 올리셔도 되구요.

    • 나그네 2009/11/03 05:34 댓글주소 | 수정 | 삭제

      WFP(Windows File Protection) 때문에 그런거 아닐까요? system 폴더에 있는 notepad.exe 를 다른 폴더로 옮겨서하면 괜찮을 것 같은데요.ㅋ

    • reversecore 2009/11/03 22:25 댓글주소 | 수정 | 삭제

      나그네님, 소중한 답변 감사드립니다. ^^

  8. 냥냥 2009/10/31 16:09 댓글주소 | 수정 | 삭제 | 댓글

    메일 주소가 어떻게 되시나요..? ;;
    마땅히 보낼만한...ㅠㅠ

  9. vice 2009/12/16 11:15 댓글주소 | 수정 | 삭제 | 댓글

    잘 보구 갑니다. 간단하구 쉽게 설명 잘 해주시네요 ^^

  10. thav 2009/12/21 13:23 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다. 근데. 님께서 첨부해주신 샘플 소스 구동하다가 notepad 저장할때 정지됩니다.
    혹시 이런 현상 보신적 있나요?
    OS 는 윈도우 XP 입니다.

    • ReverseCore 2009/12/22 11:30 댓글주소 | 수정 | 삭제

      thav님, 안녕하세요.
      notepad 저장시에 프로그램이 멈춘다는 말씀이시죠?

      제 환경(XP SP3)에서는 그런 일이 없었지만, 충분히 그럴 가능성은 있습니다.

      * 참고로 제 테스트 PC 는 OS 만 설치된 클린 PC 환경입니다.

      제가 여러가지 상황에서 충분히 테스트하지 않아서 그럴 수 있고요. thav님 PC 에 실행중인 프로세스들에 의해서 그렇게 될 수 도 있습니다. (충돌!)
      (또한 실습 예제 파일들은 코드의 간결성을 위해서 에러처리가 거의 되어있지 않습니다.)

      (가능하시다면) 다른 PC 에서도 테스트 해보시기 바랍니다.

      감사합니다.


API Hooking - Tech Map

study 2009/09/29 06:39

API Hooking 에서 사용되는 각종 방법들에 대한 Tech Map 을 소개하고 간략한 설명을 하겠습니다.

API Hooking 의 기본 소개는 아래 글을 참고해주세요.

참고: API Hooking - 리버싱의 '꽃'



API Hooking Tech Map


아래 그림이 API Hooking 의 모든 기술적 범주를 포함하는 Tech Map 입니다.


<Fig. 1>

위 Tech Map 을 이용하면 그 동안 막연하게만 보였던 API 후킹이 (기술적으로) 단번에 파악됩니다.

API 후킹 작업을 할 때 상황에 맞게 위의 Tech Map 에서 적절한 기법을 골라서 적용하시면 됩니다. (가장 널리 사용되는 기법은 빨간색으로 표시하였습니다.)


[ Method – Object (what) ]

API 후킹 방식(Method)에 대한 대분류 입니다.

API 후킹 방식(Method)는 작업 대상(Object)에 따라서 크게 static 방식dynamic 방식으로 나눌 수 있습니다.

static 방식은 작업 대상(Object)이 ‘파일’이며, dynamic 방식은 작업 대상이 프로세스 ‘메모리’ 입니다.
일반적으로 API 후킹이라고 하면 dynamic 방식을 말하며, 매우 특수한 상황에서 static 방식을 사용할 때도 있습니다.

각각에 대한 설명은 아래의 표에 정리하였습니다.


<Fig. 2>

* static 방식은 여러 가지 단점 때문에 일반적으로 사용하기에 어려운 부분이 많이 있습니다. 다만 특수한 경우에 사용되기도 하므로 여기서는 소개 정도만 하고 넘어갑니다.


[ Location (where) ]

대상의 어느 부분을 공략(조작)해야 하는지에 대한 내용입니다.

일반적으로 3 군데의 공략 위치가 있습니다.

1) IAT

IAT 에 있는 API 주소를 후킹 함수 주소로 변경하는 방법입니다.

장점은 가장 단순하며, 구현 방법이 가장 쉽습니다.

단점으로는 IAT 에 없는데 프로그램에서 사용되는 API 들에 대해서는 후킹할 수 없습니다. (예: DLL 을 동적으로 로딩해서 사용하는 API)

2) Code

프로세스 메모리에 매핑된 시스템 라이브러리(*.dll)에서 API 의 실제 주소를 찾아가 코드를 직접 수정해버리는 방법입니다.

참고로 이 방법이 가장 널리 사용되는 방법이며, 구현에 있어서 아래와 같은 여러 가지 다양한 옵션이 있습니다.
 
- 처음 5 byte 를 JMP XXXXXXXX 명령어로 패치하는 방법
- 함수 일부를 덮어쓰는 방법
- 필요한 부분만 일부 변경하는 방법

3) EAT

DLL 의 EAT(Export Address Table) 에 기록된 API 의 시작 주소를 후킹 함수 주소로 변경하는 방법입니다.

개념은 간단하지만 코드 구현에 있어서 위 2) 번 방법이 더 간단하고 강력하므로 EAT 수정 방법은 잘 사용되지 않습니다.


[ Technique (How) ]

후킹 대상 프로세스 메모리에 침투하여 후킹 함수를 설치하는 구체적 기법(Technique)에 대한 내용입니다.

크게 Debug 와 Injection 기법으로 나눌 수 있으며, Injection 기법은 다시 Code 와 Dll 기법으로 나뉘어 집니다.

A) Debug

대상 프로세스를 디버깅하면서 API 후킹을 하는 방법입니다.

아마 이 말이 무슨 의미인지 이해가 잘 안 되는 분들이 계실 것입니다.
“그게 디버깅이지 무슨 API 후킹이야?” 하고 말이죠.

디버거(Debugger)는 디버깅 당하는 프로세스(Debuggee)에 대한 모든 권한(실행 제어, 메모리 액세스, 기타)을 가지기 때문에, Debuggee 의 프로세스 메모리에 후킹 함수를 자유롭게 설치 할 수 있습니다.

여기서 얘기하는 Debugger 는 일반적인 OllyDbg, Windbg, IDAPro 등이 아니라, 후킹을 위하여 사용자가 직접 제작한 프로그램입니다.
즉, 프로그램 내에서 Debug API 를 이용하여 대상 프로세스에 Attach 하고 (실행이 잠깐 멈춰진 상태에서) 후킹 함수를 설치합니다. 그 후 실행을 재개시키면 완벽한 API Hooking 이 이뤄지는 것입니다. (XP 이상의 시스템에서는 Debuggee 의 종료 없이 Debugger 를 Detach 시킬 수 도 있습니다.)

물론 기존 디버거(OllyDbg, Windbg, IDAPro)에 자동화 스크립트를 사용하여 API 후킹을 자동화 시키는 방법도 있습니다. (특히 Immunity Debugger 같은 경우 강력한 전용 Python 스크립트를 지원하고 있습니다.)

이 방식의 장점은 구현만 완벽하다면 (하나의 프로세스에 대한) 가장 강력한 후킹 방법입니다. API 후킹 뿐만 아니라 필요에 따라서 실행 흐름 까지도 완벽히 제어할 수 있습니다.

따라서 API 후킹 도중이라도 사용자가 Interactive 하게 프로그램의 실행을 멈추고 API 후킹을 추가/수정/제거 등의 작업을 할 수 있습니다. (다른 방식과 가장 큰 차이점입니다.)

단점은 Debugger 에 대한 지식(혹은 자동화 스크립트에 대한 지식)이 필요합니다. 또한 안정적인 동작을 위해서는 많은 테스트가 요구됩니다. 이와 같은 단점들 때문에 (강력함에도 불구하고) 범용적으로는 사용하기는 쉽지 않습니다.

B) Injection

Injection 기법은 해당 프로세스 메모리 영역에 침투하는 기술로써 Injection 대상에 따라 B-1) Code InjectionB-2) DLL Injection 으로 나눌 수 있습니다. 그 중에서 DLL Injection 기법이 가장 널리 사용됩니다.

DLL Injection 기법은 대상 프로세스로 하여금 강제로 사용자가 원하는 DLL file 을 로딩하게 만드는 기술입니다. (DLL Injection 에 대한 자세한 설명은 예전의 제 글을 참고하세요. DLL Injection - 다른 프로세스에 침투하기 (1))

Injection 할 DLL 에 미리 후킹 코드와 설치 코드를 만들고 DllMain() 에서 설치 코드를 호출해 주면 Injection 되는 순간에 API 후킹이 완료됩니다.

Code Injection 기법은 기존 DLL Injection 보다 좀 더 발전된 (복잡한) 기술이며, 주로 악성코드(바이러스, 쉘코드, 기타)에서 많이 사용됩니다. (DLL Injection 은 AV 제품에서 탐지가 잘 되므로, 악성 코드들은 좀 더 탐지하기 어려운 Code Injection 을 많이 시도하고 있습니다.)

Code Injection 기법의 구현방법은 상당히 까다로운 편입니다. 그 이유는 DLL Injection 처럼 완전한 형태의 PE 이미지가 아니라 실행 코드와 데이터만 Injection 된 상태에서 자신이 필요한 API 주소를 직접 구해서 사용해야 하며, 코드 내의 메모리 주소에 접근할 때 잘못된 주소를 액세스 하지 않도록 매우 주의해야 하기 때문입니다.

말로만 설명하면 어렵습니다. 나중에 코드를 보며 직접 실습을 해보도록 하겠습니다.
(직접 해보시면 왜 어렵다고 말씀 드렸는지 느낌이 팍 오실 것입니다.)

[ API ]

Tech Map 에 소개된 방법들을 실제로 구현하기 위해서 사용되는 API 들을 소개합니다.

한번씩 읽어 보시고 향후 실습할 때 사용법에 대해서 자세히 살펴보도록 하겠습니다.

참고로 위에 소개된 API 말고도 OpenProcess(), WriteProcessMemory(), ReadProcessMemory() API 들은 다른 프로세스 메모리에 접근하려고 할 때 항상 사용되는 API 들입니다.


+---+

설명이 많이 길었습니다.
다소 지루하시더라도 세부 기술 설명에 앞서 이러한 이론적인 설명은 꼭 필요합니다.

위와 같이 Tech Map 으로 전체 기술에 대해 이론적으로 잘 정리해 두면 기술에 대해서 더 잘 이해할 수 있고, 실전에서 (상황에 맞게) API Hooking 을 적용하기 쉬워집니다.

다음 번 포스트에서는 위 방법을 하나씩 실습해 보도록 하겠습니다. (static 방법에 대한 설명은 생략하겠습니다.) 각 경우에 대한 실습을 진행하면서 그때그때 필요한 설명은 자세히 추가하겠습니다.


Debug 방식의 API Hooking
API Hooking - 메모장 WriteFile() 후킹


ReverseCore

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

  1. 이승철 2009/09/30 11:07 댓글주소 | 수정 | 삭제 | 댓글

    기대됩니다. ㅜㅜ 추석이 오기 전에 빨리 보고 싶네요 ㅜㅜ

    이번 추석은 리버싱과 함께

    • reversecore 2009/09/30 18:55 댓글주소 | 수정 | 삭제

      ^^ 안녕하세요. 기대하신만큼 좋은글이 나와야 하는데 말이죠~ ^^

  2. 2009/10/01 17:04 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  3. 2009/10/02 01:28 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  4. nettok 2009/10/02 07:43 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 잘보고 갑니다.
    앞으로 자주 들러야겠네요 와방 기대됩니다 ^_^

  5. 2009/10/14 15:38 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다



API Hooking 에 대한 강좌입니다.
User 에서 API Hooking의 다양한 기법들에 대해서 자세히 설명하도록 하겠습니다.



후킹 (Hooking)

리버싱에서 후킹은 정보를 가로채고, 실행 흐름을 변경하고, 원래와는 다른 기능을 제공하게 하는 기술입니다.

후킹의 전체 프로세스는 아래와 같습니다.

- 디스어셈블러/디버거를 이용하여 프로그램의 구조와 동작원리를 파악
- 버그 수정 또는 기능 개선에 필요한 Hook 코드를 개발 [1]
- 실행 파일과 프로세스 메모리를 자유롭게 조작하여 Hook 코드 설치

위와 같은 일련의 작업들은 그야말로 리버스 엔지니어링 기술의 핵심(Core) 이라고 할 수 있습니다.

그래서 전 후킹을 "리버싱의 꽃" 이라고 부릅니다.

여러가지 후킹 기술이 있지만 그중에서도 API 를 후킹하는 기술을 API Hooking 이라고 하고, User mode 후킹 중에서 메시지 후킹[2]과 함께 가장 널리 사용되는 기술입니다.


[1] 프로그램 소스가 있다면 대부분의 경우 후킹은 필요하지 않을 것입니다. 하지만 특수한 상황(소스 코드가 없거나, 소스 코드의 수정이 여의치 않은 상황)에서는 후킹 기술이 요긴하게 사용될 수 있습니다.

[2] 메시지 후킹(Message Hooking) 에 대한 내용은 제 글을 참고하시기 바랍니다.

- Windows Message Hooking (윈도우 메시지 후킹)



API (Application Programming Interface)


API Hooking 을 설명하려면 먼저 API(Application Programming Interface) 에 대해서 짚고 넘어가야 합니다.

Windows OS 에서는 사용자 어플리케이션이 시스템 자원(메모리, 파일, 네트워크, 비디오, 사운드, 기타)을 사용하고 싶을 때 직접 할 수 있는 방법이 없습니다. 왜냐하면 그것들은 OS 가 직접 관리하며, 여러 가지 이유(안정성, 보안, 효율, 기타)로 사용자 어플리케이션의 직접적인 접근을 막아놓았기 때문입니다.

이럴 때 사용자 어플리케이션은 시스템 커널에게 요청해야 합니다. 요청 방법이 바로 MS 에서 제공한 Win32 API 를 이용하는 것입니다. (API 는 해당 OS 제작사에서 제공합니다.)

즉, API 함수 없이는 어떤 의미 있는 프로그램을 만들어 낼 수 없습니다.

아래 그림은 32 bit Windows OS 의 프로세스 메모리를 간략히 나타낸 것입니다.


<Fig. 1>

실제 어플리케이션 코드를 실행 시키기 위해 많은 시스템 라이브러리(DLL) 들이 로딩됩니다. [3] 모든 프로세스에는 기본적으로 kernel32.dll 이 로딩되구요, kernel32.dll 은 ntdll.dll 을 로딩합니다. (참고로 GUI 어플리케이션에서는 user32.dll 과 gdi32.dll 또한 필수 라이브러리 입니다.)

ntdll.dll 의 역할이 바로 유저 모드 어플리케이션의 코드에서 발생하는 시스템 자원에 대한 접근을 커널 모드에게 요청 하는 것입니다.


간단한 를 들어보겠습니다.
notepad.exe 에서 c:\abc.txt 라는 파일을 열고자 합니다.
코드에서는 msvcrt!fopen() API 를 호출합니다. 그 이후의 API 호출 흐름을 보면 아래와 같습니다.

msvcrt!fopen()
  
kernel32!CreateFileW()
    ntdll!ZwCreateFile()
      ntdll!KiFastSystemCall()
        SYSENTER                     // Intel IA-32 Op Code
          => 커널 모드 진입


일반적인 시스템 자원을 사용하는 API 는 kernel32.dll 과 ntdll.dll 을 타고 가다가 결국 SYSENTER 명령을 통해 커널 모드로 진입하게 됩니다.


[3] 'DLL 로딩(loading)' 이라는 용어보다는 'DLL 매핑(mapping)' 이라는 용어가 더 정확한 표현입니다. Windows 운영체제는 DLL 을 최초 한번만 메모리에 적재(loading) 하고, 그 이후부터는 프로세스에게 매핑(mapping) 시켜주는 메커니즘을 사용합니다.



API Hooking


API Hooking 이란 Win32 API 호출을 중간에서 가로채서 제어권을 얻어내는 것입니다.

API Hooking 의 이점은 다음과 같습니다.

- API 호출 전/후에 사용자의 훅 코드(Hook Code)를 실행시킬 수 있습니다.
- API 에 넘어온 파라미터 혹은 API 함수의 리턴값을 엿보거나 조작 할 수 있습니다.
- API 호출 자체를 취소시키거나 사용자 코드로 실행 흐름을 변경시킬 수 있습니다.

이해를 돕기 위해 아래 그림을 봐주시기 바랍니다.

먼저 정상적인 API 호출입니다.


<Fig. 2>

코드 영역 주소에서 CreateFile() API 를 호출하였습니다. [4]

CreateFile() API 는 kernel32.dll 에서 서비스(export) 하므로 kernel32.dll 영역의 CreateFile() API 가 실행되고 정상적으로 리턴합니다.

[4] 실제 kernel32 에서 서비스되는 API 이름은 CreateFileA() 와 CreateFileW() 입니다. 프로그래밍할 때 CreateFile() 만 써주면 컴파일 시에 적절히 CreateFileA()/CreateFileW() 중에서 결정됩니다. 여기서는 설명의 편의상 CreateFile() 로 하였습니다.


다음은 kernel32!CreateFile() 가 후킹된 경우입니다.


<Fig. 3>

사용자가 DLL Injection 기술로 hook.dll 을 프로세스 메모리 공간에 침투 시킵니다. 그리고 kernel32!CreateFile() 를 hook!MyCreateFile() 로 후킹하였습니다. (후킹 함수 설치 방법은 DLL Injection 말고도 더 있습니다.)

이제부터 해당 프로세스에서 CreateFile() API 가 호출 될 때마다 kernel32!CreateFile() 이 호출 되는 것이 아니라, hook!MyCreateFile() 이 호출 됩니다.

후킹 함수(MyCreateFile)와 원본 함수(CreateFile)의 호출 순서는 경우에 따라 달라집니다.
입력된 파라미터를 조작하고 싶을 때는 후킹 함수가 먼저 실행되고 나중에 원본 함수가 실행됩니다. 또한 API 리턴값을 조작하고 싶을 때는 원본 함수가 먼저 실행된 후 후킹 함수가 나중에 실행됩니다.

이 외에도 후킹 목적에 따라서 원본 함수 호출 전/후에 후킹 함수를 실행시키거나 원본 함수를 아예 호출하지 않는 등의 여러 가지 변형이 가능합니다. 따라서 후킹 목적과 상황에 따라서 적절히 사용해 주시면 되겠습니다.

이것이 바로 API Hooking 의 기본 개념입니다.

API Hooking 을 구현하는 방법은 다양합니다. 하지만 후킹의 기본 개념은 변하지 않습니다.

위의 개념을 잘 이해하시면 이후에 설명 드리는 구현 방법에 대해서도 쉽게 이해하실 수 있습니다.


+---+

다음 포스트로 이어집니다.

API Hooking - Tech Map


ReverseCore

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

  1. 마플 2009/09/29 16:01 댓글주소 | 수정 | 삭제 | 댓글

    글 잘 봤습니다.
    정말 깔끔하네요. 멋져요~

  2. alex 2009/11/12 13:39 댓글주소 | 수정 | 삭제 | 댓글

    정말 글쓰는데 재주가 있으세요!
    너무너무 잘 쉽게 풀어쓰셨네요!
    좋은글 많이보고 많은 지식얻어갑니다!!

    많은글 써주세요!! >.<

  3. alex 2009/11/12 13:40 댓글주소 | 수정 | 삭제 | 댓글

    책 한권 쓰심이... ㅎㅎ 그림들도 넘 깔끔하고 좋네요 ㅋ

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

      alex님, 안녕하세요.

      제가 예전에 이 댓글을 못 보고 지나쳤군요. ㅠㅠ

      책도 쓰고 싶은 욕심이 있습니다.

      칭찬 감사합니다. ^^

  4. 정기욱 2010/03/31 13:43 댓글주소 | 수정 | 삭제 | 댓글

    후킹에대해서 찾던중 잘보고갑니다. 깔금하네요..^^

  5. 안정현 2010/06/17 11:04 댓글주소 | 수정 | 삭제 | 댓글

    윈도우 구조대충. 어셈블리어대충 해석할정도.허접실력인데요/..
    HACK.DLL 의소스처럼 C언어 로 소슨나열해놓은게 해석이 안돼네요 ㅜㅜ,.
    그 소스들이 C언어인가요? C언어는 배웠는데 .. 뭐가뭔지 몰르겟어요 ㅜㅜ
    일단 뭐부터공부를해나가야할지막막하네요.. 하고는싶은데 .

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

      리버싱을 처음 시작할 때는 막막한 느낌이 드는게 사실이죠.

      제 경험상 리버싱은 디버깅부터 시작하시는게 좋을것 같습니다.

      아주 간단한 프로그램을 디버깅 하면서 하나씩 배우는 것이죠.

      제 블로그의 처음 부터 읽어보시기 바랍니다.

      감사합니다.

  6. BM 2010/06/24 17:41 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요?

    후킹 함수 설치 방법에 DLL Injection 말고 다른 어떤 방법이 있는지 구체적으로 알고 싶습니다.

    • reversecore 2010/06/25 10:21 댓글주소 | 수정 | 삭제

      아래 강좌의 TechMap 그림에 정리를 해 놓았습니다.

      http://www.reversecore.com/55

      유저모드에서 크게 아래와 같은 방법이 있구요...
      DLL Injection
      Code Injection
      Debug
      Static patch
      ...

      각 방법별로 구현하는 기법들이 다양하게 존재합니다.

      또 궁금하신 점은 다시 질문 올려주세요

      감사합니다.

  7. 2010/12/10 19:14 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/12/16 15:30 댓글주소 | 수정 | 삭제

      안녕하세요.

      정확한 주소와 함수 형식을 알면 어떤 함수라도 훅을 설치할 수 있습니다.

      감사합니다.

  8. 박은선 2011/01/05 01:08 댓글주소 | 수정 | 삭제 | 댓글

    언제나 검색엔진을 통해 들어오지만, 늘 감동이네요! 윗분 말씀처럼, 책을 쓰셔도 되실 내공인듯!

  9. 박정운 2011/08/06 10:20 댓글주소 | 수정 | 삭제 | 댓글

    당신은 정말 정말 천재입니다.
    이런방법까지 동원할수 있다는게 그저 신기할따름이고
    읽고 또 읽고 그러지만, 책으로 이런 내용들이 나와서 퍼지게 되면 정말 골치아파질수도 있겠다 생각이 듭니다. 물론 악의적으로 이용하지 않는다면 문제가 안되겠지만,
    정말이지 글 잘 보고 인쇄까지 해서 분석하고 있습니다. 감사합니다.

    • reversecore 2011/09/19 20:24 댓글주소 | 수정 | 삭제

      API Hooking 을 비롯한 리버싱 기술들은 제가 만들어낸 것은 하나도 없구요. 저또한 다른 사람들이 해놓은 지식과 경험을 배운것 뿐입니다.

      그사람들이 천재인 셈이지요. ^^

      감사합니다.

  10. 2011/08/13 20:39 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  11. sizn 2011/09/07 11:00 댓글주소 | 수정 | 삭제 | 댓글

    글쓰기도 잘하시고 글 이해도를 높히게 그림도 적절히 넣어주시는 센스
    잘보고 갑니다.

    아이폰 개발자로 현재 WM개발을 하면서 들리게 되었네요 ㅎ
    항상 건승하세요!~바잇!