在注入 shellcode 或转储内存之前,进程枚举是必需的。威胁参与者倾向于将 CreateToolhelp32Snapshot 与 Process32First 和 Process32Next 一起使用,以收集正在运行的进程列表。如果他们更精通技术,他们将直接使用 NtQuerySystemInformation 系统调用。
尽管本文将重点介绍如何获取专门用于 LSASS 的 PID,但此处描述的方法可用于解析任何过程的 PID。其中一些是众所周知的,并且之前已经讨论过,但也有一些许多读者不熟悉的新内容。需要明确的是,有一些方法可以使用 Window 对象获取 PID,但 LSASS 没有任何方法,这里不会讨论。
CreateToolhelp32Snapshot() 和 EnumProcesses 都将此系统调用与 SystemProcessInformation 类一起使用。在避免使用 Win32 API 时,通常会使用此系统调用。返回的数据还包含线程信息,因此 Thread32First 和 Thread32Next 使用它。另一方面,模块必须从每个进程的 PEB 中手动读取,因此 Module32First 和 Module32Next 需要额外的系统调用,如 NtOpenProcess 和 NtReadVirtualMemory。
//
// Use NtQuerySystemInformation(SystemProcessInformation)
//
DWORD
GetLsaPidFromName(void) {
NTSTATUS Status;
DWORD ProcessId = 0, Length = 1024;
std::vector ProcessList(1024); do {
do {
Status = NtQuerySystemInformation(
SystemProcessInformation,
ProcessList.data(),
Length,
&Length
);
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
ProcessList.resize(Length);
}
} while (Status == STATUS_INFO_LENGTH_MISMATCH);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtQuerySystemInformation(SystemProcessInformation) failed : %ld", GetLastError());
break;
}
DWORD Offset = 0;
PSYSTEM_PROCESS_INFORMATION ProcessEntry = NULL;
do {
ProcessEntry = (PSYSTEM_PROCESS_INFORMATION)(ProcessList.data() + Offset);
if (!lstrcmpiW(ProcessEntry->ImageName.Buffer, L"lsass.exe")) {
ProcessId = HandleToLong(ProcessEntry->UniqueProcessId);
break;
}
Offset += ProcessEntry->NextEntryOffset;
} while (ProcessEntry->NextEntryOffset);
} while(FALSE);
return ProcessId;
}
相关视频教程
恶意软件开发(更新到了142节)
WTSEnumerateProcesses API 使用 RPC 服务获取进程列表。在内部,该服务使用 NtQuerySystemInformation (SystemProcessInformation),但由于调用是在另一个进程中进行的,因此它可能曾经用于逃避进程枚举检测。
//
// Use Windows Terminal Services
//
DWORD
GetLsaPidFromWTS(void) {
DWORD ProcessId = 0; do {
PWTS_PROCESS_INFO info = NULL;
DWORD cnt = 0;
BOOL Result;
Result = WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &info, &cnt);
if (!Result) {
printf("WTSEnumerateProcesses() failed : %ld\n", GetLastError());
break;
}
for(DWORD i=0; i<cnt; i++) {
if (!lstrcmpiW(info[i].pProcessName, L"lsass.exe")) {
ProcessId = info[i].ProcessId;
break;
}
}
WTSFreeMemory(info);
} while (FALSE);
return ProcessId;
}
我们可以使用 WMI 获取类Win32_Process的实例列表。NtQuerySystemInformation() 的调用发生在 WMI 提供程序主机 (wmiprvse.exe) 中。与 WTS 一样,枚举发生在另一个进程中,对于逃避检测可能很有用。
//
// Windows Management Instrumentation (WMI)
//
DWORD
GetPidForLsass(IWbemServices *svc) {
IEnumWbemClassObject *ent = NULL;
DWORD ProcessId = 0; do {
HRESULT hr;
hr = svc->CreateInstanceEnum(
L"Win32_Process",
WBEM_FLAG_RETURN_IMMEDIATELY |
WBEM_FLAG_FORWARD_ONLY,
NULL,
&ent);
if (FAILED(hr)) {
printf("IWbemServices::CreateInstanceEnum() failed : %08lX\n", hr);
break;
}
for (;!ProcessId;) {
ULONG cnt = 0;
IWbemClassObject *obj = NULL;
hr = ent->Next(INFINITE, 1, &obj, &cnt);
if(!cnt) break;
VARIANT name;
VariantInit(&name);
hr = obj->Get(L"Name", 0, &name, NULL, NULL);
if (SUCCEEDED(hr)) {
if (!lstrcmpiW(V_BSTR(&name), L"lsass.exe")) {
VARIANT pid;
VariantInit(&pid);
hr = obj->Get(L"ProcessID", 0, &pid, NULL, NULL);
if (SUCCEEDED(hr)) {
ProcessId = V_UI4(&pid);
VariantClear(&pid);
}
}
VariantClear(&name);
}
obj->Release();
}
} while(FALSE);
if (ent) ent->Release();
return ProcessId;
}
//
// Read instances of Win32_Process and filter by Name.
//
DWORD
GetLsaPidFromWMI(void) {
IWbemLocator *loc = NULL;
IWbemServices *svc = NULL;
DWORD ProcessId = 0;
do {
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr)) {
printf("CoInitialize() failed : %08lX\n", hr);
break;
}
hr = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
if (FAILED(hr)) {
printf("CoInitializeSecurity() failed : %08lX\n", hr);
break;
}
hr = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator,
(LPVOID*)&loc);
if (FAILED(hr)) {
printf("CoInitializeSecurity() failed : %08lX\n", hr);
break;
}
hr = loc->ConnectServer(
L"root\\cimv2",
NULL,
NULL,
NULL,
0,
NULL,
NULL,
&svc);
if (FAILED(hr)) {
printf("IWbemLocator::ConnectServer() failed : %08lX\n", hr);
break;
}
ProcessId = GetPidForLsass(svc);
} while (FALSE);
if (svc) svc->Release();
if (loc) loc->Release();
CoUninitialize();
return ProcessId;
}
我们可以直接从存储在 HKLM\SYSTEM\CurrentControlSet\Control\Lsa 的注册表值中读取 PID。LsaPid
//
// Query registry for LSA process ID.
//
DWORD
GetLsaPidFromRegistry(void) {
UNICODE_STRING LsaPath, LsaValue;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE LsaKey = NULL;
NTSTATUS Status;
UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(DWORD)];
PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)Buffer;
ULONG Length;
DWORD ProcessId = 0; do {
LsaPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Lsa");
InitializeObjectAttributes(
&ObjectAttributes,
&LsaPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtOpenKey(&LsaKey, KEY_QUERY_VALUE, &ObjectAttributes);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtOpenKey() failed : %ld\n", GetLastError());
break;
}
LsaValue = RTL_CONSTANT_STRING(L"LsaPid");
Status = NtQueryValueKey(
LsaKey,
&LsaValue,
KeyValuePartialInformation,
Buffer,
sizeof(Buffer),
&Length);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtQueryValueKey() failed : %ld\n", GetLastError());
break;
}
PDWORD pData = (PDWORD)&PartialInfo->Data[0];
ProcessId = pData[0];
} while(FALSE);
if (LsaKey) NtClose(LsaKey);
return ProcessId;
}
LSASS 托管许多服务:
CNG KeyIsolation (KeyIso) CNG KeyIsolation (KeyIso)
安全帐户管理器 (SamSs)
凭据管理器 (VaultSvc)
我们可以打开这些服务中的任何一个来查询状态,并使用SC_STATUS_PROCESS_INFO来获取进程 ID。与 WTS 一样,此信息是通过 RPC 获取的,是获取服务 PID 的好方法。
//
// Query samss for LSA process ID.
//
DWORD
GetLsaPidFromService(void) {
SC_HANDLE ManagerHandle = NULL, ServiceHandle = NULL;
SERVICE_STATUS_PROCESS ProcessInfo;
HANDLE Handle = NULL;
DWORD Length, ProcessId = 0;
BOOL Result; do {
ManagerHandle = OpenSCManagerW(
NULL,
NULL,
SC_MANAGER_CONNECT
);
if (!ManagerHandle) {
printf("OpenSCManager() failed : %ld\n", GetLastError());
break;
}
ServiceHandle = OpenServiceW(
ManagerHandle,
L"samss",
SERVICE_QUERY_STATUS
);
if (!ServiceHandle) {
printf("OpenService() failed : %ld\n", GetLastError());
break;
}
Result = QueryServiceStatusEx(
ServiceHandle,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ProcessInfo,
sizeof(ProcessInfo),
&Length
);
if (!Result) {
printf("QueryServiceStatusEx() failed : %ld\n", GetLastError());
break;
}
ProcessId = ProcessInfo.dwProcessId;
} while(FALSE);
if (ServiceHandle) {
CloseServiceHandle(ServiceHandle);
}
if (ManagerHandle) {
CloseServiceHandle(ManagerHandle);
}
return ProcessId;
}
如果我们打开 LSASS.EXE 的文件句柄,并使用 FileProcessIdsUsingFileInformation 类将其传递给 NtQueryInformationFile,我们可以获取已打开文件的进程 ID 列表,并且列表中的第一个应始终属于 LSASS 进程。通常,LSASS 二进制文件位于 C:\Windows\System32\ 文件夹中,但使用 GetSystemDirectory() 之类的文件来获取正确的路径会更安全。
//
// Query process path for process ID
//
DWORD
GetLsaPidFromPath(void) {
IO_STATUS_BLOCK StatusBlock;
NTSTATUS Status;
WCHAR LsassPath[MAX_PATH + 1];
DWORD ProcessId = 0;
HANDLE FileHandle = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING NtPath; do {
//
// Get the Windows directory.
//
GetSystemDirectoryW(LsassPath, MAX_PATH);
PathAppendW(LsassPath, L"lsass.exe");
//
// Convert DOS path to NT path
//
RtlDosPathNameToNtPathName_U(
LsassPath,
&NtPath,
NULL,
NULL
);
//
// Open file for reading.
//
InitializeObjectAttributes(
&ObjectAttributes,
&NtPath,
OBJ_CASE_INSENSITIVE,
0,
NULL
);
Status = NtOpenFile(
&FileHandle,
FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&StatusBlock,
FILE_SHARE_READ,
NULL
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtOpenFile() failed : %ld\n", GetLastError());
break;
}
//
// Get list of process IDs with this process path opened.
//
std::vector Buffer;
for (DWORD Length=4096;;Length += 4096) {
Buffer.resize(Length);
Status = NtQueryInformationFile(
FileHandle,
&StatusBlock,
Buffer.data(),
Buffer.size(),
FileProcessIdsUsingFileInformation
);
if (Status != STATUS_INFO_LENGTH_MISMATCH) break;
}
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtQueryInformationFile() failed : %ld\n", GetLastError());
break;
}
auto PidFileInfo = (PFILE_PROCESS_IDS_USING_FILE_INFORMATION)Buffer.data();
if (PidFileInfo->NumberOfProcessIdsInList) {
ProcessId = DWORD(PidFileInfo->ProcessIdList[0]);
}
} while(FALSE);
if (FileHandle) NtClose(FileHandle);
return ProcessId;
}
获取命名管道服务器的 PID 的推荐方法是使用 GetNamedPipeServerProcessId API。在内部,这将调用 GetNamedPipeAttribute() API,截至 2022 年 8 月,MSDN 仍未记录该 API。调用时,它最终将执行 NtFsControlFile 系统调用。客户端或服务器可以使用它从其对等方获取会话 ID、进程 ID 或计算机名称。
BOOL GetNamedPipeAttribute(
HANDLE Pipe,
PIPE_ATTRIBUTE_TYPE AttributeType,
PSTR AttributeName,
PVOID AttributeValue,
PSIZE_T AttributeValueLength);
若要获取 PID,请打开命名管道“\Device\NamedPipe\lsass”,并使用 NtFsControlFile 发送以“ServerProcessId”作为输入的FSCTL_PIPE_GET_PIPE_ATTRIBUTE控制代码,该代码应返回进程 ID。
//
// Get the LSA PID from named pipe.
//
DWORD
GetLsaPidFromPipe(void) {
UNICODE_STRING LsaName = RTL_CONSTANT_STRING(L"\\Device\\NamedPipe\\lsass");
HANDLE LsaHandle;
IO_STATUS_BLOCK StatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
DWORD ProcessId = 0; do {
//
// Open named pipe for reading.
//
InitializeObjectAttributes(
&ObjectAttributes,
&LsaName,
OBJ_CASE_INSENSITIVE,
0,
NULL
);
Status = NtOpenFile(
&LsaHandle,
FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&StatusBlock,
FILE_SHARE_READ,
NULL
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtOpenFile() failed : %ld\n", GetLastError());
break;
}
//
// Query the server process ID.
//
LPSTR Attribute = "ServerProcessId";
Status = NtFsControlFile(
LsaHandle,
NULL,
NULL,
NULL,
&StatusBlock,
FSCTL_PIPE_GET_PIPE_ATTRIBUTE,
Attribute,
lstrlenA(Attribute) + 1,
&ProcessId,
sizeof(DWORD));
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtFsControlFile() failed : %ld\n", GetLastError());
}
} while(FALSE);
if (LsaHandle) NtClose(LsaHandle);
return ProcessId;
}
以同样的方式,我们可以查询哪个进程打开了文件,我们可以使用 NtQueryOpenSubKeysEx 获取打开了注册表项的 PID 列表。对于 LSASS,这是打开 HKLM\SAM 的唯一进程。不幸的是,此方法需要 SeRestorePrivilege,但如果您打算打开 LSASS,则至少需要启用 SYSTEM 或 SeDebugPrivilege。
//
// NtQueryOpenSubKeysEx() : Requires Admin rights to enable restore privilege.
//
DWORD
GetLsaPidFromRegName(void) {
UNICODE_STRING RegName = RTL_CONSTANT_STRING(L"\\REGISTRY\\MACHINE\\SAM");
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
DWORD ProcessId = 0; do {
//
// The restore privilege is required for this to work.
//
BOOLEAN Old;
Status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &Old);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE) failed : %ld\n", GetLastError());
break;
}
InitializeObjectAttributes(
&ObjectAttributes,
&RegName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
std::vector OutBuffer(1024);
ULONG OutLength = 1024;
Status = NtQueryOpenSubKeysEx(
&ObjectAttributes,
OutLength,
OutBuffer.data(),
&OutLength
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtQueryOpenSubKeysEx() failed : %ld\n", GetLastError());
break;
}
PKEY_OPEN_SUBKEYS_INFORMATION SubKeyInfo = (PKEY_OPEN_SUBKEYS_INFORMATION)OutBuffer.data();
ProcessId = HandleToUlong(SubKeyInfo->KeyArray[0].ProcessId);
} while(FALSE);
return ProcessId;
}
你们中的一些人可能已经知道 sysinternals 的 pslist 使用性能计数器来获取进程信息。也许现在这在进程枚举中不流行的原因是它在获取信息时非常嘈杂。排在首位的是它记录不全的事实。当然,互联网上也有演示如何使用计数器的来源,但它们通常可以追溯到 1990 年代。
最初,这种方法似乎非常隐蔽,但当您意识到获取进程列表所需的操作量时,它显然不是最好的方法。尽管如此,性能数据仍然是 Windows 的一个没有得到足够关注的领域。那里有一座数据金矿。
//
// Read LSASS process ID from performance counters.
//
DWORD
GetLsaPidFromPerf(void) {
DWORD ProcessId = 0;
std::vector Buffer; //
// Read performance data for each process.
//
for (DWORD Length=8192;;Length += 8192) {
Buffer.resize(Length);
DWORD rc = RegQueryValueExW(
HKEY_PERFORMANCE_DATA,
L"230",
NULL,
0,
Buffer.data(),
&Length);
if (rc == ERROR_SUCCESS) break;
}
//
// Read offset for the process ID.
//
auto pPerf = (PPERF_DATA_BLOCK)Buffer.data();
auto pObj = (PPERF_OBJECT_TYPE) ((PBYTE)pPerf + pPerf->HeaderLength);
auto pCounterDef = (PPERF_COUNTER_DEFINITION) ((PBYTE)pObj + pObj->HeaderLength);
DWORD ProcessIdOffset = 0;
for (auto i=0; iNumCounters; i++) {
if (pCounterDef->CounterNameTitleIndex == 784) {
ProcessIdOffset = pCounterDef->CounterOffset;
break;
}
pCounterDef++;
}
//
// Read process name and compare with lsass
//
auto pInst = (PPERF_INSTANCE_DEFINITION) ((PBYTE)pObj + pObj->DefinitionLength);
for (auto i=0; iNumInstances; i++) {
auto pName = (PWSTR) ((PBYTE)pInst + pInst->NameOffset);
auto pCounter = (PPERF_COUNTER_BLOCK) ((PBYTE)pInst + pInst->ByteLength);
if (*pName && !lstrcmpiW(pName, L"lsass")) {
ProcessId = *((LPDWORD) ((PBYTE)pCounter + ProcessIdOffset));
break;
}
pInst = (PPERF_INSTANCE_DEFINITION) ((PBYTE)pCounter + pCounter->ByteLength);
}
RegCloseKey(HKEY_PERFORMANCE_DATA);
return ProcessId;
}
从 Vista 开始,可以从网络存储接口服务 (NSI) 获取网络连接的进程 ID。LSASS 通常至少有一个端口侦听传入连接,如果我们知道该端口,我们可以使用一些代码读取 PID。该示例将 NtDeviceIoControlFile 与未记录的 IOCTL 代码和未记录的结构一起使用。遗留系统的另一种方法是打开套接字句柄并发送IOCTL_TDI_QUERY_INFORMATION。
typedef struct _NSI_CONNECTION_INFO {
PVOID Buffer;
SIZE_T Size;
} NSI_CONNECTION_INFO, *PNSI_CONNECTION_INFO;typedef struct _NSI_CONNECTION_ENTRIES {
NSI_CONNECTION_INFO Address;
NSI_CONNECTION_INFO Reserved;
NSI_CONNECTION_INFO State;
NSI_CONNECTION_INFO Process;
} NSI_CONNECTION_ENTRIES, *PNSI_CONNECTION_ENTRIES;
typedef enum _NPI_MODULEID_TYPE {
MIT_GUID = 1,
MIT_IF_LUID,
} NPI_MODULEID_TYPE;
typedef struct _NPI_MODULEID {
USHORT Length;
NPI_MODULEID_TYPE Type;
union {
GUID Guid;
LUID IfLuid;
};
} NPI_MODULEID, *PNPI_MODULEID;
NPI_MODULEID NPI_MS_TCP_MODULEID = {
sizeof(NPI_MODULEID),
MIT_GUID,
{0xEB004A03, 0x9B1A, 0x11D4, {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}}
};
// the following structures were reverse engineered and won't be correct.
typedef struct _NSI_CONNECTION_TABLE {
DWORD Unknown1[4];
PNPI_MODULEID ModuleId;
DWORD64 TypeId;
ULONG64 Flags;
NSI_CONNECTION_ENTRIES Entries;
DWORD NumberOfEntries;
} NSI_CONNECTION_TABLE, *PNSI_CONNECTION_TABLE;
typedef union _NSI_IP_ADDR_U {
sockaddr_in v4;
sockaddr_in6 v6;
} NSI_IP_ADDR_U, *PNSI_IP_ADDR_U;
typedef struct _NSI_CONNECTION_ADDRESS {
NSI_IP_ADDR_U Local;
NSI_IP_ADDR_U Remote;
} NSI_CONNECTION_ADDRESS, *PNSI_CONNECTION_ADDRESS;
typedef struct _NSI_CONNECTION_STATE {
PULONG ulState;
ULONG ulTimestamp;
} NSI_CONNECTION_STATE, *PNSI_CONNECTION_STATE;
typedef struct _NSI_CONNECTION_PROCESS {
DWORD dwOwningPidUdp;
BOOL bFlag;
DWORD liCreateTimestampUdp;
DWORD dwOwningPidTcp;
LARGE_INTEGER liCreateTimestamp;
ULONGLONG OwningModuleInfo;
} NSI_CONNECTION_PROCESS, *PNSI_CONNECTION_PROCESS;
#define FSCTL_TCP_BASE FILE_DEVICE_NETWORK
#define _TCP_CTL_CODE(Function, Method, Access) \
CTL_CODE(FSCTL_TCP_BASE, Function, Method, Access)
#define NSI_IOCTL_GET_INFORMATION _TCP_CTL_CODE(0x006, METHOD_NEITHER, FILE_ANY_ACCESS)
//
// Read the LSA PID from TCP table.
//
DWORD
GetLsaPidFromTcpTable(void) {
DWORD ProcessId = 0;
HANDLE NsiHandle = NULL;
NTSTATUS Status;
IO_STATUS_BLOCK IoStatusBlock;
PNSI_CONNECTION_ADDRESS Address = NULL;
PNSI_CONNECTION_STATE State = NULL;
PNSI_CONNECTION_PROCESS Process = NULL;
do {
//
// Open handle to Network Store Interface Service. (nsiproxy.sys)
//
NsiHandle = CreateFileW(
L"\\\\.\\Nsi",
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (NsiHandle == INVALID_HANDLE_VALUE) break;
//
// Tell service to return information for TCP connections.
// The first call obtains the number of entries available.
//
NSI_CONNECTION_TABLE TcpTable={0};
TcpTable.ModuleId = &NPI_MS_TCP_MODULEID;
TcpTable.TypeId = 3; // TCP connections.
TcpTable.Flags = 1 | 0x100000000;
Status = NtDeviceIoControlFile(
NsiHandle,
NULL, // no event object. making a synchronous request.
NULL,
NULL,
&IoStatusBlock,
NSI_IOCTL_GET_INFORMATION, // ioctl code to return all information
&TcpTable, // in
sizeof(TcpTable),
&TcpTable, // out
sizeof(TcpTable)
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtDeviceIoControlFile() failed : %ld\n", GetLastError());
break;
}
//
// Allocate memory for entries.
//
Address = (PNSI_CONNECTION_ADDRESS)calloc(TcpTable.NumberOfEntries + 2, sizeof(NSI_CONNECTION_ADDRESS));
State = (PNSI_CONNECTION_STATE)calloc(TcpTable.NumberOfEntries + 2, sizeof(NSI_CONNECTION_STATE));
Process = (PNSI_CONNECTION_PROCESS)calloc(TcpTable.NumberOfEntries + 2, sizeof(NSI_CONNECTION_PROCESS));
//
// Assign buffers and the size of each structure.
// Then try again.
//
TcpTable.Entries.Address.Buffer = Address;
TcpTable.Entries.Address.Size = sizeof(NSI_CONNECTION_ADDRESS);
TcpTable.Entries.State.Buffer = State;
TcpTable.Entries.State.Size = sizeof(NSI_CONNECTION_STATE);
TcpTable.Entries.Process.Buffer = Process;
TcpTable.Entries.Process.Size = sizeof(NSI_CONNECTION_PROCESS);
Status = NtDeviceIoControlFile(
NsiHandle,
NULL, // no event object. making a synchronous request.
NULL,
NULL,
&IoStatusBlock,
NSI_IOCTL_GET_INFORMATION, // ioctl code to return all information
&TcpTable, // in
sizeof(TcpTable),
&TcpTable, // out
sizeof(TcpTable)
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtDeviceIoControlFile() failed : %ld\n", GetLastError());
break;
}
//
// Loop through each entry to find LSASS ports.
// When found, return the Process ID.
//
for (DWORD i=0; i= 49664 && lport <= 49667 || lport == 49155) {
ProcessId = pid;
break;
}
}
} while (FALSE);
if (Address) free(Address);
if (State) free(State);
if (Process) free(Process);
if (NsiHandle) NtClose(NsiHandle);
return ProcessId;
}
事件 ID 4608 又名“Windows 正在启动”包含 LSASS 的进程 ID。可以使用 Windows 事件日志 API 提取它。当然,还有其他事件需要考虑:例如登录事件。4608 恰好从 Vista 到 Windows 11/Server 2022 保持一致。
//
// Query the PID for LSASS from the Security event log.
//
DWORD
GetLsaPidFromEventLogs(void) {
EVT_HANDLE hResults = NULL, hContext = NULL, hEvent = NULL;
DWORD dwProcessId = 0; do {
//
// Get all records from the Security log with event ID 4608
//
hResults = EvtQuery(
NULL,
L"Security",
L"*/*[EventID=4608]",
EvtQueryTolerateQueryErrors
);
if (!hResults) {
printf("EvtQuery(Security, EventID=4608) failed : %ld\n", GetLastError());
break;
}
//
// Move position of results to the last entry. (the latest available)
//
BOOL Result;
Result = EvtSeek(hResults, 0, NULL, 0, EvtSeekRelativeToLast);
if (!Result) {
printf("EvtSeek() failed : %ld\n", GetLastError());
break;
}
//
// Read last event.
//
DWORD dwReturned = 0;
Result = EvtNext(
hResults,
1,
&hEvent,
INFINITE, 0,
&dwReturned);
if (!Result || dwReturned != 1) {
printf("EvtNext() failed : %ld\n", GetLastError());
break;
}
//
// Create a render context so that we only extract the PID
//
LPCWSTR ppValues[] = {L"Event/System/Execution/@ProcessID"};
hContext = EvtCreateRenderContext(
ARRAYSIZE(ppValues),
ppValues,
EvtRenderContextValues);
if (!hContext) {
printf("EvtCreateRenderContext failed : %ld\n", GetLastError());
break;
}
//
// Extract the PID.
//
EVT_VARIANT pProcessId={0};
EvtRender(
hContext,
hEvent,
EvtRenderEventValues,
sizeof(EVT_VARIANT),
&pProcessId,
&dwReturned,
NULL
);
//
// Save it.
//
dwProcessId = pProcessId.UInt32Val;
} while (FALSE);
if (hEvent) EvtClose(hEvent);
if (hContext) EvtClose(hContext);
if (hResults) EvtClose(hResults);
return dwProcessId;
}
PID 值递增 4,因此我们可以轻松地循环浏览潜在的 PID 并查询图像。
BOOL
GetProcessNameById(DWORD ProcessId, WCHAR ImageName[MAX_PATH + 1]) {
WCHAR ImageBuffer[512];
SYSTEM_PROCESS_ID_INFORMATION ProcessInformation;
NTSTATUS Status;
DWORD Length; //
// Query the system for image name of process ID.
//
ProcessInformation.ProcessId = LongToHandle(ProcessId);
ProcessInformation.ImageName.Buffer = ImageBuffer;
ProcessInformation.ImageName.Length = 0;
ProcessInformation.ImageName.MaximumLength = sizeof(ImageBuffer);
Status = NtQuerySystemInformation(
SystemProcessIdInformation,
&ProcessInformation,
sizeof(ProcessInformation),
NULL
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
//printf("NtQuerySystemInformation(SystemProcessIdInformation) failed : %ld\n", GetLastError());
return FALSE;
}
//
// Strip path and copy name to buffer.
//
PathStripPathW(ImageBuffer);
Length = ProcessInformation.ImageName.Length;
if (Length > (MAX_PATH * sizeof(WCHAR))) {
Length = MAX_PATH * sizeof(WCHAR);
}
memcpy(
ImageName,
ImageBuffer,
Length
);
return TRUE;
}
//
// Run a loop. Increment PID and resolve an image name. Then compare with lsass.exe
//
DWORD
GetLsaPidFromBruteForce(void) {
DWORD ProcessId = 0;
WCHAR ImageName[MAX_PATH + 1]={0};
//
// 0xFFFFFFFC is the maximum PID but probably too much for this.
//
for (DWORD pid=8; pid<0xFFFFFFFC; pid += 4) {
if (GetProcessNameById(pid, ImageName)) {
if (!lstrcmpiW(ImageName, L"lsass.exe")) {
ProcessId = pid;
break;
}
}
}
return ProcessId;
}
LSASS 创建一个 section 对象,用于存储有关其性能的信息,恰如其分地命名为“\LsaPerformance” 数据内部是 PID。性能监视器用户具有读取访问权限,而 SYSTEM 和管理员具有完全访问权限。数据结构是未记录的,PID 可能出现在不同的偏移量处,因此这需要努力提高可靠性。
//
// Only tested on Windows 10. The offset of PID differs across versions.
//
DWORD
GetLsaPidFromSection(void) {
UNICODE_STRING SectionName = RTL_CONSTANT_STRING(L"\\LsaPerformance");
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
DWORD ProcessId = 0;
HANDLE SectionHandle = NULL;
PVOID ViewBase = NULL; do {
InitializeObjectAttributes(
&ObjectAttributes,
&SectionName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
Status = NtOpenSection(
&SectionHandle,
SECTION_MAP_READ,
&ObjectAttributes
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtOpenSection() failed : %ld\n", GetLastError());
break;
}
LARGE_INTEGER SectionOffset={0};
SIZE_T ViewSize = 0;
Status = NtMapViewOfSection(
SectionHandle,
NtCurrentProcess(),
&ViewBase,
0, // ZeroBits
0, // CommitSize
&SectionOffset,
&ViewSize,
ViewShare,
0,
PAGE_READONLY
);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtMapViewOfSection() failed : %ld\n", GetLastError());
break;
}
PDWORD data = (PDWORD)ViewBase;
ProcessId = data[32]; // Windows 10. it appears at different offsets for different builds.
} while(FALSE);
if (ViewBase) NtUnmapViewOfSection(NtCurrentProcess(), ViewBase);
if (SectionHandle) NtClose(SectionHandle);
return ProcessId;
}
自 Windows 10 版本 1909 起,“19H2”可用于查询 ALPC 服务的会话和进程 ID。此处的示例使用“\RPC Control\samss lpc”,它应该在 LSASS 进程内运行。这并不总是可靠的。有时 NtAlpcQueryInformation() 会失败并显示STATUS_INVALID_PARAMETER。
//
// Query process ID from session information. Only works on 19H2 builds.
//
DWORD
GetLsaPidFromAlpc(void) {
HANDLE AlpcPort = NULL;
UNICODE_STRING AlpcName;
DWORD ProcessId = 0;
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes; do {
//
// Connect to RPC service.
//
AlpcName = RTL_CONSTANT_STRING(L"\\RPC Control\\samss lpc");
Status = NtAlpcConnectPort(
&AlpcPort,
&AlpcName,
NULL,
NULL,
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtAlpcConnectPort() failed : %ld\n", GetLastError());
break;
}
//
// Query session information.
//
ALPC_SERVER_SESSION_INFORMATION SessionInfo={0};
Status = NtAlpcQueryInformation(
AlpcPort,
AlpcServerSessionInformation,
&SessionInfo,
sizeof(SessionInfo),
NULL);
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
printf("NtAlpcQueryInformation() failed : %ld\n", GetLastError());
break;
}
ProcessId = SessionInfo.ProcessId;
} while(FALSE);
if (AlpcPort) NtClose(AlpcPort);
return ProcessId;
}
如您所见,有很多方法可以获取进程的 PID。对于 LSASS,从命名管道读取 ServerProcessId 属性似乎是最优雅的方法。它不需要任何内存分配,至少可以在 Vista 到 Windows 11 上运行,并且应该在未来继续运行。这里没有讨论的另一个问题涉及枚举句柄和计算进程打开了多少个安全令牌。得分最高的条目应该是 LSASS,但当然也有可能使用这种方法识别出错误的进程。
二进制漏洞课程(更新中)
windows网络安全防火墙与虚拟网卡(更新完成)
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
ios逆向
windbg
还有很多免费教程(限学员)
更多详细内容添加作者微信