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


◀ PREV : [1] : ... [33] : [34] : [35] : [36] : [37] : [38] : [39] : [40] : [41] : ... [91] : NEXT ▶