在内核中调用系统服务(NATIVE APIs)
[原文出处] : 《Windows NT2000 Native API Reference》 附录B
[关键字] : System services, NATIVE API
[原作者] : Gary Nebbett
[中文翻译] : prince
[翻译日期] : 2006.04.25 – 15:12
[译者声明] :
最近写程序时想用一些 NATIVE APIs,查了《Undocumented functions of NTDLL》手册,找到相应的函数原型声明,放到我的工程文件中,却发现有的可以直接使用,有的却无法被编译器识别。网上找了很久,终于在《Windows NT2000 Native API Reference》中发现了此文,抱着学习的目的顺手翻译一下,肯定有不对的地方,无奈英文太烂,只能止步于此,各位大侠如果发现任何问题,还请mail至cracker_prince[at]163[dot]com不吝赐教,谢谢。
另外,转贴请保持文章完整,并注明出处是看雪学院(www.pediy.com)。本来不想写这句话的,但是我发现我曾经翻译的烂文章被很多网站转载,并且被无耻地去掉了原作者、译者声明和出处,在这里强烈BS一下!做人要厚道!
[正文] :
就像在前言中陈述过的,理论上有可能用那些运行在内核中PASSIVE_LEVEL IRQL的代码去调用所有系统服务。对于内核态程序来说前面几章的文档中讲过的那些系统服务是可用的,条件是关于holding权限需求的声明是可以被忽略的(prince注:对不起,我完全不理解这句话)。尽管如此,我们面临着一个实际的困难就是:ntoskrnl.exe并没有导出所有必需的函数的入口点。
在Windows 2000平台上下面这些ZwXxx函数的入口点都是被ntoskrnl.exe导出的:
ZwAccessCheckAndAuditAlarm ZwPowerInformation
ZwAdjustPrivilegesToken ZwPulseEvent
ZwAlertThread ZwQueryDefaultLocale
ZwAllocateVirtualMemory ZwQueryDefaultUILanguage
ZwCancelIoFile ZwQueryDirectoryFile
ZwCancelTimer ZwQueryDirectoryObject
ZwClearEvent ZwQueryEaFile
ZwClose ZwQueryInformationFile
ZwCloseObjectAuditAlarm ZwQueryInformationProcess
ZwConnectPort ZwQueryInformationToken
ZwCreateDirectoryObject ZwQueryInstallUILanguage
ZwCreateEvent ZwQueryKey
ZwCreateFile ZwQueryObject
ZwCreateKey ZwQuerySection
ZwCreateSection ZwQuerySecurityObject
ZwCreateSymbolicLinkObject ZwQuerySymbolicLinkObject
ZwCreateTimer ZwQuerySystemInformation
ZwDeleteFile ZwQueryValueKey
ZwDeleteKey ZwQueryVolumeInformationFile
ZwDeleteValueKey ZwReadFile
ZwDeviceIoControlFile ZwReplaceKey
ZwDisplayString ZwRequestWaitReplyPort
ZwDuplicateObject ZwResetEvent
ZwDuplicateToken ZwRestoreKey
ZwEnumerateKey ZwSaveKey
ZwEnumerateValueKey ZwSetDefaultLocale
ZwFlushInstructionCache ZwSetDefaultUILanguage
ZwFlushKey ZwSetEaFile
ZwFlushVirtualMemory ZwSetEvent
ZwFreeVirtualMemory ZwSetInformationFile
ZwFsControlFile ZwSetInformationObject
ZwInitiatePowerAction ZwSetInformationProcess
ZwLoadDriver ZwSetInformationThread
ZwLoadKey ZwSetSecurityObject
ZwMakeTemporaryObject ZwSetSystemInformation
ZwMapViewOfSection ZwSetSystemTime
ZwNotifyChangeKey ZwSetTimer
ZwOpenDirectoryObject ZwSetValueKey
ZwOpenEvent ZwSetVolumeInformationFile
ZwOpenFile ZwTerminateProcess
ZwOpenKey ZwUnloadDriver
ZwOpenProcess ZwUnloadKey
ZwOpenProcessToken ZwUnmapViewOfSection
ZwOpenSection ZwWaitForMultipleObjects
ZwOpenSymbolicLinkObject ZwWaitForSingleObject
ZwOpenThread ZwWriteFile
ZwOpenThreadToken ZwYieldExecution
ZwOpenTimer
在Windows 2000平台上下面这些NtXxx函数入口点都是被ntoskrnl.exe导出的:
NtAddAtom NtQueryEaFile
NtAdjustPrivilegesToken NtQueryInformationAtom
NtAllocateLocallyUniqueId NtQueryInformationFile
NtAllocateUuids NtQueryInformationProcess
NtAllocateVirtualMemory NtQueryInformationToken
NtClose NtQueryQuotaInformationFile
NtConnectPort NtQuerySecurityObject
NtCreateEvent NtQuerySystemInformation
NtCreateFile NtQueryVolumeInformationFile
NtCreateSection NtReadFile
NtDeleteAtom NtRequestPort
NtDeleteFile NtRequestWaitReplyPort
NtDeviceIoControlFile NtSetEaFile
NtDuplicateObject NtSetEvent
NtDuplicateToken NtSetInformationFile
NtFindAtom NtSetInformationProcess
NtFreeVirtualMemory NtSetInformationThread
NtFsControlFile NtSetQuotaInformationFile
NtLockFile NtSetSecurityObject
NtMapViewOfSection NtSetVolumeInformationFile
NtNotifyChangeDirectoryFile NtUnlockFile
NtOpenFile NtVdmControl
NtOpenProcess NtWaitForSingleObject
NtOpenProcessToken NtWriteFile
NtQueryDirectoryFile
如果是被导出的ZwXxx表中的函数,就可以在内核态代码中直接调用;如果仅是在NtXxx表中被导出的函数,那么内核代码就必须确认指针和对象访问的检查结果(prince 注:这句话也令我困惑),就像在前言中描述的那样。
下面这些函数根本就没有被导出:
ZwAcceptConnectPort ZwQueryInformationThread
ZwAccessCheck ZwQueryIntervalProfile
ZwAccessCheckByType ZwQueryIoCompletion
ZwAccessCheckByTypeAndAuditAlarm ZwQueryMultipleValueKey
ZwAccessCheckByTypeResultList ZwQueryMutant
ZwAccessCheckByTypeResultListAndAuditAlarm ZwQueryPerformanceCounter
ZwAdjustGroupsToken ZwQuerySemaphore
ZwAlertResumeThread ZwQuerySystemEnvironmentValue
ZwAllocateUserPhysicalPages ZwQuerySystemTime
ZwAllocateVirtualMemory64 ZwQueryTimer
ZwAreMappedFilesTheSame ZwQueryTimerResolution
ZwAssignProcessToJobObject ZwQueryVirtualMemory
ZwCallbackReturn ZwQueryVirtualMemory64
ZwCancelDeviceWakeupRequest ZwQueueApcThread
ZwCompleteConnectPort ZwRaiseException
ZwContinue ZwRaiseHardError
ZwCreateChannel ZwReadFile64
ZwCreateEventPair ZwReadFileScatter
ZwCreateIoCompletion ZwReadRequestData
ZwCreateJobObject ZwReadVirtualMemory
ZwCreateMailslotFile ZwReadVirtualMemory64
ZwCreateMutant ZwRegisterThreadTerminatePort
ZwCreateNamedPipeFile ZwReleaseMutant
ZwCreatePagingFile ZwReleaseSemaphore
ZwCreatePort ZwRemoveIoCompletion
ZwCreateProcess ZwReplyPort
ZwCreateProfile ZwReplyWaitReceivePort
ZwCreateSemaphore ZwReplyWaitReceivePortEx
ZwCreateThread ZwReplyWaitReplyPort
ZwCreateToken ZwReplyWaitSendChannel
ZwCreateWaitablePort ZwRequestDeviceWakeup
ZwDelayExecution ZwRequestWakeupLatency
ZwDeleteObjectAuditAlarm ZwResumeThread
ZwExtendSection ZwSaveMergedKeys
ZwFilterToken ZwSecureConnectPort
ZwFlushBuffersFile ZwSendWaitReplyChannel
ZwFlushWriteBuffer ZwSetContextChannel
ZwFreeUserPhysicalPages ZwSetContextThread
ZwFreeVirtualMemory64 ZwSetDefaultHardErrorPort
ZwGetContextThread ZwSetHighEventPair
ZwGetDevicePowerState ZwSetHighWaitLowEventPair
ZwGetPlugPlayEvent ZwSetInformationJobObject
ZwGetTickCount ZwSetInformationKey
ZwImpersonateAnonymousToken ZwSetInformationToken
ZwImpersonateClientOfPort ZwSetIntervalProfile
ZwImpersonateThread ZwSetIoCompletion
ZwInitializeRegistry ZwSetLdtEntries
ZwIsSystemResumeAutomatic ZwSetLowEventPair
ZwListenChannel ZwSetLowWaitHighEventPair
ZwListenPort ZwSetSystemEnvironmentValue
ZwLoadKey2 ZwSetSystemPowerState
ZwLockVirtualMemory ZwSetThreadExecutionState
ZwMapUserPhysicalPages ZwSetTimerResolution
ZwMapViewOfVlmSection ZwSetUuidSeed
ZwNotifyChangeMultipleKeys ZwShutdownSystem
ZwOpenChannel ZwSignalAndWaitForSingleObject
ZwOpenEventPair ZwStartProfile
ZwOpenIoCompletion ZwStopProfile
ZwOpenJobObject ZwSuspendThread
ZwOpenMutant ZwSystemDebugControl
ZwOpenObjectAuditAlarm ZwTerminateJobObject
ZwOpenSemaphore ZwTerminateThread
ZwPlugPlayControl ZwTestAlert
ZwPrivilegeCheck ZwUnlockVirtualMemory
ZwPrivilegeObjectAuditAlarm ZwUnmapViewOfVlmSection
ZwPrivilegedServiceAuditAlarm ZwWaitHighEventPair
ZwProtectVirtualMemory ZwWaitLowEventPair
ZwProtectVirtualMemory64 ZwWriteFile64
ZwQueryAttributesFile ZwWriteFileGather
ZwQueryEvent ZwWriteRequestData
ZwQueryFullAttributesFile ZwWriteVirtualMemory
ZwQueryInformationJobObject ZwWriteVirtualMemory64
ZwQueryInformationPort
对于某些函数,都有一些功能与其相近的被导出的和有文档的内核函数存在;比如,KeQueryPerformanceCounter函数就可以可以用来代替ZwQueryPerformanceCount函数。
ntddk.h定义了一些内部对象(events, mutants, semaphores, timers, and files)格式,还有一些被导出的和有文档的对象管理器和内核函数,有了这些就有可能自己来重新实现一些系统服务(NATIVE API)。示例B.1就是NtQueryEvent函数的重新实现,去掉了参数确认(prince 注:希望我翻译的对)。
Example B.1: Re-Implementing NtQueryEvent
#include “ntdll.h”
NTSTATUS
NTAPI
MyQueryEvent(
IN HANDLE EventHandle,
IN NT::EVENT_INFORMATION_CLASS EventInformationClass,
OUT PVOID EventInformation,
IN ULONG EventInformationLength,
OUT PULONG ResultLength OPTIONAL
)
{
if (ResultLength) *ResultLength = 0;
if (EventInformationClass != NT::EventBasicInformation)
return STATUS_INVALID_INFO_CLASS;
if (EventInformationLength != sizeof (NT::EVENT_BASIC_INFORMATION))
return STATUS_INFO_LENGTH_MISMATCH;
NT::PKEVENT Event;
NTSTATUS rv = NT::ObReferenceObjectByHandle( EventHandle,
EVENT_MODIFY_STATE,
*NT::ExEventObjectType,
NT::ExGetPreviousMode(),
(PVOID*)&Event, 0);
if (NT_SUCCESS(rv)) {
NT::PEVENT_BASIC_INFORMATION(EventInformation)->EventType = NT::EVENT_TYPE(Event->Header.Type);
NT::PEVENT_BASIC_INFORMATION(EventInformation)->SignalState = NT::KeReadStateEvent(Event);
NT::ObDereferenceObject(Event);
if (ResultLength) *ResultLength = sizeof (NT::EVENT_BASIC_INFORMATION);
}
return rv;
}
Example B.1中可以看到很多常见的错误代码。
ObReferenceObjectByHandle函数可以返回如下的错误代码:
当EventHandle是无效句柄的时候返回STATUS_INVALID_HANDLE;当EventHandle是有效句柄但却不是一个事件(event)对象句柄时返回STATUS_OBJECT_TYPE_MISMATCH;当句柄没有被授权访问的时候返回STATUS_ACCESS_DENIED;当访问的前一种模式为用户态时返回EVENT_MODIFY_STATE。指向前一个状态的指针的参数检查会导致返回错误代码STATUS_ACCESS_VIOLATION或者STATUS_DATATYPE_MISALIGNMENT。
这个例子也显示了对象管理器仅仅是封装了像KEVENT这样的简单的数据结构来提供例如naming, ACLs, reference counting和quotas这样的服务。
对于那些内部保留的不能直接访问的系统服务,除了动态链接到ntdll.dll来映射到每一个进程的地址空间然后导出所有的ZwXxx函数的入口点之外还没有什么好的解决方案。使用这种技术需要注意的是ntdll.dll是以copy on write方式映射的,所以进程可以修改ntdll.dll的代码来实现ZwXxx函数调用(但是对于运行在系统进程中的线程像system进程来说应该不是问题)。
Example B.2: Dynamically Binding to ntdll.dll
#include “ntdll.h”
PVOID FindNT()
{
ULONG n;
NT::ZwQuerySystemInformation(NT::SystemModuleInformation, &n, 0, &n);
PULONG q = PULONG(NT::ExAllocatePool(NT::PagedPool, n));
NT::ZwQuerySystemInformation(NT::SystemModuleInformation, q, n * sizeof *q, 0);
NT::PSYSTEM_MODULE_INFORMATION p = NT::PSYSTEM_MODULE_INFORMATION(q + 1);
PVOID ntdll = 0;
for (ULONG i = 0; i < *q; i++)
if (_stricmp(p[i].ImageName + p[i].ModuleNameOffset, “ntdll.dll”) == 0)
ntdll = p[i].Base;
NT::ExFreePool(q);
return ntdll;
}
PVOID FindFunc(PVOID Base, PCSTR Name)
{
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(Base);
PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(PCHAR(Base) + dos->e_lfanew);
PIMAGE_DATA_DIRECTORY expdir = nt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
ULONG size = expdir->Size;
ULONG addr = expdir->VirtualAddress;
PIMAGE_EXPORT_DIRECTORY exports = PIMAGE_EXPORT_DIRECTORY(PCHAR(Base) + addr);
PULONG functions = PULONG(PCHAR(Base) + exports->AddressOfFunctions);
PSHORT ordinals = PSHORT(PCHAR(Base) + exports->AddressOfNameOrdinals);
PULONG names = PULONG(PCHAR(Base) + exports->AddressOfNames);
PVOID func = 0;
for (ULONG i = 0; i < exports->NumberOfNames; i++) {
ULONG ord = ordinals[i];
if (functions[ord] < addr || functions[ord] >= addr + size) {
if (strcmp(PSTR(PCHAR(Base) + names[i]), Name) == 0)
func = PCHAR(Base) + functions[ord];
}
}
return func;
}
VOID Unload(NT::PDRIVER_OBJECT)
{
}
typedef NTSTATUS (NTAPI *NtQueryPerformanceCounter)(PLARGE_INTEGER, PLARGE_INTEGER);
extern “C”
NTSTATUS DriverEntry(NT::PDRIVER_OBJECT DriverObject, NT::PUNICODE_STRING)
{
LARGE_INTEGER Count, Freq;
NtQueryPerformanceCounter(FindFunc(FindNT(), “ZwQueryPerformanceCounter”))(&Count, &Freq);
NT::DbgPrint(“Freq = %lx, Count = %lx\n”, Freq.LowPart, Count.LowPart);
if (DriverObject) DriverObject->DriverUnload = Unload;
return DriverObject ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
}
示例B.2首先调用ZwQuerySystemInformation函数得到kernel images列表(包括ntdll.dll),并且从这些信息中得到了ntdll.dll的基址。接下来利用PE文件格式的知识定位导出表并且搜索我们想要的函数入口点。
示例B.2可以作为设备驱动安装,用ZwLoadDriver函数启动或者直接用ZwSetSystemInformation函数加载。
prince
2006-4-25