Microsoft MVP성태의 닷넷 이야기
VC++: 56. Win32 API 후킹 - Trampoline API Hooking [링크 복사], [링크+제목 복사]
조회: 21404
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

Win32 API 후킹 - Trampoline API Hooking

마침, 1월 달 '마이크로소프트웨어' 기사에 보니 '다시 보는 후킹 기법'이 나왔더군요. ^^

다시 보는 후킹 기법
; http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=39157

마지막 부분의 "리스트 2 - IAT 후킹 동작 방식을 알아보는 예제"를 보면 (정상 동작은 하겠지만) 소스 코드 중간에 MessageBox 주소 계산을 0x30C4 라는 상수값으로 넣어둔 것을 볼 수 있습니다. 이러면... 테스트 하는 환경에 따라 달라질 수 있기 때문에 일단 실습은 포기하고.

혹시나 싶어서, 검색을 해보았더니... 오호~~~ "Trampoline API Hooking" 이라는 재미있는 방식이 나돌고 있습니다.

Intercepting System API Calls
; http://software.intel.com/en-us/articles/intercepting-system-api-calls/

(와~~~ 글쓴이가 "Seung-Woo Kim" 라는 한국사람입니다. ^^)

일단, 위의 글에 공개된 InterceptAPI 소스 코드를 복사하고 다음과 같이 사용해 보았습니다.

HMODULE WINAPI TrampolineLoadLibraryW(__in wchar_t * lpFileName);
HMODULE WINAPI NeoLoadLibraryW(__in wchar_t *lpFileName);

HMODULE WINAPI NeoLoadLibraryW(__in wchar_t *lpFileName)
{
    ::OutputDebugString(lpFileName);
    return TrampolineLoadLibraryW(lpFileName);
}

HMODULE WINAPI TrampolineLoadLibraryW(__in  wchar_t *lpFileName)
{
  double  a;
  double  b;
  
  a = 0.0;
  b = 1.0;
  a = a / b;

  return NULL;
}

BOOL InterceptAPI2(const char* c_szDllName, const char* c_szApiName, DWORD dwReplaced, DWORD dwTrampoline, int offset);

void fnIntercept()
{
    InterceptAPI2("kernel32.dll", "LoadLibraryW", (DWORD)&NeoLoadLibraryW, (DWORD)&TrampolineLoadLibraryW, 5);
}

그런데, 제 컴퓨터(Windows 7 x64)에서는 오류가 발생했습니다.

BYTE *pbTargetCode = (BYTE *) dwAddressToIntercept; 
BYTE *pbReplaced = (BYTE *) dwReplaced; 
BYTE *pbTrampoline = (BYTE *) dwTrampoline; 
 
// Change the protection of the trampoline region 
// so that we can overwrite the first 5 + offset bytes. 
VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_WRITECOPY, &dwOldProtect);  <=== 예외 발생

예외 메시지는 그 유명한 AV(Access Violation)!

Unhandled exception at 0x0fc716b7 (Intercept.dll) in Win32App.exe: 0xC0000005: Access violation.

감이 오시죠? ^^

DEP 옵션이 켜져 있었기 때문에 발생한 것으로, 역시나 Visual C++ EXE 프로젝트에서 /NXCOMPAT:NO 옵션을 주면 위의 코드가 정상적으로 동작하는 것을 확인할 수 있었습니다.




하지만, DEP 옵션을 끄는 것은 그다지 바람직한 방법은 아닙니다. 혹시 DEP 옵션을 건드리지 않고 해결할 수는 없을까요?

이를 해결하려고 마이크로소프트웨어에 실렸던 "다시 보는 후킹 기법" 기사와 비교를 해보았습니다. 가만 보니까, VirtualProtect 의 옵션값이 다르더군요.

VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_WRITECOPY, &dwOldProtect);
 * PAGE_WRITECOPY ==> PAGE_EXECUTE_READWRITE

아하... 그래서 반영을 해보았는데, 애석하게도 첫번째 VirtualProtect 의 사용은 정상적으로 실행이 되는 반면, 마지막의 VirtualProtect 에서는 그와 같이 바꿔도 AV 오류가 여전히 발생했습니다.

VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
==> Unhandled exception at 0x75c7432f in Win32App.exe: 0xC0000005: Access violation.

음~~~, 할 수 없이 웹 검색을 해보았습니다. 어느 글에서인가... VirtualProtectEx 함수를 이용하라는 글이 나오더군요. 그래서, 다음과 같이 변경을 해주니... 오~~~ 정말 동작이 됩니다. ^^

HANDLE hHandle = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE, ::GetCurrentProcessId());
VirtualProtectEx(hHandle, (void *) dwAddressToIntercept, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); 




위에 공개된 Trampoline API 후킹의 아쉬운 점이라면 x64 에 대한 지원이 추가되지 않았다는 점입니다. 우선, 소스 코드에는 포인터 주소를 DWORD 로 다루고 있기 때문에 x64 에서는 모두 적합하지 않은 주소를 가리키게 됩니다. 게다가 JMP 문의 주소 지정이 x86 과 x64에서 달라지는 데요. 이에 대해서는 다음의 글을 참고하십시오.

API Hooking: x64 Trampolines 
; http://maliciousattacker.blogspot.com/2008/10/api-hooking-x64-trampolines.html

그리고, 또 한가지 문제가 있습니다. x64 에서는 VirtualProtect 로 변경된 페이지일지라도 직접적인 메모리 쓰기가 지원되지 않습니다. Trampoline API 후킹 소스의 "*pbTargetCode++ = 0xE9;" 와 같은 라인들은 모두 AV 예외가 발생해 버리는데요. 이를 방지하기 위해서는 WriteProcessMemory 를 사용해야만 합니다.

*pbTrampoline++ = 0xE9;
==> 
    BYTE writeByte = 0xE9;
    WriteProcessMemory(hHandle,(LPVOID) pbTrampoline, &writeByte, 1, &BytesWritten);
    pbTrampoline++;

일단... x64 에 대한 지원은 미뤄두고 WriteProcessMemory 정도만을 반영한 소스 코드를 첨부했으니 참고하십시오. ^^

압축을 해제하면 아래와 같이 나오고,

  • Win32App: C++ EXE 프로젝트 (테스트 용)
  • WindowsFormsApplication1: C# 윈폼 프로젝트 (테스트 용)
  • InterceptCOM: C++ Win32 DLL 프로젝트 (Trampoline API 후킹 방식을 이용한 LoadLibrary 가로채기가 구현된 예제)

2개의 EXE 테스트 프로젝트는 '프로젝트 생성시의 기본 소스' 코드이고 단지 InterceptCOM DLL 에서 노출시켜주는 fnInterceptCOM 함수를 호출하는 일 밖에 하지 않습니다. 주요 소스 코드는 "InterceptCOM.cpp" 파일이고, 내용을 아래에 실어두었으니 굳이 프로젝트 받기가 귀찮은 분들은 아래의 내용을 Copy&Paste 해서 사용해도 되겠습니다.

HMODULE WINAPI TrampolineLoadLibraryW(__in wchar_t * lpFileName);
HMODULE WINAPI NeoLoadLibraryW(__in wchar_t *lpFileName);

HMODULE WINAPI NeoLoadLibraryW(__in wchar_t *lpFileName)
{
    ::OutputDebugString(lpFileName);
    return TrampolineLoadLibraryW(lpFileName);
}

HMODULE WINAPI TrampolineLoadLibraryW(__in  wchar_t *lpFileName)
{
  double  a;
  double  b;
  
  a = 0.0;
  b = 1.0;
  a = a / b;

  return NULL;
}

BOOL InterceptAPI2(const char* c_szDllName, const char* c_szApiName, DWORD dwReplaced, DWORD dwTrampoline, int offset);

INTERCEPTCOM_API void fnInterceptCOM()
{
    InterceptAPI2("kernel32.dll", "LoadLibraryW", (DWORD)&NeoLoadLibraryW, (DWORD)&TrampolineLoadLibraryW, 5);
}

BOOL InterceptAPI2(const char* c_szDllName, const char* c_szApiName, DWORD dwReplaced, DWORD dwTrampoline, int offset) 
{ 
    DWORD dwOldProtect = 0; 
    DWORD dwAddressToIntercept = (DWORD)GetProcAddress(GetModuleHandleA((char*)c_szDllName), (char*)c_szApiName); 
 
    BYTE *pbTargetCode = (BYTE *) dwAddressToIntercept; 
    BYTE *pbReplaced = (BYTE *) dwReplaced; 
    BYTE *pbTrampoline = (BYTE *) dwTrampoline; 
 
    HANDLE hHandle = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE, ::GetCurrentProcessId());

    VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_EXECUTE_READWRITE, &dwOldProtect); 

    SIZE_T BytesWritten;

    for (int i = 0; i < offset; i ++)
    {
        WriteProcessMemory(hHandle,(LPVOID) pbTrampoline, pbTargetCode, 1, &BytesWritten);
        pbTrampoline ++;
        pbTargetCode ++;
    }
 
    pbTargetCode = (BYTE *) dwAddressToIntercept; 
 
    BYTE writeByte = 0xE9;
    WriteProcessMemory(hHandle,(LPVOID) pbTrampoline, &writeByte, 1, &BytesWritten);
    pbTrampoline++;
 
    int writeInt = (pbTargetCode+offset) - (pbTrampoline + 4); 
    WriteProcessMemory(hHandle,(LPVOID) pbTrampoline, &writeInt, 4, &BytesWritten);

    VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_EXECUTE, &dwOldProtect); 
 
    VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_WRITECOPY, &dwOldProtect); 
 
    writeByte = 0xE9;
    WriteProcessMemory(hHandle,(LPVOID) pbTargetCode, &writeByte, 1, &BytesWritten);
    pbTargetCode++;

    writeInt = pbReplaced - (pbTargetCode +4); 
    WriteProcessMemory(hHandle,(LPVOID) pbTargetCode, &writeInt, 4, &BytesWritten);

    VirtualProtectEx(hHandle, (void *) dwAddressToIntercept, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
 
    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

    CloseHandle(hHandle);
 
    return TRUE; 
} 




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 2/8/2012 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2013-06-17 01시48분
전략 서신: API 후킹을 할 때에 생각해야 할 것들…
; http://www.jiniya.net/wp/archives/11276
정성태
2013-08-07 06시55분
x64의 경우에는 단순 점프만으로 안되는데요. 다음의 글에서 매우 자세하게 설명되어 있습니다. ^^

Windows x64 binary 모듈 단위 이동
; http://ezbeat.tistory.com/453
정성태
2013-12-11 02시12분
Mhook, an API hooking library, V2.2
; http://codefromthe70s.org/mhook22.aspx
정성태
2015-08-31 04시31분
Debugging walkthrough: Access violation on nonsense instruction, episode 3
; http://blogs.msdn.com/b/oldnewthing/archive/2015/08/28/10638035.aspx
정성태
2020-07-15 09시55분
Windows Hot Patching Mechanism Explained
; https://dev.to/pelock/windows-hot-patching-mechanism-explained-2m1f
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12290정성태8/11/2020121.NET Framework: 931. C# - IP 주소에 따른 국가별 위치 확인파일 다운로드1
12289정성태8/6/2020107개발 환경 구성: 502. Portainer에 윈도우 컨테이너를 등록하는 방법
12288정성태8/5/202043오류 유형: 637. WCF - The protocol 'net.tcp' does not have an implementation of HostedTransportConfiguration type registered.
12287정성태8/5/202034오류 유형: 636. C# - libdl.so를 DllImport로 연결 시 docker container 내에서 System.DllNotFoundException 예외 발생
12286정성태8/5/202033개발 환경 구성: 501. .NET Core 용 container 이미지 만들 때 unzip이 필요한 경우
12285정성태8/4/202065오류 유형: 635. 윈도우 10 업데이트 - 0xc1900209 [2]
12284정성태8/4/202094디버깅 기술: 169. Hyper-V의 VM에 대한 메모리 덤프를 뜨는 방법
12283정성태8/3/202039디버깅 기술: 168. windbg - 필터 드라이버 확인하는 확장 명령어(!fltkd)
12282정성태8/2/202048디버깅 기술: 167. windbg 디버깅 사례: AppDomain 간의 static 변수 사용으로 인한 crash (2)
12281정성태8/2/2020159개발 환경 구성: 500. (PDB 연결이 없는) DLL의 소스 코드 디버깅을 dotPeek 도구로 해결하는 방법
12280정성태8/2/202054오류 유형: 634. 오라클 (평생) 무료 클라우드 VM 생성 후 SSH 접속 시 키 오류 발생
12279정성태7/29/202063개발 환경 구성: 499. 닷넷에서 접근해보는 InterSystems의 Cache 데이터베이스파일 다운로드1
12278정성태8/2/202065VS.NET IDE: 149. ("Binary was not built with debug information" 상태로) 소스 코드 디버깅이 안되는 경우
12277정성태8/2/2020101개발 환경 구성: 498. DEVPATH 환경 변수의 사용 예 - .NET Reflector의 (PDB 연결이 없는) DLL의 소스 코드 디버깅
12276정성태7/23/2020119.NET Framework: 930. 개발자를 위한 닷넷 어셈블리 바인딩 - DEVPATH 환경 변수
12275정성태7/28/2020101개발 환경 구성: 497. 닷넷에서 접근해보는 InterSystems의 IRIS Data Platform 데이터베이스파일 다운로드1
12274정성태7/21/202079개발 환경 구성: 496. Azure - Blob Storage Account의 Location 이전 방법파일 다운로드1
12273정성태7/20/2020208개발 환경 구성: 495. Azure - Location이 다른 웹/DB 서버의 경우 발생하는 성능 하락
12272정성태7/16/2020100.NET Framework: 929. (StrongName의 버전 구분이 필요 없는) .NET Core 어셈블리 바인딩 규칙파일 다운로드1
12271정성태7/16/202081.NET Framework: 928. .NET Framework의 Strong-named 어셈블리 바인딩 (2) - 런타임에 바인딩 리디렉션파일 다운로드1
12270정성태7/16/202075오류 유형: 633. SSL_CTX_use_certificate_file - error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small
12269정성태7/16/202070오류 유형: 632. .NET Core 웹 응용 프로그램 - The process was terminated due to an unhandled exception.
12268정성태7/15/202074오류 유형: 631. .NET Core 웹 응용 프로그램 오류 - HTTP Error 500.35 - ANCM Multiple In-Process Applications in same Process
12267정성태7/15/2020141.NET Framework: 927. C# - 윈도우 프로그램에서 Credential Manager를 이용한 보안 정보 저장파일 다운로드1
12266정성태7/14/2020159오류 유형: 630. 사용자 계정을 지정해 CreateService API로 서비스를 등록한 경우 "Error 1069: The service did not start due to a logon failure." 오류발생
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...