Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
UnhookMe — 동적 Windows API 리졸버 및 언후커로, 후킹된 함수(IAT, EAT, 인라인 패치)를 감지하고 복원하여 레드팀 멀웨어에서 모니터링되지 않은 시스템 호출을 호출합니다. | Kitploit
도구/GitHubGitHub/mgeeky/unhookme
Defensive ToolsRed TeamingPayload Development
GitHubmgeeky/unhookme

UnhookMe

동적 Windows API 리졸버 및 언후커로, 후킹된 함수(IAT, EAT, 인라인 패치)를 감지하고 복원하여 레드팀 멀웨어에서 모니터링되지 않은 시스템 호출을 호출합니다.

저장소 보기
348484년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

UnhookMe - 동적으로 언후킹하는 임포트 리졸버

침입적 AV와 EDR이 향상된 가시성 요구를 위해 실행 중인 프로세스에 핫 패치를 도입하는 시대에, 현대의 공격자는 이러한 감시 체계를 통과할 강력한 도구가 필요합니다. 사용 중인 함수를 실시간으로 언후킹할 수 있는 동적 임포트 리졸버의 제안된 구현은 공격자의 복원력 강화 노력을 위한 또 하나의 단계입니다.

여기서 제안하는 솔루션은 링커가 해결한 WinAPI 임포트(컴파일된 실행 파일의 PE 헤더, 특히 Import Address Table에 표시됨)를 사용하는 대신, 임포트를 동적 방식으로만 해결하는 완전 동적 접근 방식을 채택하는 것입니다. 이러한 동적 리졸버는 운영자의 어떤 지시 없이도 백그라운드에서 발생하는 언후킹 로직을 갖출 수 있습니다.


가장 간단한 사용 예

다음은 언후킹되고 모니터링되지 않은 상태로 MessageBoxW를 호출하는 방법입니다:

root@kitploit:~
    RESOLVE(user32, MessageBoxW);
    _MessageBoxW(0, L"Look Ma! I'm unhooked!", L"Third - Unhooked", 0);

모든 마법은 _MessageBoxW라는 이름의 ImportResolver<T> 객체를 생성하는 RESOLVE 매크로 정의 내에서 발생합니다.


시연

Unhookme showcase animation

다음은 UnhookMe 예제의 작동 방식입니다:

  1. 후킹 대상이 아닌 첫 번째 MessageBoxW를 표시합니다.
  2. 그런 다음 MessageBoxW 프롤로그를 직접 후킹하여 메시지를 표시하지 않고 항상 0을 반환하도록 만듭니다.
  3. 마지막으로 UnhookingImportResolver 리졸버를 사용하여 MessageBoxW를 동적으로 해결합니다. 이 리졸버는 적용된 프롤로그 패치를 감지하고 원래 바이트를 복원하여 MessageBoxW 기능을 효과적으로 언후킹합니다.

메시지 상자를 띄우는 동안 콘솔의 stdout에 출력되는 로그 라인은 다음과 같습니다:

root@kitploit:~
[~] Resolved symbol kernel32.dll!CreateFileA
[~] Resolved symbol kernel32.dll!ReadProcessMemory
[~] Resolved symbol kernel32.dll!MapViewOfFile
[~] Resolved symbol kernel32.dll!VirtualProtectEx
[#] Found trampoline hook in symbol: MessageBoxW . Restored original bytes from file.
[~] Resolved symbol user32.dll!MessageBoxW

사용 방법?

총 5개의 C++ 소스 코드/헤더 파일을 솔루션에 포함해야 합니다. 그러나 메인 프로그램 파일은 아래에 설명된 대로 두 개의 필수 헤더만 포함하면 됩니다.

  • resolver.h - UnhookingImportResolver 구현의 대부분과 편리한 매크로 정의를 포함하는 헤더
  • resolver.cpp - 전역 옵션이 정의된 소스 코드
  • usings.h - 일반적으로 사용되는 WinAPI에 대한 수십 개의 using 타입 정의를 포함하는 하나의 거대하고 지저분한 헤더 파일
  • PE.cpp - 사용자 정의 PE 파서 소스 코드 파일
  • PE.h - 사용자 정의 PE 파서 헤더 파일

필수 헤더

프로그램은 다음 두 헤더만 포함하면 됩니다:

root@kitploit:~
#include "usings.h"
#include "resolver.h"

전역 옵션

리졸버의 작동 방식이나 활동 보고 방식에 영향을 미치는 몇 가지 전역 옵션이 있습니다. 이들은 resolver.cpp 파일의 맨 처음에 정의되어 있습니다:

리졸버 전역 옵션:

  • globalQuietOption - 출력을 전혀 원하지 않으면 true로 설정
  • globalVerboseOption - 상세한 출력을 원하면 true로 설정
  • globalAntiSplicingOption - 해결된 함수가 후킹된 경우 언후킹
  • globalLogFilePath - 출력 로그 라인을 리디렉션할 위치. 비어 있으면 stdout 선택
root@kitploit:~
bool globalQuietOption = false;
bool globalVerboseOption = true;
bool globalAntiSplicingOption = true;

wchar_t globalLogFilePath[MAX_PATH] = L"";

사용자 정의 API 타입 지정

리졸버를 사용하려면 먼저 엄격한 형식의 using 문으로 함수 포인터 타입을 선언해야 합니다:

root@kitploit:~
    using fn_FunctionName = ReturnType WINAPI (
        ParamType1 paramName1,
        ...,
        ParamTypeN paramNameN,
    );

이 저장소에는 수십 개의 인기 Windows API에 대한 미리 정의된 using 타입이 포함된 usings.h 헤더 파일이 함께 제공됩니다.

_FunctionName_은 ImportResolver가 해결하려는 WinAPI에 해당하며, 해당 함수 포인터는 WINAPI 호출 규약(x86에서 __stdcall, x64에서 __fastcall)을 가진 것으로 표시되어야 합니다. _ReturnType_은 WINAPI 타입 수정자 앞에 와야 합니다.

함수 해결 및 사용

위에 지정된 대로 함수 포인터 타입이 정의되면 다음과 같이 사용할 수 있습니다:

root@kitploit:~
    RESOLVE(libraryName, FunctionName);
    ReturnType output = _FunctionName(param1, ..., paramN);

매크로 RESOLVE는 ImportResolver 템플릿 객체를 인스턴스화하고 지정된 라이브러리 이름을 조정합니다.

리졸버는 다양한 상황에서 쉽게 사용할 수 있는 생성자 호출을 제공하는 몇 가지 추가 매크로 정의를 제공합니다:

root@kitploit:~
#define RESOLVE(mod, func)                    RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, ::globalAntiSplicingOption)
#define RESOLVE_NO_UNHOOK(mod, func)          RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, false)

#define RESOLVE_VERBOSE_UNHOOK(mod, func)     RESOLVE_PARAMETERIZED(mod, func, true, true)
#define RESOLVE_VERBOSE_NOUNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, true, false)
#define RESOLVE_NOVERBOSE_UNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, false, true)
#define RESOLVE_NOVERBOSE_NOUNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, false, false)

리졸버의 생성자:

root@kitploit:~
    template<typename Ret, typename ...Args>
    ImportResolver<Ret WINAPI(Args...)>(
            std::string dllName,
            std::string funcName,
            bool _verbose = false,
            bool _unhook = false,
            bool *_wasItHooked = nullptr
        )

작동 방식

기본 리졸버는 사용자 정의 PE 헤더 파서를 활용하여 참조된 모든 DLL 모듈을 처리하고, 해당 모듈의 PE 헤더 무결성과 참조된 함수의 스텁 바이트 무결성을 매핑하고 확인합니다.

아이디어는 다음과 같습니다:

  1. 먼저 GetModuleHandle을 통해 접근할 수 없는 경우 사용자가 참조한 라이브러리(RESOLVE 매크로의 첫 번째 매개변수로 지정된 라이브러리)를 로드하기 위해 LoadLibrary를 호출합니다.

  2. 그런 다음 로드된/참조된 라이브러리의 PE 헤더를 처리하고, 내보내기를 매핑하며, 내보내기 주소 배열을 검색하고 교차 검증을 위해 이러한 주소를 직접 계산합니다.

  3. DLL의 Export Address Table에 정의된 루틴의 주소가 예상과 일치하지 않으면 내보내기가 EAT 후킹된 것으로 간주됩니다. 실행 파일의 Import Address Table(IAT) 항목이 변경되어 DLL 코드 섹션의 올바른 위치를 가리키지 않는 경우에도 마찬가지로 함수가 IAT 후킹된 것으로 간주됩니다.

  4. 지금까지 후크가 발견되지 않았다고 가정하고, 함수 프롤로그의 처음 N 바이트를 가져와 디스크에 저장된 DLL 파일의 내용과 비교합니다. 메모리에서 가져온 바이트와 파일에서 가져온 바이트 사이에 불일치가 있으면 함수가 인라인 패치(핫 패치)된 것으로 간주합니다.

  5. 함수가 후킹된 것으로 간주되면 원래 내보내기 주소(직접 계산한 주소)를 반환하고/또는 항목을 언후킹합니다. 패치 바이트가 있으면 복원합니다.

  6. 마지막으로, 리졸버의 성능 영향을 최적화하기 위해 로드된 모든 모듈의 이미지 베이스와 해결된 함수 주소를 캐시하고, 이후 조회 시 캐시(std::map)에서 반환합니다.

이러한 동적 언후킹 리졸버가 직면한 문제 중 하나는 전달된 API를 탐색하는 문제입니다(DLL에 이 함수가 이 모듈에 구현되지 않았지만 다른 모듈에 있다는 내보내기 썽크가 포함될 수 있음). 이 구현이 이를 지원하지만 가끔 탐색 로직이 깨집니다.


☕ 지원하기 ☕

이 프로젝트와 다른 프로젝트들은 잠 못 이루는 밤과 많은 노력의 결과입니다. 제가 하는 일이 마음에 들고 항상 커뮤니티에 환원하는 것을 감사하게 생각한다면, 커피 한 잔 사주시거나 (또는 맥주 한 잔이 더 좋습니다) 감사 인사를 전해주세요! 💪


저자

root@kitploit:~
   Mariusz Banach / mgeeky, 21
   <mb [at] binary-offensive.com>
   (https://github.com/mgeeky)
도구 다운로드