10.17(수) 실습 - ( API Hooking, 예외 )

2007/10/17 14:06

1.GetMessageBoxA 의 후킹~ 
[ more.. | less.. ]
// API Hooking 의 개념 - DLL 함수 호출의 원리
typedef UINT ( WINAPI* F )( HWND, PCSTR, PCSTR, UINT );

UINT WINAPI foo( HWND hwnd, PCSTR msg, PCSTR caption, UINT btn )
{
    printf( "foo : %x, %s, %s \n", hwnd, msg, caption );

    // 원래 함수로 다시 보낸다.
    // MessageBoxA();  // 재귀호출을 한다..

    HMODULE hDll = GetModuleHandle("User32.dll");
    F f = (F)GetProcAddress( hDll, "MessageBoxA" );

    return f( hwnd, msg, caption, btn );
}

int main()
{
    printf( "%p\n", GetModuleHandle("User32.dll") );
    printf( "%p\n", MessageBoxA );

    // .idata section 에서 MessageBoxA의 주소를 담은 곳을 foo의 주소로 변경한다.
    // 주소에 타입없이 대입시킨다면 호출시 읽어 올수가 없다.
    // 그러므로 int 로 강제 형변환 해준다.
    *((int*)(0x00418338)) = (int)foo;

    MessageBoxA( 0, "Hello", "", MB_OK);    // User32.dll 에 있다.
}

2. Debug Help API 를 이용한 후킹~
[ more.. | less.. ]
#include "Dbghelp.h"

#pragma comment( lib, "Dbghelp.lib" )

// 제프리책 853p 에 있는 코드들~

void Replace( HMODULE hDll,     // exe의 주소
              char*   name,     // 후킹할 함수를 가진 dll의 이름
              PROC    oldfunc,  // 원래 API함수의 주소
              PROC    newfunc ) // 후킹할 함수
{
    // .exe에서 .idata section의 주소를 얻는다.
    ULONG sz;
    IMAGE_IMPORT_DESCRIPTOR* pImage = (IMAGE_IMPORT_DESCRIPTOR*)
        ImageDirectoryEntryToData( hDll,    // 모듈 주소
                                   TRUE,    // Mapping?
                                   IMAGE_DIRECTORY_ENTRY_IMPORT,
                                   &sz );

    printf( ".idata의 주소 : %p\n", pImage );

    // 2. User32.dll 의 항목을 구한다.
    for( ; pImage->Name; ++pImage )
    {
        char* p = (char*)hDll + pImage->Name;
        if( strcmpi( p, name ) == 0 ) break;        //찾은경우
    }
    if ( pImage->Name == 0 )
    {
        printf( "User32.dll을 import 하지 않습니다.\n" );
        return;
    }
    printf( "User32.dll를 관리하는 구조체 주소 : %p\n", pImage );

    // 3. Thunk Table에서 원하는 함수를 찾는다.
    IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)
        ( (char*)hDll + pImage->FirstThunk );

    // 4. 이제 함수를 찾아서 새로운 함수 주소를 덮어 쓴다.
    for ( ; pThunk->u1.Function; ++pThunk )
    {
        if( (PROC)(pThunk->u1.Function) == oldfunc )
        {
            PROC* p = (PROC*)&(pThunk->u1.Function);

            // Release 모드에서는 가상주소를 보호해준다.
            // 그러므로 보호속성을 R/W로 변경해야 한다.
            DWORD old;
            VirtualProtect( p, sizeof(PROC), PAGE_READWRITE, &old );
            // 쓰기를 할때는 WriteProcessMemory() 함수를 사용핮.
            WriteProcessMemory( GetCurrentProcess(), p, &newfunc, sizeof(PROC), 0 );                                
        }
    }
}

typedef UINT ( WINAPI* F )( HWND, PCSTR, PCSTR, UINT );

UINT WINAPI foo( HWND hwnd, PCSTR msg, PCSTR caption, UINT btn )
{
    printf( "foo : %x, %s, %s \n", hwnd, msg, caption );

    // 원래 함수로 다시 보낸다.
    // MessageBoxA();  // 재귀호출을 한다..

    HMODULE hDll = GetModuleHandle("User32.dll");
    F f = (F)GetProcAddress( hDll, "MessageBoxA" );

    return f( hwnd, msg, caption, btn );
}

void Setup()
{
    Replace( GetModuleHandle(0), "User32.dll", (PROC)MessageBoxA, (PROC)foo );
}

int main()
{
    Setup();

    printf( "%p\n", GetModuleHandle("User32.dll") );
    printf( "%p\n", MessageBoxA );  // Release 모드에서는 이 순간 MessageBoxA의 주소를
                                    // ECX 레지스터에 보관해둔다.
    // Replace( GetModuleHandle(0), "User32.dll", (PROC)MessageBoxA, (PROC)foo );

    MessageBoxA( 0, "Hello", "", MB_OK);    // User32.dll 에 있다.
}

3. __try, __except 기초
[ more.. | less.. ]
// SEH : structual Exception Handling
int n = 0;

DWORD Filter( DWORD code )
{
    if ( code == EXCEPTION_INT_DIVIDE_BY_ZERO ) // 0xc0000094
    {
        printf( "0으로 나누는 예외 발생. 다른값을 입력 : " );
        scanf("%d", &n);

        return -1;  // 예외의 원인을 수정했으므로 다시 실행해 본다.
    }
    return 1;
}

int main()
{
    int s = 0;

    __try
    {
        s = 10 / n ; // Code : 0xc0000094

        char* p = 0;
        *p = 'A';   // access - violation Code : 0xc0000005
       
        printf( "%d\n", s );
    }
    // 1: 핸들러 수행, -1: 예외난곳을 다시 실행 0: 외부의 __try블록으로 전달.
    __except( Filter( GetExceptionCode() ) )
    {
        printf( "예외발생: %x\n", GetExceptionCode() );
    }
    printf("프로그램은 계속 실행\n");
}

4. C++ throw의 __except
[ more.. | less.. ]
// C++예외 e06d7363 를 발생시키기!!!!!!!!
int main()
{
    __try
    {
        throw 1;    // C++ 예외...
    }
    __except( 1 )
    {
        printf("예외 번호 : %x\n", GetExceptionCode() );
    }
}

5. STH( Structure Termination Handling ) __try, __finally ( 구조화된 종료 처리 )
  1) 개념
[ more.. | less.. ]
DWORD foo()
{
    DWORD ret = 0;

    __try
    {
        // 성능이 떨어질 수 있으므로 return은 자제하자.!!!
        return ret; // 이순간 리턴값을 잠시 스택에 보관하고
                    // finally 를 실행한 후에 .. 리턴값을 다시 꺼내서 리턴.
    }
    __finally
    {
        BOOL b = AbnormalTermination();

        if ( b == TRUE )
        {
            printf("__try를 탈출하는 코드(리턴,예외) 발생..\n");
        }
        else
        {
            printf("....\n");
        }
        printf( "이 부분은 try를 벗어나려고 하면 항상 호출됩니다.\n" );
    }
    return 0;
}

int main()
{
    int s = foo();

    printf( "%d\n", s );
}

 2) 응용 ( 자원관리를 __finally 에서 담당해준다.!!! )
[ more.. | less.. ]
// STH를 사용하는 의사코드..
// One Entry Multiple Exit
BOOL foo()
{
    HANDLE hFile = INVALID_HANDLE_VALUE;    // -1
    BOOL   ret = FALSE;
    char*  buf = 0;

    hFile = CreateFile(...);
    if ( hFile == -1 ) return FALSE;

    buf = new char[1000];
    if ( buf == 0 ) return FALSE;

    ret = ReadFile(...);
    if ( ret == FALSE ) { CloseHandle( hFile ); delete[] buf;
        return FALSE;
    }

    // 성공...
    CloseHandle( hFile );
    delete[] buf;

    return TRUE;
}
///////////////////////////////////////////////////////////
BOOL foo()
{
    HANDLE hFile = INVALID_HANDLE_VALUE;    // -1
    BOOL   ret = FALSE;
    char*  buf = 0;

    hFile = CreateFile(...);
    if ( hFile != -1 )
    {
        buf = new char[1000];
        if ( buf != 0 )
        {
            ret = ReadFile(...);
            if ( ret != FALSE )
            {
                // 성공.....
            }
            delete[] buf;
        }
        CloseHandle( hFile );
    }

    return ret;
}
///////////////////////////////////////////////////////////
// 모든 자원관리를 한군데로 몰아서 한다.
// return 은 성능에 문제를 주므로 __leave 를 써서 finally로 간다.
// STH를 사용한 자원 관리 기법 - 23장(제프리)
////////////////////////////////////////////////////////
BOOL foo()
{
    // 필요한 변수를 실패값으로 초기화 해서 만들어라.
    HANDLE hFile = INVALID_HANDLE_VALUE;    // -1
    BOOL   ret = FALSE;
    char*  buf = 0;

    // __try 안에서 자원 할당과 사용을 한다.
    __try
    {
        hFile = CreateFile(...);
        if ( hFile == -1 )  __leave; // goto __finally

        buf = new char[1000];
        if ( buf == 0 )     __leave;

        ret = ReadFile(...);
        if ( ret == FALSE ) __leave;
    }
    __finally
    {
        // 이부분은 반드시 실행됨이 보장된다. 자원해지.
        if ( buf != 0 ) delete[] buf;
        if ( hFile != INVALID_HANDLE_VALUE ) CloseHandle( hFile );
    }

    return ret;
}

6. SEH 를 응용해서 메모리 할당 해보기. ( 일명 : Big Shell )
[ more.. | less.. ]
struct CELL
{
    char data[4096-5];   // 1Page- 5byte..
};

int main()
{
    // 필요한 CELL 만큼 가상주소를 예약한다.
    CELL* pTable = (CELL*)VirtualAlloc( 0, sizeof(CELL)*1000,
                                        MEM_RESERVE, PAGE_NOACCESS );

    printf( "예약된 주소 : %p\n", pTable );

    while( 1 )
    {
        int no = 0;
        char data[4096] = { 0 };

        printf( "CELL 번호 >> " );
        scanf( "%d", &no );

        printf( "DATA >> " );
        scanf( "%s", data );
        /////////////////////////////////
        __try
        {
            // 확정된 데이타에 더 작은 데이터를 넣는다면 에러가 나오지 않는다.!!
            strcpy( pTable[no].data, data );
        }
        __except( 1 )
        {
            printf( "%d CELL을 확정 합니다.\n", no );

            VirtualAlloc( &pTable[no], sizeof(CELL), MEM_COMMIT, PAGE_READWRITE );

            // 이제 다시 복사한다.
            strcpy( pTable[no].data, data );
        }       
    }
}

7. API Hooking ( SetTimer를 바꿔치기 해보자 - 미완성 ㅜ_ㅜ )
 1) DLL ( Inject 할 Dll )
[ more.. | less.. ]
////////////////////////////////////////////////////////////////////////////////
// SpeedHook.dll 만들기
////////////////////////////////////////////////////////////////////////////////

#include <dbghelp.h>

#pragma comment( lib, "dbghelp.lib" )

typedef UINT_PTR (WINAPI* F)( HWND, UINT_PTR, UINT, TIMERPROC ); void Replace( HMODULE hDll,     // exe의 주소
              char*   name,     // 후킹할 함수를 가진 dll의 이름
              PROC    oldfunc,  // 원래 API함수의 주소
              PROC    newfunc ) // 후킹할 함수
{
    // .exe에서 .idata section의 주소를 얻는다.
    ULONG sz;
    IMAGE_IMPORT_DESCRIPTOR* pImage = (IMAGE_IMPORT_DESCRIPTOR*)
        ImageDirectoryEntryToData( hDll,    // 모듈 주소
                                   TRUE,    // Mapping?
                                   IMAGE_DIRECTORY_ENTRY_IMPORT,
                                   &sz );

    // 2. User32.dll 의 항목을 구한다.
    for( ; pImage->Name; ++pImage )
    {
        char* p = (char*)hDll + pImage->Name;
        if( strcmpi( p, name ) == 0 ) break;        //찾은경우
    }
    if ( pImage->Name == 0 )
    {
        return;
    }

    // 3. Thunk Table에서 원하는 함수를 찾는다.
    IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)
        ( (char*)hDll + pImage->FirstThunk );

    // 4. 이제 함수를 찾아서 새로운 함수 주소를 덮어 쓴다.
    for ( ; pThunk->u1.Function; ++pThunk )
    {
        if( (PROC)(pThunk->u1.Function) == oldfunc )
        {
            PROC* p = (PROC*)&(pThunk->u1.Function);

            DWORD old;
            VirtualProtect( p, sizeof(PROC), PAGE_READWRITE, &old );

            WriteProcessMemory( GetCurrentProcess(), p, &newfunc, sizeof(PROC), 0 );
        }
    }
}

UINT_PTR MyTimer( HWND hwnd, UINT_PTR id, UINT ms, TIMERPROC f )
{
    HMODULE h = GetModuleHandle( "user32.dll" );
    F func = (F)GetProcAddress( h, "SetTimer" );

    // 원래 함수를 호출한다.
    return func( hwnd, id, ms/10, f );
}

void Setup()
{
    Replace( GetModuleHandle(0), "user32.dll", (PROC)SetTimer, (PROC)MyTimer );
}

BOOL WINAPI DllMain( HANDLE h, DWORD r, LPVOID how )
{
    if ( r == DLL_PROCESS_ATTACH )
    {
       Setup();
    }
    return TRUE;
}

2) Inject ( DLL을 Inject 하기.. )
[ more.. | less.. ]
void DLLInject( DWORD pid, const char* path )
{
    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | // 프로세스의 정보 요청
        PROCESS_CREATE_THREAD | // CreateRemoteThread를 사용하기 위해
        PROCESS_VM_OPERATION | // VirtualAllocEx/VirtualFreeEx를 사용하기 위해
        PROCESS_VM_WRITE,
        0, pid );
    //---------------------------------------------------------
    // LoadLibrary 의 주소를 찾는다.
    HMODULE hDll = GetModuleHandle( "Kernel32.dll");

    PTHREAD_START_ROUTINE f = (PTHREAD_START_ROUTINE)
                              GetProcAddress( hDll, "LoadLibraryA");
   
    // PTHREAD_START_ROUTINE 은 미리 정의된 스레드함수포인터 type이다.!!!

    // 계산기의 가상주소 공간에 메모리를 할당한다.
    void* p = VirtualAllocEx( hProcess, 0, strlen(path)+1,
                            MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    // Inject 할 DLL의 경로를 계산기 메모리에 복사해 놓는다.

    DWORD len;
    WriteProcessMemory( hProcess, p, path, strlen(path)+1, &len);


    //---------------------------------------------------------
    // 다른 프로세스에 스레드를 생성한다.
    HANDLE hThread = CreateRemoteThread( hProcess, 0, 0,
                                        f, (void*)p, // 함수, 파라미터.
                                        0, 0);

    WaitForSingleObject( hThread, INFINITE );

    CloseHandle( hThread );
    VirtualFreeEx( hProcess, p, sizeof(path), MEM_RELEASE );


    // 대상 프로세스에서 ".dll"을 unload 한다
    // (CreateRemoteThread와 FreeLibrary를 이용하여)
    hThread = CreateRemoteThread( hProcess, NULL, 0,
        (LPTHREAD_START_ROUTINE)GetProcAddress( hDll,
        "FreeLibrary" ),
        (void*)p, 0, NULL );
    WaitForSingleObject( hThread, INFINITE );

    // Clean up
    CloseHandle( hThread );

}
int main()
{
    HWND hwnd = FindWindow( 0, "지뢰 찾기");

    if ( hwnd == 0 )
    {
        printf("계산기 부터 실행하세요..\n");
        return 0;
    }
    // 계산기 프로세스에 DLL을 강제로 넣는다.
    DWORD pid;
    DWORD tid = GetWindowThreadProcessId( hwnd, &pid );

    DLLInject( pid, "C:\\SpeedHook.dll");
    getch();
}

Tags

System, 실습