NtUtils 는 자주 사용되는 코드 조각 및 지능형 데이터 유형과 결합되어 일반 Winapi/Ntapi 헤더보다 더 나은 오류 처리 및 언어 통합 기능을 갖춘 일련의 기능을 제공하는 Delphi의 Windows 시스템 프로그래밍용 프레임워크입니다.
전용 저장소 에서 몇 가지 예제 코드를 찾을 수 있습니다.
라이브러리는 총 3개의 레이어로 구성된 레이어 구조를 가지고 있습니다.
Winapi.*.pas
와 라이브러리 Ntapi.*.pas
헤더를 혼합하는 것이 가능합니다. 하지만 이름이 충돌하는 경우 네임스페이스 접두사를 명시적으로 지정해야 할 수도 있습니다.System.SysUtils
, System.Rtti
및 System.Generics.Collections
에 따라 다릅니다.따라서 최신 무료 버전의 Delphi에는 필요한 모든 것이 이미 포함되어 있습니다. 보너스로 RTTI(일명 리플렉션) 없이 콘솔 응용 프로그램을 컴파일하면 실행 파일이 매우 작아집니다. 자세한 내용은 예시를 참조하세요.
라이브러리의 모든 파일을 프로젝트에 포함시키는 것은 일반적으로 중복되므로 파일 자동 검색을 위해 Delphi를 구성할 수 있습니다. 이런 방식으로 uses
섹션에서 유닛을 지정할 수 있으며, Delphi는 해당 유닛과 해당 종속성을 프로젝트에 자동으로 포함합니다. Delphi가 검색을 수행하는 폴더를 구성하려면 프로젝트 -> 옵션 -> 빌딩 -> Delphi Compiler로 이동하여 검색 경로에 다음 줄을 추가하세요.
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
프로젝트의 폴더 이름이나 위치가 다른 경우 해당 줄을 적절하게 조정해야 합니다.
라이브러리는 실패한 TNtxStatus 값을 반환하여 호출자에게 실패를 나타냅니다. TNtxStatus
(NtUtils.pas에 정의됨)는 오류 코드( NTSTATUS
, HRESULT
및 Win32 오류와 호환 가능)와 실패 위치, 스택 추적 및 시도된 작업의 특성에 대한 메타데이터를 저장하는 구조입니다. 공개 호출에 대한 예상/요청된 액세스 마스크 또는 쿼리/설정 호출에 대한 정보 클래스 값과 같은 기타 세부 정보입니다. TNtxStatus
성공했는지 확인하려면 IsSuccess
메서드를 사용하세요. 기본 오류 코드에 액세스하거나 설정하려면(해당 유형 및 호출자의 기본 설정에 따라) Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
등과 같은 속성을 사용합니다.
예외 사용을 선호하는 경우 언제든지 주어진 TNtxStatus
에 대해 RaiseOnError()
호출할 수 있습니다. System.SysUtils
가져오지 않고(가능한 경우) 실제로 예외를 사용하려는 경우가 아니라면 전용 ENtError
예외 클래스(내장 EOSError
에서 파생됨)를 가져오는 NtUiLib.Exceptions를 포함하는 것이 더 좋습니다.
NtUiLib.Errors는 TNtxStatus
값을 문자열로 표시하기 위한 네 가지 메서드를 연결합니다. 예를 들어, 값이 0xC0000061
인 오류가 토큰의 세션 ID를 변경하려는 시도에서 발생한 경우 이러한 메서드는 다음 정보를 반환합니다.
방법 | 반환된 문자열 |
---|---|
Name | STATUS_PRIVILEGE_NOT_HELD |
Description | A required privilege is not held by the client |
Summary | Privilege Not Held |
ToString | NtSetInformationToken returned STATUS_PRIVILEGE_NOT_HELD |
더 나아가 사용자에게 예쁜 메시지 상자를 표시하려면 NtUiLib.Errors.Dialog가 ShowNtxStatus()
제공합니다. 또한 NtUiLib.Exceptions.Dialog를 포함하면 필요한 반영 지원이 제공되고 대화가 더욱 풍부해집니다. 다음은 그 모습에 대한 예입니다.
TNtxStatus
스택 추적 캡처를 지원합니다(기본적으로 비활성화됨). 이를 활성화하려면 NtUtils.CaptureStackTraces
True로 설정하세요. 의미 있는 방식으로 스택 추적을 표시하려면 실행 파일에 대한 디버그 기호 생성을 구성해야 한다는 점을 명심하세요. 불행하게도 Delphi는 일반적으로 충분하지 않은 .map
파일(프로젝트 -> 옵션 -> 빌딩 -> Delphi 컴파일러 -> 링크 -> 맵 파일을 통해 구성)만 출력할 수 있습니다. 기호 API가 이를 이해할 수 있도록 이를 .dbg
파일로 변환하려면 타사 map2dbg 도구가 필요합니다. .dbg
파일이면 충분할 수 있지만 cv2pdb 를 통해 최신 .pdb
로 변환하여 추가로 처리하는 것이 더 좋습니다.
디버그 기호를 자동으로 생성하려면 프로젝트에 다음 빌드 후 이벤트를 추가하세요.
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
Delphi에는 가비지 수집기가 포함되어 있지 않으므로 레코드, 문자열, 동적 배열, 인터페이스 등 몇 가지 유형만 기본적으로 관리됩니다. 반면에 클래스와 포인터는 안전한 형식으로 try-finally 블록을 사용해야 하는 명시적인 정리가 필요하므로 프로그램이 상당히 복잡해집니다. 이 문제를 해결하기 위해 라이브러리에는 DelphiUtils.AutoObjects에 구현된 메모리 및 기타 리소스에 대한 자동 수명 관리 기능이 포함되어 있습니다. 이 모듈의 유형을 사용하여 참조를 계산하고 함수 에필로그에서 객체를 자동으로 해제하기 위한 예외로부터 안전한 코드를 자동으로 생성하도록 컴파일러에 지시합니다. 이 모듈은 정리가 필요할 수 있는 다양한 유형의 리소스에 대한 여러 인터페이스를 정의합니다. 다음과 같은 계층 구조가 도입됩니다.
그래프 LR;
하위 그래프 id1[모든 리소스]
IAutoReleasable
끝
하위 그래프 id2[A THandle 값]
IHandle
끝
하위 그래프 id3[A Delphi 클래스]
IAutoObject[IAutoObject<T>]
끝
하위 그래프 id4[A 포인터]
IAutoPointer[IAutoPointer<P>]
끝
하위 그래프 id5[A 메모리 영역]
IMemory[IMemory<P>]
끝
IAutoReleasable --> IHandle;
IAutoReleasable --> IAutoObject;
IAutoReleasable --> IAutoPointer;
IAutoPointer --> IMemory;
IAutoReleasable
은 (자동) 정리에 대한 조치를 취해야 하는 모든 리소스의 기본 유형입니다. IHandle
THandle 값으로 정의된 리소스에 대한 래퍼 역할을 합니다. IAutoObject<T>
는 Delphi 클래스(즉, TObject에서 파생된 모든 클래스)를 자동으로 릴리스하기 위한 일반 래퍼입니다. IAutoPointer<P>
동적으로 할당된 포인터를 해제하기 위한 유사한 인터페이스를 정의합니다(영역 크기는 관련이 없음). IMemory<P>
관리형 및 비관리형 박스형 레코드와 같이 형식화된 포인터를 통해 액세스할 수 있는 알려진 크기의 메모리 영역에 대한 래퍼를 제공합니다.
이 시설을 사용하는 방법은 다음과 같습니다.
인터페이스 중 하나를 사용하여 객체에 대한 소유권(잠재적으로 공유되는)을 유지해야 하는 모든 변수를 정의합니다.
자동 개체를 할당/복사/캡처하려면 Auto 도우미를 사용하세요.
필요한 경우 유형 정보 중복을 방지하고 구문을 단축할 수 있는 왼쪽 캐스팅을 사용합니다.
예를 들어, 다음은 고전적인 접근 방식을 사용하여 TStringList로 작업하기 위한 안전한 코드입니다.
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
상상할 수 있듯이 이 함수에 더 많은 개체를 사용하면 복잡성이 비선형적으로 크게 증가합니다. 또는 IAutoObject를 사용하고 훨씬 더 효과적으로 확장하는 동등한 코드는 다음과 같습니다.
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
컴파일러는 필요한 정리 코드를 함수 에필로그에 내보내고 예외가 발생하더라도 실행되도록 합니다. 또한 이 접근 방식을 사용하면 기본 개체에 대한 공유 소유권을 유지할 수 있으므로 현재 함수보다 오래 지속될 수 있는 참조를 저장할 수 있습니다(예: 익명 함수에서 캡처하고 반환하는 방식). 이 기능이 필요하지 않고 함수가 종료될 때 객체를 해제하는 단일 소유자를 유지하려는 경우 구문을 더욱 단순화할 수 있습니다.
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
이 코드는 여전히 초기 코드와 동일합니다. 내부적으로는 인터페이스를 저장하고 나중에 개체를 해제하는 숨겨진 지역 변수를 생성합니다.
동적 메모리 할당 작업 시 다음과 같이 왼쪽 캐스팅을 사용하는 것이 편리할 수 있습니다.
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
값 유형을 참조 유형인 것처럼 공유할 수 있는 박스형(힙에 할당됨) 관리 레코드를 생성할 수도 있습니다. Delphi 문자열 및 동적 배열과 같은 관리되는 필드도 포함될 수 있습니다. 컴파일러는 자동으로 해제하기 위한 코드를 내보냅니다.
type
TMyRecord = record
MyInteger: Integer;
MyArray: TArray<Integer>;
end ;
PMyRecord = ^TMyRecord;
var
x: IMemory<PMyRecord>;
begin
IMemory(x) := Auto.Allocate<TMyRecord>;
x.Data.MyInteger := 42 ;
x.Data.MyArray := [ 1 , 2 , 3 ];
end ;
Delphi는 참조 계산을 사용하므로 두 개체에 순환 종속성이 있으면 메모리 누수가 여전히 발생할 수 있습니다. 약한 참조를 사용하면 이런 일이 발생하는 것을 방지할 수 있습니다. 이러한 참조는 수명 연장에 포함되지 않으며 이를 저장하는 변수는 대상 객체가 소멸되면 자동으로 nil이 됩니다. 약한 참조를 사용하려면 먼저 강한 참조로 업그레이드해야 합니다. 자세한 내용은 DelphiUtils.AutoObjects의 Weak<I>를 참조하세요.
일반적으로 사용되는 가변 크기 포인터 유형에 사용할 수 있는 몇 가지 별칭이 있습니다. 다음은 몇 가지 예입니다.
핸들은 위에서 설명한 논리를 따르는 IHandle 유형(DelphiUtils.AutoObjects 참조)을 사용하므로 명시적으로 닫을 필요가 없습니다. 코드 가독성을 위해서만 사용할 수 있는 IHandle의 일부 별칭(IScmHandle, ISamHandle, ILsaHandle 등)도 찾을 수 있습니다.
핸들 값의 소유권을 IHandle로 가져와야 하는 경우 이 인터페이스를 구현하는 클래스와 기본 리소스를 해제하는 방법을 알고 있어야 합니다. 예를 들어, NtUtils.Objects는 NtClose
호출이 필요한 커널 객체에 대한 클래스를 정의합니다. 또한 Auto
에 도우미 메서드를 연결하여 Auto.CaptureHandle(...)
통해 값으로 커널 핸들을 캡처할 수 있습니다. 소유하지 않는 IHandle을 만들려면 Auto.RefHandle(...)
사용하세요.
레코드, 클래스 및 열거형의 이름은 T
로 시작하고 CamelCase를 사용합니다(예: TTokenStatistics
). 레코드 또는 기타 값 유형에 대한 포인터는 P
로 시작합니다(예: PTokenStatistics
). 인터페이스 이름은 I
로 시작합니다(예: ISid
). 상수는 ALL_CAPITALS를 사용합니다. 알려진 공식 이름(예: Windows SDK에 정의된 유형)이 있는 헤더 계층의 모든 정의는 이 이름을 지정하는 SDKName
특성으로 표시됩니다.
대부분의 함수는 다음과 같은 이름 규칙을 사용합니다. 끝에 x가 있는 하위 시스템의 접두사(Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Action + Target/Object 유형/등. 함수 이름에도 CamelCase를 사용합니다.
라이브러리는 Windows 7 이상, 32비트 및 64비트 버전을 모두 대상으로 합니다. 그러나 일부 기능은 최신 64비트 버전의 Windows 11에서만 사용할 수 있습니다. 일부 예로는 AppContainers 및 ntdll syscall unhooking이 있습니다. 라이브러리 함수가 Windows 7에 없을 수 있는 API에 의존하는 경우 지연된 가져오기를 사용하고 런타임에 가용성을 확인합니다.
Delphi에는 라이브러리가 NtUiLib 레이어 내에서 활용하는 풍부한 반사 시스템이 함께 제공됩니다. 헤더 레이어에 정의된 대부분의 유형은 이를 달성하기 위해 사용자 정의 속성(DelphiApi.Reflection 참조)으로 장식됩니다. 이러한 장식은 라이브러리가 런타임에 복잡한 데이터 유형(예: PEB, TEB, USER_SHARED_DATA)을 정확하게 표현하고 한 줄의 코드로 놀라운 보고서를 생성하는 데 도움이 되는 유용한 메타데이터를 생성합니다.
다음은 NtUiLib.Reflection.Types를 사용하는 Ntapi.NtSecApi의 TSecurityLogonSessionData
를 표현한 예입니다.
다음은 다양한 모듈의 목적에 대한 개요입니다.
지원 유닛 | 설명 |
---|---|
DelphiUtils.AutoObjects | 자동 리소스 수명 관리 |
DelphiUtils.AutoEvents | 다중 구독자 익명 이벤트 |
DelphiUtils.Arrays | TArray 도우미 |
DelphiUtils.Lists | 유전적 이중 연결 목록 기본 요소 |
DelphiUtils.Async | 비동기 I/O 지원 정의 |
DelphiUtils.ExternalImport | Delphi 외부 키워드 IAT 도우미 |
DelphiUtils.RangeChecks | 범위 확인 도우미 |
NtUtils | 일반적인 라이브러리 유형 |
NtUtils.SysUtils | 문자열 조작 |
NtUtils.Errors | 오류 코드 변환 |
NtUiLib.Error | 오류 코드 이름 조회 |
NtUiLib.예외 | SysUtils 예외 통합 |
DelphiUiLib.Strings | 문자열 다듬기 |
DelphiUiLib.Reflection | 기본 RTTI 지원 |
DelphiUiLib.Reflection.Numeric | 숫자 유형의 RTTI 표현 |
DelphiUiLib.Reflection.Records | 레코드 유형의 RTTI 표현 |
DelphiUiLib.Reflection.Strings | 문자열의 RTTI 구체화 |
NtUiLib.Reflection.Types | 일반 유형에 대한 RTTI 표현 |
NtUiLib.Console | 콘솔 I/O 도우미 |
NtUiLib.TaskDialog | TaskDialog 기반 GUI |
NtUiLib.Errors.Dialog | GUI 오류 대화 상자 |
NtUiLib.Exceptions.Dialog | GUI 예외 대화상자 |
시스템 유닛 | 설명 |
---|---|
NtUtils.ActCtx | 활성화 컨텍스트 |
NtUtils.AntiHooking | 연결 해제 및 직접 시스템 호출 |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | CSRSS/SxS 등록 |
NtUtils.DbgHelp | DbgHelp 및 디버그 기호 |
NtUtils.디버그 | 디버그 객체 |
NtUtils.Dism | DISM API |
NtUtils.환경 | 환경변수 |
NtUtils.Environment.User | 사용자 환경 변수 |
NtUtils.Environment.Remote | 다른 프로세스의 환경 변수 |
NtUtils.파일 | Win32/NT 파일 이름 |
NtUtils.Files.Open | 파일 및 파이프 열기/생성 |
NtUtils.Files.Operations | 파일 작업 |
NtUtils.Files.Directories | 파일 디렉터리 열거 |
NtUtils.Files.FltMgr | 필터 관리자 API |
NtUtils.Files.Mup | 다중 UNC 공급자 |
NtUtils.Files.Volumes | 볼륨 작업 |
NtUtils.Files.Control | FSCTL 운영 |
NtUtils.ImageHlp | PE 파싱 |
NtUtils.ImageHlp.Syscalls | 시스템콜 번호 검색 |
NtUtils.ImageHlp.DbgHelp | DbgHelp가 없는 공개 기호 |
NtUtils.Jobs | 작업 개체 및 사일로 |
NtUtils.Jobs.Remote | 크로스 프로세스 작업 개체 쿼리 |
NtUtils.Ldr | LDR 루틴 및 구문 분석 |
NtUtils.Lsa | LSA 정책 |
NtUtils.Lsa.Audit | 감사 정책 |
NtUtils.Lsa.Sid | SID 조회 |
NtUtils.Lsa.로그온 | 로그온 세션 |
NtUtils.매니페스트 | Fusion/SxS 매니페스트 빌더 |
NtUtils.Memory | 메모리 작업 |
NtUtils.MiniDump | 미니덤프 형식 구문 분석 |
NtUtils.Objects | 커널 개체 및 핸들 |
NtUtils.Objects.스냅샷 | 스냅샷 처리 |
NtUtils.Objects.네임스페이스 | NT 객체 네임스페이스 |
NtUtils.Objects.Remote | 크로스 프로세스 핸들 작업 |
NtUtils.Objects.비교 | 핸들 비교 |
NtUtils.패키지 | 앱 패키지 및 패키지 제품군 |
NtUtils.Packages.SRCache | 상태 저장소 캐시 |
NtUtils.Packages.WinRT | WinRT 기반 패키지 정보 |
NtUtils.Power | 전원 관련 기능 |
NtUtils.프로세스 | 프로세스 객체 |
NtUtils.Processes.Info | 쿼리/설정 정보 처리 |
NtUtils.Processes.Info.Remote | 코드 삽입을 통한 쿼리/설정 처리 |
NtUtils.Processes.Modules | 크로스 프로세스 LDR 열거 |
NtUtils.Processes.Snapshots | 프로세스 열거 |
NtUtils.Processes.Create | 공통 프로세스 생성 정의 |
NtUtils.Processes.Create.Win32 | Win32 프로세스 생성 방법 |
NtUtils.Processes.Create.Shell | 쉘 프로세스 생성 방법 |
NtUtils.Processes.Create.Native | NtCreateUserProcess 및 공동. |
NtUtils.Processes.Create.Manual | NtCreateProcessEx |
NtUtils.Processes.Create.Com | COM 기반 프로세스 생성 |
NtUtils.Processes.Create.Csr | SbApiPort를 통한 프로세스 생성 |
NtUtils.Processes.Create.Package | Appx 활성화 |
NtUtils.Processes.Create.Remote | 코드 삽입을 통한 프로세스 생성 |
NtUtils.Processes.Create.Clone | 프로세스 복제 |
NtUtils.Profiles | 사용자 및 AppContainer 프로필 |
NtUtils.Registry | 레지스트리 키 |
NtUtils.Registry.Offline | 오프라인 하이브 조작 |
NtUtils.Registry.VReg | 사일로 기반 레지스트리 가상화 |
NtUtils.Sam | SAM 데이터베이스 |
NtUtils.섹션 | 섹션/메모리 투영 객체 |
NtUtils.보안 | 보안 설명자 |
NtUtils.Security.Acl | ACL 및 ACE |
NtUtils.Security.Sid | SID |
NtUtils.Security.AppContainer | AppContainer 및 기능 SID |
NtUtils.셸코드 | 코드 주입 |
NtUtils.Shellcode.Dll | DLL 주입 |
NtUtils.Shellcode.Exe | EXE 주입 |
NtUtils.Svc | SCM 서비스 |
NtUtils.Svc.SingleTaskSvc | 서비스 구현 |
NtUtils.동기화 | 동기화 프리미티브 |
NtUtils.시스템 | 시스템 정보 |
NtUtils.TaskScheduler | 작업 스케줄러 |
NtUtils.Threads | 스레드 객체 |
NtUtils.Tokens.Info | 스레드 쿼리/설정 정보 |
NtUtils.Threads.Worker | 스레드 작업자(스레드 풀) |
NtUtils.Tokens | 토큰 객체 |
NtUtils.Tokens.Impersonate | 토큰 가장 |
NtUtils.Tokens.로그온 | 사용자 및 S4U 로그온 |
NtUtils.Tokens.AppModel | 토큰 AppModel 정책 |
NtUtils.Transactions | 트랜잭션(TmTx) 객체 |
NtUtils.Transactions.Remote | 프로세스를 트랜잭션으로 강제 적용 |
NtUtils.UserManager | 사용자 관리자 서비스(Umgr) API |
NtUtils.Wim | Windows 이미징(*.wim) API |
NtUtils.WinSafer | 더 안전한 API |
NtUtils.WinStation | 터미널 서버 API |
NtUtils.WinUser | User32/GUI API |
NtUtils.WinUser.WindowAffinity | 창 선호도 수정 |
NtUtils.WinUser.WinstaLock | 윈도우 스테이션 잠금 및 잠금 해제 |
NtUtils.XmlLite | XmlLite를 통한 XML 구문 분석 및 제작 |
NtUiLib.AutoCompletion | 편집 컨트롤 자동 완성 |
NtUiLib.AutoCompletion.Namespace | NT 개체 네임스페이스 자동 완성 |
NtUiLib.AutoCompletion.Sid | SID 자동 완성 |
NtUiLib.AutoCompletion.Sid.Common | 단순 SID 이름 공급자/인식자 |
NtUiLib.AutoCompletion.Sid.AppContainer | AppContainer 및 패키지 SID 공급자/인식자 |
NtUiLib.AutoCompletion.Sid.Capability | 기능 SID 공급자/인식자 |
NtUiLib.WinCred | 자격 증명 대화 상자 |