오후 11:24 2003-12-28
조경민 (bro@shinbiro.com)
==================================================================
참고문서 :
http://www.codeproject.com/system/hooksys.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndebug/html/msdn_peeringpe.asp

어떤 다른 프로세스에서 실행하는 특정 함수를 내가 만든 함수로 가로채는
APIHOOK을 하는 방법에 대한 정리이다.

두가지 절차를 통해서 APIHOOK를 할 수 있는데 첫번째 해야 하는 일은
해당 프로세스로 내 코드를 주입(Inject)시키는 것과 두번째는 들어간
내 코드가 해당 프로세스의 함수 호출을 바꿔치기 하는 것이다.

주입하는 방법은 여러가지 있지만 Win9x/NT에서 다 되는 SetWindowsHookEx를
사용하는 전역후크로 해당 프로세스로 들어가는 방법이 있으며 (시스템 속도를
저하 시키므로 NT이상에서는 비추) WinNT이상에서 되는 CreateRemoteThread를
사용하는 방법이 있다.

먼저 함수 호출을 바꿔치기를 하는 방법은 Win32 실행파일형식인 PE (Portable-Execute)
안에 존재하는 IAT( Import Address Table )의 함수 주소를 바꿔치는 것이다.
잘 알려진 이 기술에 대한 설명은 'PE IAT Patching' 이라고 한다.


-------------------------------------------------------------------
오전 12:13 2003-12-29
IAT 바꿔치기
본 코드는 hooksys에서 원하는 부분만 떼어 낸것이다.
hooksys의 UML구조를 그리고 멋지게 설계해 둔것은 이해하는데 별로 도움이 되지
않기 때문에 주제에 맞는 부분만 짤라서 놓았다.
IAT바꾸는 코드는 자기 프로세스 내의 API에 대해서 바꿔치기를 시도하지만
일딴 바꾸는 것을 할 수있게 되면 다른 프로세스로 바꾸는 코드를 Inject시켜서
호출하기만 하면 되는 것이다.
-------------------------------------------------------------------

// PE 포맷다루는 API사용 (PE안의 IAT테이블을 조회해야한다)
#include <imagehlp.h>
#pragma comment(lib, "imagehlp.lib")
:
//---------------------------------------------------------------------------
// ReplaceInOneModule
//  
// Replace the address of the function in the IAT of a specific module
//---------------------------------------------------------------------------
BOOL IAT_ReplaceInOneModule( PCSTR pszCalleeModName, PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller)
{
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        PVOID sm_pvMaxAppAddr = si.lpMaximumApplicationAddress;
        const BYTE cPushOpCode = 0x68;  

        BOOL bResult = FALSE;
        __try
        {
                ULONG ulSize;
                // Get the address of the module's import section
                PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
                        (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
                        hmodCaller,
                        TRUE,
                        IMAGE_DIRECTORY_ENTRY_IMPORT,
                        &ulSize
                        );
                // Does this module has import section ?
                if (pImportDesc == NULL)
                        __leave;  
                // Loop through all descriptors and
                // find the import descriptor containing references to callee's functions
                while (pImportDesc->Name)
                {
                        PSTR pszModName = (PSTR)((PBYTE) hmodCaller + pImportDesc->Name);
                        if (stricmp(pszModName, pszCalleeModName) == 0)
                                break;   // Found
                        pImportDesc++;
                } // while
                // Does this module import any functions from this callee ?
                if (pImportDesc->Name == 0)
                        __leave;  
                // Get caller's IAT
                PIMAGE_THUNK_DATA pThunk =
                        (PIMAGE_THUNK_DATA)( (PBYTE) hmodCaller + pImportDesc->FirstThunk );
                // Replace current function address with new one
                while (pThunk->u1.Function)
                {
                        // Get the address of the function address
                        PROC* ppfn = (PROC*) &pThunk->u1.Function;
                        // Is this the function we're looking for?
                        BOOL bFound = (*ppfn == pfnCurrent);
                        // Is this Windows 9x
                        if (!bFound && (*ppfn > sm_pvMaxAppAddr))
                        {
                                PBYTE pbInFunc = (PBYTE) *ppfn;
                                // Is this a wrapper (debug thunk) represented by PUSH instruction?
                                if (pbInFunc[0] == cPushOpCode)
                                {
                                        ppfn = (PROC*) &pbInFunc[1];
                                        // Is this the function we're looking for?
                                        bFound = (*ppfn == pfnCurrent);
                                } // if
                        } // if

                        if (bFound)
                        {
                                MEMORY_BASIC_INFORMATION mbi;
                                ::VirtualQuery(ppfn, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
                                // In order to provide writable access to this part of the
                                // memory we need to change the memory protection
                                if (FALSE == ::VirtualProtect(
                                        mbi.BaseAddress,
                                        mbi.RegionSize,
                                        PAGE_READWRITE,
                                        &mbi.Protect)
                                        )
                                        __leave;
                                // Hook the function.
                *ppfn = *pfnNew;
                                bResult = TRUE;
                                // Restore the protection back
                DWORD dwOldProtect;
                                ::VirtualProtect(
                                        mbi.BaseAddress,
                                        mbi.RegionSize,
                                        mbi.Protect,
                                        &dwOldProtect
                                        );
                                break;
                        } // if
                        pThunk++;
                } // while
        }
        __finally
        {
                // do nothing
        }
        // This function is not in the caller's import section
        return bResult;
}

// Win9x에서 GetProcAddress한 것은 재조정할 필요가 있다.
FARPROC GetFixedProc( FARPROC lpfn )
{
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        PVOID sm_pvMaxAppAddr = si.lpMaximumApplicationAddress;
        const BYTE cPushOpCode = 0x68;  
        
        if( lpfn > sm_pvMaxAppAddr)
        {
                // The address is in a shared DLL; the address needs fixing up
                PBYTE pb = (PBYTE) lpfn;
                if (pb[0] == cPushOpCode)
                {
                        // Skip over the PUSH op code and grab the real address
                        PVOID pv = * (PVOID*) &pb[1];
                        lpfn = (PROC) pv;
                }
        }
        return lpfn;
}

// 기존 함수 포인터
FARPROC lpfnOld = NULL;

// 특정 임포트된 모듈DLL의 특정 함수를 내가 작성한 것으로 바꿔치기 한다.
bool ApiHook( char* modulename, char* funcname, FARPROC newfunc )
{
        lpfnOld = GetProcAddress( GetModuleHandle( modulename ), funcname );
        lpfnOld = GetFixedProc( lpfnOld );

        // hCallerModule는 호출자의 모듈이다. 호출자모듈에 존재하는 IAT를 찾아서
        // 해당 함수를 newfunc으로 바꿔치게 된다.
        // 현재 내 프로세스의 모듈이 호출하게 되므로 아래처럼 현재 모듈을 리턴한다.
        // 만일 다른 프로세스에 Inject주입된 후 에라면 해당 다른 프로세스의 모듈이
        // 들어가게 될것이다.
        HMODULE hCallerModule = GetModuleHandle(NULL);
        return (TRUE==
                IAT_ReplaceInOneModule( modulename, lpfnOld, newfunc, hCallerModule ) );
}

typedef int (WINAPI *lpMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);

int WINAPI MyMessageBoxA(
  HWND hWnd,          // handle to owner window
  LPCSTR lpText,     // text in message box
  LPCSTR lpCaption,  // message box title
  UINT uType          // message box style
)
{
        return ((lpMessageBoxA)lpfnOld)( hWnd, "Hooked Message!!", lpCaption, uType );
}

void CTestIAT_ReplaceDlg::OnButton1()
{
        // TODO: Add your control notification handler code here
        ApiHook( "User32.DLL", "MessageBoxA", (FARPROC)MyMessageBoxA );
}

// 버튼1이 눌린 후로는 MyMessageBoxA가 호출되게 될것이다.
void CTestIAT_ReplaceDlg::OnButton2()
{
        // TODO: Add your control notification handler code here
        ::MessageBoxA( NULL, "hello world", "app", MB_OK);
}



----------------------------------------------------------------------------------

오전 12:31 2004-01-17
NT하에서 CreateRemoteThread를 사용하여 특정 DLL을 해당 다른 프로세스에
강제로 Inject 넣는 코드이다. Win9x하에서는 느리지만 GlobalHook을 사용하여
넣을 수 있다.
----------------------------------------------------------------------------------

//---------------------------------------------------------------------------
// ModuleFromAddress
//
// Returns the HMODULE that contains the specified memory address
//---------------------------------------------------------------------------
static HMODULE ModuleFromAddress(PVOID pv)
{
        MEMORY_BASIC_INFORMATION mbi;

        return ((::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
                ? (HMODULE) mbi.AllocationBase : NULL);
}
//---------------------------------------------------------------------------
// IsWindows9x
//
//
//---------------------------------------------------------------------------
static BOOL WINAPI IsWindows9x()
{
        BOOL bResult = FALSE;
        OSVERSIONINFO vi = { sizeof(vi) };

        ::GetVersionEx(&vi);
        if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
                bResult = TRUE;

        return bResult;
}

//
// Attempts to enable SeDebugPrivilege. This is required by use of
// CreateRemoteThread() under NT/2K
//
BOOL EnableDebugPrivilege()
{
        HANDLE           hToken;
        LUID             sedebugnameValue;
        TOKEN_PRIVILEGES tp;

        if ( !::OpenProcessToken(
                GetCurrentProcess(),
                TOKEN_ADJUST_PRIVILEGES | // to adjust privileges
                TOKEN_QUERY,              // to get old privileges setting
                &hToken
                ) )
                //
                // OpenProcessToken() failed
                //
                return FALSE;
        //
        // Given a privilege's name SeDebugPrivilege, we should locate its local LUID mapping.
        //
        if ( !::LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) )
        {
                //
                // LookupPrivilegeValue() failed
                //
                ::CloseHandle( hToken );
                return FALSE;
        }

        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = sedebugnameValue;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        if ( !::AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(tp), NULL, NULL ) )
        {
                //
                // AdjustTokenPrivileges() failed
                //
                ::CloseHandle( hToken );
                return FALSE;
        }

        ::CloseHandle( hToken );
        return TRUE;
}

HMODULE NT_InjectDll( DWORD dwPid, LPCTSTR pszDllFullPath )
{
        static bool _enabled_debug_privilege = false;
        if( _enabled_debug_privilege == false )
        {
                EnableDebugPrivilege();
                _enabled_debug_privilege = true;
        }

        HMODULE hDll                                = NULL;
        BOOL   bResult          = FALSE;
        HANDLE hProcess         = NULL;
        HANDLE hThread          = NULL;
        PSTR   pszLibFileRemote = NULL;
        __try
        {
                if (TRUE == IsWindows9x())
                        __leave;
                
                char szLibFile[MAX_PATH];
                _tcscpy( szLibFile, pszDllFullPath );
                /*
                ::GetModuleFileNameA
                        ModuleFromAddress(GetMsgProc),
                        szLibFile,
                        MAX_PATH
                        );
                */

                /*
                // 이미 Inject DLL이 해당 프로세스에 포함되어있다면
                // Inject 할필요없다.

                BOOL bFound = FALSE;
                CModuleInstance* pModuleInstance;
                for (long i = 0; i < pProcess->GetModuleCount(); i++)
                {
                        pModuleInstance = pProcess->GetModuleByIndex(i);
                        if ( 0 == stricmp(pModuleInstance->Get_Name(), szLibFile) )
                        {
                                bFound = TRUE;
                                break;
                        } // if
                } // for
                if (bFound)
                        __leave;
                */

                // Get a handle for the process we want to inject into
                hProcess = ::OpenProcess(
                        PROCESS_ALL_ACCESS, // Specifies all possible access flags
                        FALSE,
                        dwPid
                        );
                if (hProcess == NULL)
                        __leave;
                
                // Calculate the number of bytes needed for the DLL's pathname
                int cch = 1 + strlen(szLibFile);

                // Allocate space in the remote process for the pathname
                pszLibFileRemote = (PSTR)::VirtualAllocEx(
                        hProcess,
                        NULL,
                        cch,
                        MEM_COMMIT,
                        PAGE_READWRITE
                        );
                if (pszLibFileRemote == NULL)
                        __leave;
                // Copy the DLL's pathname to the remote process's address space
                if (!::WriteProcessMemory(
                        hProcess,
                        (PVOID)pszLibFileRemote,
                        (PVOID)szLibFile,
                        cch,
                        NULL))
                        __leave;
                // Get the real address of LoadLibraryW in Kernel32.dll
                PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
                        ::GetProcAddress(::GetModuleHandle("Kernel32"), "LoadLibraryA");
                if (pfnThreadRtn == NULL)
                        __leave;
                // Create a remote thread that calls LoadLibraryW(DLLPathname)
                hThread = ::CreateRemoteThread(
                        hProcess,
                        NULL,
                        0,
                        pfnThreadRtn,
                        (PVOID)pszLibFileRemote,
                        0,
                        NULL
                        );
                if (hThread == NULL)
                        __leave;
                // Wait for the remote thread to terminate
                ::WaitForSingleObject(hThread, INFINITE);

                ::GetExitCodeThread( hThread, (LPDWORD)&hDll );

                bResult = TRUE;
        }
        __finally
        {
                // Free the remote memory that contained the DLL's pathname
                if (pszLibFileRemote != NULL)
                        ::VirtualFreeEx(hProcess, (PVOID)pszLibFileRemote, 0, MEM_RELEASE);

                if (hThread  != NULL)
                        ::CloseHandle(hThread);

                if (hProcess != NULL)
                        ::CloseHandle(hProcess);
        }
        return hDll;
}

BOOL NT_EjectDll( DWORD dwPid, HMODULE hDll )
{
        BOOL   bResult  = FALSE;
        HANDLE hProcess = NULL;
        HANDLE hThread  = NULL;
        __try
        {
                if (TRUE == IsWindows9x())
                        __leave;
                /*
                //
                // Do not force the server to eject the DLL
                //
                if (process.Get_ProcessId() == ::GetCurrentProcessId())
                        __leave;
                */

                /*
                // Inject DLL이 해당 프로세스에 포함되어있지 않다면
                // Eject할 필요가 없다
                char szLibFile[MAX_PATH];
                ::GetModuleFileNameA(
                        ModuleFromAddress(GetMsgProc),
                        szLibFile,
                        MAX_PATH
                        );
                

                BOOL bFound = FALSE;
                CModuleInstance* pModuleInstance;
                for (long i = 0; i < process.GetModuleCount(); i++)
                {
                        pModuleInstance = process.GetModuleByIndex(i);
                        if ( 0 == stricmp(pModuleInstance->Get_Name(), szLibFile) )
                        {
                                bFound = TRUE;
                                break;
                        } // if
                } // for

                if (!bFound)
                        __leave;
                        */

                // Get a handle for the target process.
                hProcess = ::OpenProcess(
                        PROCESS_ALL_ACCESS, // Specifies all possible access flags
                        FALSE,
                        dwPid
                        );
                if (hProcess == NULL)
                        __leave;
                // Get the real address of LoadLibraryW in Kernel32.dll
                PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
                        ::GetProcAddress(::GetModuleHandle("Kernel32"), "FreeLibrary");
                if (pfnThreadRtn == NULL)
                        __leave;
                // Create a remote thread that calls LoadLibraryW(DLLPathname)
                hThread = ::CreateRemoteThread(
                        hProcess,
                        NULL,
                        0,
                        pfnThreadRtn,
                        hDll,
                        0,
                        NULL
                        );
                if (hThread == NULL)
                        __leave;
                // Wait for the remote thread to terminate
                ::WaitForSingleObject(hThread, INFINITE);

                bResult = TRUE;
        }
        __finally
        {
                if (hThread != NULL)
                        ::CloseHandle(hThread);
                if (hProcess != NULL)
                        ::CloseHandle(hProcess);
        }
        return bResult;
}



CString GetExePath()
{
        TCHAR szExePath[MAX_PATH];

        GetModuleFileName( NULL, szExePath, sizeof(szExePath) );

        TCHAR* pszExeName = _tcsrchr( szExePath, '\\' );
        *pszExeName = '\0';

        return CString( szExePath );
}

HMODULE hInjectDll = NULL;
void CTestInjectNTDlg::OnButton1()
{
        // TODO: Add your control notification handler code here
        CString sPid;
        GetDlgItemText( IDC_PID, sPid );

        CString sDllFullPath;
        sDllFullPath.Format(_T("%s\\Injectdll.dll"), GetExePath() );
        hInjectDll = NT_InjectDll( _ttol(sPid), sDllFullPath );
}

void CTestInjectNTDlg::OnButton2()
{
        // TODO: Add your control notification handler code here
        CString sPid;
        GetDlgItemText( IDC_PID, sPid );
        
        NT_EjectDll( _ttol(sPid), hInjectDll );
}

+ Recent posts