Microsoft MVP성태의 닷넷 이야기
VC++: 6. Win32 API Hook - 소스는 "공개소스"에있습니다. [링크 복사], [링크+제목 복사],
조회: 22691
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

오늘 자료실에 올린, Win32 API Hook의 압축 파일에 넣어둔 "설명서.txt" 파일의 내용입니다.
보시고, 흥미롭다고 판단되면, 소스를 다운로드 받으셔서 분석해 보시는 것도 좋을 것 같습니다.

/////////////////////////////////////////////////////////////////////



압축을 풀면, 다음과 같은 폴더가 나옵니다.

Bin
class
IBizHook
techlib
TestATL

실제적으로 컴파일해야 하는 프로젝트가 있는 폴더는 단 2개입니다.
IBizHook과 TestATL입니다.

- Bin 폴더는 IBizHook과 TestATL 프로젝트를 컴파일하면, 최종 DLL이 놓여지는 폴더이고, TestSock.htm이라는 예제파일이 있습니다.

- class 폴더는 IAT.cpp, IAT.h 파일이 있습니다. 이 소스는 MSDN 웹 사이트에서 구한 것입니다.

- IBizHook 폴더는 실제로 API Hook을 설치하고, 해제하는 루틴이 들어 있는 COM 객체입니다. 재사용이 가능하도록 되어 있지는 않습니다.
  현재의 IBizHook 모듈안의 소스는 TestATL.DLL을 위해서 제작된 것입니다.

- TestATL 폴더는 소켓 함수를 쓰는 간단한 MsgBox라는 메서드만을 포함하고 있습니다.

- techlib 폴더는 IAT에 대한 설명을 담고 있는 MSDN 사이트 URL입니다.


///////////////////////////////////////////////////////////////////////////////////////


동작 방법.
1 . IBizHook을 컴파일 합니다.
2. TestATL을 컴파일 합니다.
3. Internet Explorer를 실행시켜서 Bin 폴더 아래에 있는 TestSock.htm을 실행시킵니다.
4. "메시지" 버튼이 IE 화면에 나오는데, 클릭합니다.
5. "TEST" 메시지 박스가 뜨는 것이 보입니다.

///////////////////////////////////////////////////////////////////////////////////////

소스 추적
1. TestSock.htm을 먼저 설명드리겠습니다.
  ㄱ. IBizHook.DLL 안에 있는 IBizHook.FuncSock 객체를 생성합니다.
  ㄴ. 생성 후, StartHook 메서드를 호출하는 것을 볼 수 있습니다.
  ㄷ. VC++에서 IBizHook 프로젝트를 열어서, 해당 메서드를 살펴봅니다.
  ㄹ. `Func.cpp 안에 StartHook 메서드를 보면, 간단하다는 것을 알 수 있습니다.
       처음 라인부터 살펴보면,
      m_hookFunc.dwOrdinal = 16; // Ws2_32.dll을 "Depends" 툴로 확인하면,
// recv 함수의 경우, 함수 이름이 export 되어 있지 않고,
// 단지 Ordinal만 나오는 것을 확인할 수 있습니다.
// MSDN 웹사이트의 예제는 함수 이름이 없는 경우는,
// 훅킹할 수 있는 방법이 없었는데, 제가 IAT.cpp를 약간
// 수정해서 "recv" 함수처럼 Ordinal만 있는 경우를 위해서
// 작동이 되도록 했습니다.
// 단지, 주의할 것은 함수명이 있는데도, Ordinal을 지정하면 안되는 경우가 있습니다.
// 그러니, 반드시 함수명이 제공되는 경우는 그냥
// m_hookFunc.szFunc = "recv";
// 처럼 적어주시면 됩니다.

m_hookFunc.pProc = (PROC)hook_recv; // 가로채기를 위해 제작한 함수
// 즉, API StartHook 메서드가 실행된 이후에는
// TestATL.dll에서 불려지는 모든 recv 함수는
// 우선, hook_recv를 불려지게 됩니다.
// hook_recv 안에서는 조건에 따라서 실제 recv 함수를
// 부를 수도 있고, 그렇지 않을 수도 있습니다.

HMODULE hModule = GetModuleHandle( 'TestATL.dll" );
// TestATL.dll의 Import Address Table을 조작하기 위해서
// 해당 Instance Handle을 얻어놓습니다.
// 여기에서 알 수 있듯이, DLL API 가로채기는 특정 DLL
// 을 지정해야만 됩니다.

// 제가 이 예제를 IBizHook과 TestATL로 분리시켜 놓은 것은
// 그것을 명백히 보여드리기 위해서였습니다.
// 위와같이 "TestATL.dll"로 지정을 해놓고,
// IBizHook.DLL 안에서 호출되어지는 "Recv"는 가로챌 수가 없다는 것입니다.
// 왜냐면, DLL들에 대한 API 주소를 담고 있는 Import Address Table은
// PE 파일의 구조에 따라서 각각 하나의 DLL마다 포함하고 있기 때문입니다.

BOOL bRet = HookImportedFunctionsByName( ... );
// 이 함수는 MSDN 웹사이트의 BugSlayer 관련한 기사에 포함된 소스이며,
// 제가 Ordinal만 나타나는 함수의 훅킹을 위한 약간의 수정을 했습니다.
// 수정 부분에 대해서는 "techlib" 폴더의 IAT.html을 방문하시면 나오는
// 페이지에서 제공되는 소스와 비교해서 살펴보시면 됩니다.

// 이 함수는 m_hookFunc에 지정된 DLL API를 함수의 인자로 들어온
// m_pfnOrgRecv 주소로 교체를 합니다.
// 따라서, 이후의 모든 recv 함수 호출은 m_pfnOrgRecv 주소로 우회하게 됩니다.


ㅁ. 위와 같은 규격만 따라 준다면 어떠한 API든지 훅킹이 가능합니다.
ㅂ. 다시, HTML로 돌어가서, StartHook을 부르는 것으로 window.onload는 끝나게 됩니다.

ㅅ. 그 이후에, 사용자가 "메시지" 버튼을 누르게 되면, "TestATL.dll" 안에 구현된 COM 객체의 MsgBox()라는
간단한 메서드를 호출하게 됩니다.
명백히 하기 위해서 VC++ IDE에서 TestATL 프로젝트를 열어서 TestSock.cpp를 확인합니다.
ㅈ. MsgBox 메서드 안에는

SOCKET hSocket = socket( ... );
recv( ... );
closesocket( ... );

위와 같이 있는데, "IBizHook"에서의 FucnSock.cpp 안에 있는 StartHook 메서드에 의해서 recv의
주소를 교체를 했으므로, 위의 코드에서의 recv 함수 호출은 FuncSock.cpp 안에 있는
hook_recv( ... );
함수를 호출하게 됩니다.

그럼, 이제 VC++ IDE에서 FuncSock.cpp 파일을 열어봅니다.

hook_recv 함수의 구현을 보면,

int iRet = m_pfnOrgRecv( s, buf, len, flags );
::MessageBox( ... );
return iRet;

와 같이 되어 있는 것을 확인할수 있습니다.
물론, 위의 경우는 테스트를 위한 경우이기 때문에 간단합니다.
하고자 한다면, 조건에 따라서 recv 함수를 호출하지 않게도 할 수 있거나,
모든 소켓 입력에 대해서 따로 로그 파일에 남겨둘 수 도 있습니다.


ㅊ. window.onunload에 보면 EndHook이 있습니다.
즉, 설치해 놓은 주소를 다시 제거하는 것입니다. 만약, Application이 정말 이와 같이
종료를 수반한다면, 굳이 EndHook을 할 필요는 없을 것입니다. 하지만,
설정/해제의 쌍을 맞추는 것은 좋은 습관일 것입니다.





///////////////////////////////////////////////////////////////////////////////////////


못다한 얘기.

1. 원래의 MSDN 소스에는 함수 이름만을 이용해서 API Hook을 하도록 되어 있었습니다.
  원저작자가 Ordinal만 있는 경우 API Hook을 못하게 한 것은 실력이 없어서가 아님을
  저는 분명히 확신합니다.
  어쨌든, 제 편의를 위해서 저는 그 소스를 Ordinal로도 가능하게 바꾸었습니다.

  나중에 테스트를 해보니, CoCreateInstance의 경우, 이름으로는 API Hook이 되는데,
  Ordinal로는 안되었습니다.

  그런데, 제가 이 소스를 배운 이후에 국내의 "마이크로 소프트웨어" 잡지 1999.12월 호를
  우연한 기회에 보게 되었습니다. 거기 보면, "네 이웃의 메모리를 탐하지 말라"라는 제목으로
  DLL API Hook에 대한 설명이 나옵니다. 그 기사의 저자는 "김성우(한국 통신 무료전화 개발팀장)",
  "김상민(고려대 전자공학과 하나와영 7기)" 두 분이었습니다.
  중요한 것은 그 예제를 보고는, "아차!" 했습니다. Ordinal도, Export 함수 이름도 필요 없이,
  그냥 GetProcAddress로 기존 recv 함수의 주소를 알아내고, IAT를 열람하면서 그 주소와 똑같은
  엔트리를 변경시켜 주면 그만이기 때문입니다.

  즉, 실제 GetProcAddress 함수의 역할이 HookImportedFunctionsByName에 있는 것과 동일하다는 것입니다.
  내부적으로 GetProcAddress 역시, IAT를 돌면서 인자로 들어온 함수 이름과 똑같은 Entry를 찾기 때문입니다.

  단지, 그 기사에서는 다소간의 Assembly와 기계어 구조를 알아야 하는 설명이 첨가되어 있었습니다.
  종합해 보면, 그 두 가지 기사 간에는 반쪽씩 장/단점이 있는 것입니다.

  제가 그 2가지를 시간이 나면 합쳐보려고 했는데, 사실, ordinal과 함수 이름만으로도 잘 동작이
  되는 제 소스완성으로 이렇게 시간만 끌게 되었네요. 상당히 간단한(!) 문제인데도, 교체를 하기가
  상당히(!) 귀찮네요.
  혹시나, 이 글을 읽고 계시는 분들이 잠깐의 실습겸 시간을 내서 고쳐보시면 이해에 한층 더 도움이
  되시리라 확신합니다.

2. 기회가 되시면 techlib 폴더에 있는 IAT.html을 꼭 방문해보시고, 기사를 읽어보시는 것도 좋을 것입니다.
  물론, "마이크로 소프트웨어" 잡지 1999.12월 호를 구해서 보는 것이 가장 좋은 방법일 것입니다.
  그렇긴 하지만, ^^ IAT.html은 무료이고,,, "마소" 잡지는 유료이기 때문에.

3. 전체적으로, 소스가 간결합니다.
  VC++을 하시는 분이라면, 다른 언어로는 근접하기 어려운 이런 유의 기술을 습득하시는 것이,
  자신을 남들과 차별화시키는 한 방법일 것 같습니다.
  사실, VC++로 UI를 만드는 것은 정말이지, 인생의 낭비가 아닐까 싶네요.

4. 응용 분야. 요즘 들어서 Plug-In을 지원하는 Application이 많이 늘고 있습니다.
  즉, DLL을 해당 프로세스 경계 안으로 들어갈 수 있도록 하는 것이죠. 결과적으로, 그것으로 인해
  우리가 만든 DLL이 원래 EXE의 무제한에 가까운 제어를 할 수 있는 가능성이 생기는 것입니다.
  COM 객체를 띄워주는 Internet Explorer는 그 좋은 실험 대상일 것입니다.

5. 이 예제에서는 반드시 "EXE"가 DLL을 불러줄 수 있는 상황이어야 합니다.
  하지만, 모든 프로세스를 대상으로 제어를 하고 싶다면, "마이크로 소프트웨어" 잡지의 1999.12월 호 기사의
  이전 기사를 참고하시면 좋을 것 같습니다. 그 기사 외에도 "Programming Applications for Micorosoft Windows, 4th Edition"
  에서도 DLL Injection에 대해서 자세히 설명이 되어 있습니다. 며칠 전에 이 책의 번역서가
  나왔다는 반가운 소식이 있네요. 꼭 한번 보시길 바랍니다.


///////////////////////////////////////////////////////////////////////////////////////

참고 서적 및 방향

1. 사실, 급하게 API Hook을 써야 할 상황이라면, 이 예제 정도로만 이해하시고 써도 크게 무리는 없습니다.
  하지만, 말려도 말려도 무엇인가를 하고자 하시는 분들. 그런 분들께 대강의 길을 제시합니다.

  ㄱ. 우선, PE 파일의 구조 이해가 선행되어야 합니다.
  PE 파일은 Win32 응용 프로그램이 디스크에 저장되는 바로 그 EXE 파일의 구조입니다.
  MS는 가상 메모리 Swap 파일의 크기 및 운용 효율성을 위해서 실행 파일의 경우에는,
  따로 요구가 없는 한, 코드 및 데이터를 가상 메모리에 두지 않고, 그냥 디스크에 보관된
  내용을 Memory Mapped 기법으로 사용을 합니다.
  그것이 바로, EXE 프로그램이 실행되었을 때, 해당 EXE 파일이 "잠겨지는" 원인인 것입니다.


  이것을 위해서,
  MSDN Magazine의 기사 "An In-Depth Look into the Win32 Portable Executable File Format"을
  보시면 도움이 되실 것입니다. 두 달에 걸채서 연재되었습니다. 기사는 아래의 URL에서
  볼 수 있습니다.

  https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail
  https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2

ㄴ. Windows Loader에 대한 이해도 아주 "조금은" 하시는 것도 좋습니다.
  물론, 이 부분은 건너 뛰셔도 좋습니다. 하지만, "지치지 않는 열정"을 가지신 분들. 보시려면...
  보십시오. ^^

  Win32 Loader에 대해서 Pseudo 코드로 기능을 설명하고 있습니다.
  http://www.microsoft.com/msj/defaultframe.asp?page=/msj/0999/hood/hood0999.htm
  

  아래의 기사는 별로 도움이 되지 않을 것 같지만, 그래도 추천을 해봅니다.
  MSDN Magazine의 기사 "What Goes on Inside Windows 2000 : Solving the Mysteries of the Loader"
  를 아래의 URL에서 확인할 수 있습니다.
  https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/windows-2000-loader-what-goes-on-inside-windows-2000-solving-the-mysteries-of-the-loader

ㄷ. 위에서 이미 한 번씩 언급을 해드렸는데, 다시 한번 적습니다.
  책 : "Programming Applications for Micorosoft Windows, 4th Edition"
  잡지 : "마이크로 소프트웨어 1999.12월 호"
  









[최초 등록일: ]
[최종 수정일: 6/27/2021]

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

비밀번호

댓글 작성자
 



2008-03-21 05시19분
kevin25
2014-10-07 05시15분
정성태

... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13240정성태2/1/20234165디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233844디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235950.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235637.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20235171개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234744개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
13234정성태1/28/20235853개발 환경 구성: 661. dnSpy를 이용해 소스 코드가 없는 .NET 어셈블리의 코드를 변경하는 방법 [1]
13233정성태1/28/20237238오류 유형: 840. C# - WebClient로 https 호출 시 "The request was aborted: Could not create SSL/TLS secure channel" 예외 발생
13232정성태1/27/20234945스크립트: 43. uwsgi의 --processes와 --threads 옵션
13231정성태1/27/20233943오류 유형: 839. python - TypeError: '...' object is not callable
13230정성태1/26/20234304개발 환경 구성: 660. WSL 2 내부로부터 호스트 측의 네트워크로 UDP 데이터가 1개의 패킷으로만 제한되는 문제
13229정성태1/25/20235314.NET Framework: 2090. C# - UDP Datagram의 최대 크기
13228정성태1/24/20235442.NET Framework: 2089. C# - WMI 논리 디스크가 속한 물리 디스크의 정보를 얻는 방법 [2]파일 다운로드1
13227정성태1/23/20235119개발 환경 구성: 659. Windows - IP MTU 값을 바꿀 수 있을까요? [1]
13226정성태1/23/20234801.NET Framework: 2088. .NET 5부터 지원하는 GetRawSocketOption 사용 시 주의할 점
13225정성태1/21/20234006개발 환경 구성: 658. Windows에서 실행 중인 소켓 서버를 다른 PC 또는 WSL에서 접속할 수 없는 경우
13224정성태1/21/20234431Windows: 221. Windows - Private/Public/Domain이 아닌 네트워크 어댑터 단위로 방화벽을 on/off하는 방법
13223정성태1/20/20234605오류 유형: 838. RDP 연결 오류 - The two computers couldn't connect in the amount of time allotted
13222정성태1/20/20234295개발 환경 구성: 657. WSL - DockerDesktop.vhdx 파일 위치를 옮기는 방법
13221정성태1/19/20234461Linux: 57. C# - 리눅스 프로세스 메모리 정보파일 다운로드1
13220정성태1/19/20234541오류 유형: 837. NETSDK1045 The current .NET SDK does not support targeting .NET ...
13219정성태1/18/20234160Windows: 220. 네트워크의 인터넷 접속 가능 여부에 대한 판단 기준
13218정성태1/17/20234079VS.NET IDE: 178. Visual Studio 17.5 (Preview 2) - 포트 터널링을 이용한 웹 응용 프로그램의 외부 접근 허용
13217정성태1/13/20234689디버깅 기술: 185. windbg - 64비트 운영체제에서 작업 관리자로 뜬 32비트 프로세스의 덤프를 sos로 디버깅하는 방법
13216정성태1/12/20234947디버깅 기술: 184. windbg - 32비트 프로세스의 메모리 덤프인 경우 !peb 명령어로 나타나지 않는 환경 변수
13215정성태1/11/20236597Linux: 56. 리눅스 - /proc/pid/stat 정보를 이용해 프로세스의 CPU 사용량 구하는 방법 [1]
... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...