Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
BYOVD-DriverKiller — 드라이버 리버스 & 익스플로잇 | Kitploit
도구/GitHubGitHub/alex3o/byovd-driverkiller
ExploitationReverse EngineeringPost-ExploitationBinary AnalysisLearning & EducationRed Teaming
GitHubalex3o/byovd-driverkiller

BYOVD-DriverKiller

드라이버 리버스 & 익스플로잇

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

BYOVD-DriverKiller

⚠️ 경고: 이 프로젝트는 엄격히 교육 및 데모용입니다. 악의적인 환경에서 사용하기 위한 것이 아닙니다. 목표는 Windows 드라이버의 리버스 엔지니어링 방법론과 익스플로잇 단계를 배우는 것입니다.


여기서는 d1rk(SaadAhla) https://github.com/SaadAhla가 제안한 과제를 해결하기 위해 제가 수행한 과정을 설명합니다. 이 과제는 서명되어 있고 블록리스트(HVCI, LOLBIN...)에 없는 합법적인 드라이버에 대해 리버스 엔지니어링과 익스플로잇을 수행하는 것입니다. 이 Kernel-mode Driver를 통해 시스템에서 실행 중인 임의의 프로세스를 종료할 수 있는 C 프로그램도 제공되며, 그 동작 방식은 아래에서 자세히 설명합니다.

POC-BYOD

📃 사용법: DriverKiller.exe <process_name.exe> [-d]

-d 옵션: 익스플로잇 후 시스템에서 서비스와 드라이버를 삭제합니다.

드라이버의 인증서가 만료되었으므로 대상 머신에서 테스트 서명 모드(Test Signing Mode)가 활성화되어 있어야 합니다.


1부 - 리버스 엔지니어링:

과제는 SHA-256 해시로 이름이 지정된 .sys 파일을 제공합니다. 첫 번째 단계는 이 파일을 IDA로 여는 것입니다.
IDA는 무료로 사용할 수 있습니다. Hex-Rays 웹사이트에서 라이선스를 생성하고 소프트웨어를 다운로드하면 됩니다.

드라이버의 IAT(Import Address Table)를 나열하고 관심 있는 API 호출인 ZwTerminateProcess를 찾는 것부터 시작합니다.

screen1-git

ZwTerminateProcess를 더블클릭하면 IDA가 이 함수의 컴파일된 코드로 이동합니다. 해당 항목을 선택한 다음 cross-references를 표시하면 이 함수를 호출하는 드라이버 함수 목록을 얻을 수 있습니다.

screen2-git

sub_12EF4 함수(오프셋 1CE)가 ZwTerminateProcess를 사용하는 것을 확인할 수 있습니다. 더블클릭하면 IDA가 컴파일된 코드를 표시합니다.

screen11-git

디컴파일된 코드는 ZwOpenProcess(대상 프로세스에 대한 핸들을 여는 호출)와 ZwTerminateProcess(이 핸들을 통해 프로세스를 종료하는 호출)를 보여줍니다.

ZwOpenProcess 문서(https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess)를 확인하면 ClientID 매개변수가 대상 프로세스의 PID를 가리키는 포인터임을 알 수 있습니다.

바로 위 줄에서 ClientId.UniqueProcess는 변수 v22로 초기화됩니다. 이 변수는 바로 위에서 정의됩니다:

v22 = (void )((_QWORD *)i + 10);

이 할당을 이해하려면 변수 i와 +10 필드를 식별해야 합니다.

screen3-git

이 함수의 앞부분에서 SYSTEM_PROCESS_INFORMATION 매개변수를 사용한 ZwQuerySystemInformation 호출을 볼 수 있습니다. 또한 변수 v6와 함께 i가 이 구조체 항목을 반복하는 반복자임을 알 수 있습니다.

ZwQuerySystemInformation 문서(https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation)에 따르면 이 함수는 시스템에서 실행 중인 각 프로세스에 대한 항목을 포함하는 배열을 반환합니다.

SYSTEM_PROCESS_INFORMATION 구조체는 여기에 설명되어 있습니다: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    PVOID Reserved2;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID Reserved3;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG Reserved4;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    PVOID Reserved5;
    SIZE_T QuotaPagedPoolUsage;
    PVOID Reserved6;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

참고: Windows x64에서 일부 유형의 크기

  • ULONG = 4바이트
  • USHORT = 2바이트
  • HANDLE = 8바이트
  • PWSTR = 8바이트
  • KPRIORITY (LONG의 typedef) = 4바이트
  • UNICODE_STRING = 16바이트, 구조는 다음과 같습니다:
  typedef struct _UNICODE_STRING {
    USHORT Length;        -> 2      
    USHORT MaximumLength; -> + 2 = 4
    PWSTR  Buffer;        -> + 8 = 12 (12 n'est pas un multiple de 8 donc padding de 4 ajouté en amont de Buffer) = 16
} UNICODE_STRING;

UniqueProcessId의 오프셋 계산:

    ULONG NextEntryOffset;        -> 4
    ULONG NumberOfThreads;        -> + 4 = 8
    BYTE Reserved1[48];           -> + 48 = 56
    UNICODE_STRING ImageName;     -> + 16 = 72
    KPRIORITY BasePriority;       -> + 4 = 76 (76 n'est pas un multiple de 8 donc padding de 4 ajouté) = 80
    HANDLE UniqueProcessId;       -> + 8 = 88

UniqueProcessId 멤버는 따라서 오프셋 0x50 (10진수 80)에 있습니다.

v22 변수의 할당을 살펴보면 i가 QWORD 포인터(8바이트)로 캐스팅되는 것을 알 수 있습니다:

v22 = (void )((_QWORD *)i + 10);
따라서 v22는 i의 주소 + 10 * 8 = 80바이트에 해당합니다. 따라서 이 변수는 SYSTEM_PROCESS_INFORMATION 구조체에서 가져온 PID를 담고 있습니다.

어떤 PID가 ZwTerminateProcess로 전달될지 알려면 이 할당을 둘러싼 조건을 분석해야 합니다.

screen4-git

프로세스 이미지 이름이 먼저 검색되는 것을 확인할 수 있습니다:

v9 = (wchar_t )((_QWORD *)i + 8);
v9 = i의 주소 + 8 × 8 = 64바이트이기 때문입니다. 이는 ImageName 멤버의 Buffer에 해당합니다. 해당 멤버는 오프셋 56 + 2(USHORT) + 2(USHORT) + 4(padding) = 64에 있기 때문입니다.

아래의 조작과 루프를 고려할 때, 인수(a2)로 전달된 프로세스 이름과 시스템의 활성 프로세스 v9/String 사이의 비교가 수행된다는 가설을 세울 수 있습니다.

sub_1C078(String, v9, (int)v13);
v17 = strupr(a2);
v18 = strupr(String);

따라서 ZwTerminateProcess를 통해 종료할 프로세스 이름을 담고 있는 것은 a2 매개변수입니다. a2가 sub_12EF4 함수의 매개변수임을 알 수 있습니다. 더 진행하려면 이 함수의 참조를 살펴봐야 합니다 (가독성을 위해 ZwTerminateProcessCaller로 이름을 변경했습니다).

screen5-git

ZwTerminateProcessCaller가 오프셋 61A에서 sub_13624 함수에 의해 호출되는 것을 확인할 수 있습니다.

screen6-git

이 디컴파일된 코드를 분석하기 전에, UserMode에서 DeviceIoControl API 호출 후 이 코드가 실제로 사용되는지 확인하기 위해 sub_13624 함수(이름을 ZwTerminateProcessCallerCaller로 변경)의 참조를 찾아보겠습니다.

screen§-git

ZwTerminateProcessCallerCaller가 sub_14130 함수에 의해 호출되는 것을 확인할 수 있습니다 (이름을 ZwTerminateProcessCallerCallerCaller로 변경... 다행히도 엔트리 포인트 전 마지막 함수입니다 😅).

screen7-git

ZwTerminateProcessCallerCallerCaller가 오프셋 306에서 sub_1A4A8 함수에 의해 호출되는 것을 확인할 수 있습니다.

screen8-git

ZwTerminateProcessCallerCallerCaller 함수의 할당을 찾을 수 있습니다:

memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
즉, 이 함수가 MajorFunction 테이블의 모든 항목에 할당된다는 의미입니다 (0x1B = 27, 그리고 주요 IRP는 28개입니다).

screen9-git
도구 다운로드