最近发现对于一些进程我们使用 taskkill 并不能终止进程(不是不能而是为了安全被拦截了),而使用 tskill.exe(win10之前系统有)可以直接终止进程,不需要管理员权限打开进程就可以直接结束进程,而且速度比taskkill 快得多。
于是我们拷贝了一份并利用 IDA 逆向分析了一下它是如何运作的,发现主要调用 WinStationTerminateProcess 来终止进程,查找进程我们改用 NtQuerySystemInformation 来完成,算是一种结束进程的方法吧,TerminateProcess 也可以终止进程。这里为了用 WinStationTerminateProcess 实现,遂简单写了一下实现代码。
函数定义:
BOOLEAN (WINAPI* WinStationTerminateProcess)(
_In_opt_ HANDLE hServer,
_In_ ULONG ProcessId,
_In_ ULONG ExitCode
);
我们发现只要提供进程的 PID 就可以结束进程,避免了调用 OpenProcess,第一个参数指定目标工作站,如果为 NULL,表示搜索所有工作站。
完整代码:
#include <iostream>
#include <windows.h>
#include <shellscalingapi.h>
#pragma comment(lib, "Shcore.lib")
typedef HANDLE (WINAPI* MyWinStationOpenServerW)(
_In_ PWSTR ServerName
);
typedef BOOLEAN (WINAPI* MyWinStationTerminateProcess)(
_In_opt_ HANDLE hServer,
_In_ ULONG ProcessId,
_In_ ULONG ExitCode
);
DWORD WinstaEnumerateProcess(PWSTR wsTargetProcess);
BOOL WinstaEnumerateSutdownProcess(PWSTR hServer, PWSTR wsTargetProcess, ULONG ExitCode);
PVOID fpWinStationOpenServerW = NULL;
PVOID fpWinStationTerminateProcess = NULL;
HANDLE hServerSign = NULL;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_SUCCESS ((NTSTATUS) 0x00000000)
#define SystemProcessInformation 5 // 功能号
#define NTAPI __stdcall
typedef LONG NTSTATUS;
typedef LONG KPRIORITY;
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _CLIENT_ID
{
DWORD UniqueProcess;
DWORD UniqueThread;
} CLIENT_ID, * PCLIENT_ID;
typedef struct _VM_COUNTERS
{
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
} VM_COUNTERS;
typedef enum _THREAD_STATE
{
StateInitialized,
StateReady,
StateRunning,
StateStandby,
StateTerminated,
StateWait,
StateTransition,
StateUnknown
} THREAD_STATE;
typedef enum _KWAIT_REASON
{
Executive,
FreePage,
PageIn,
PoolAllocation,
DelayExecution,
Suspended,
UserRequest,
WrExecutive,
WrFreePage,
WrPageIn,
WrPoolAllocation,
WrDelayExecution,
WrSuspended,
WrUserRequest,
WrEventPair,
WrQueue,
WrLpcReceive,
WrLpcReply,
WrVirtualMemory,
WrPageOut,
WrRendezvous,
Spare2,
Spare3,
Spare4,
Spare5,
Spare6,
WrKernel
} KWAIT_REASON;
typedef struct _SYSTEM_THREAD_INFORMATION_X64
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
THREAD_STATE ThreadState;
KWAIT_REASON WaitReason;
ULONG Reserved;
} SYSTEM_THREAD_INFORMATION_X64, * PSYSTEM_THREAD_INFORMATION_X64;
typedef struct _SYSTEM_PROCESS_INFORMATION
{
ULONG NextEntryDelta; // 指向下一个结构体的指针
ULONG ThreadCount; // 本进程的总线程数
ULONG Reserved1[6]; // 保留
LARGE_INTEGER CreateTime; // 进程的创建时间
LARGE_INTEGER UserTime; // 在用户层的使用时间
LARGE_INTEGER KernelTime; // 在内核层的使用时间
UNICODE_STRING ProcessName; // 进程名
KPRIORITY BasePriority; //
ULONG ProcessId; // 进程ID
ULONG InheritedFromProcessId;
ULONG HandleCount; // 进程的句柄总数
ULONG Reserved2[2]; // 保留
ULONG_PTR PageDirectoryBase;
VM_COUNTERS VmCounters;
SIZE_T PrivatePageCount;
IO_COUNTERS IoCounters;
SYSTEM_THREAD_INFORMATION_X64 Threads[1]; // 子线程信息数组
}SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;
typedef DWORD(WINAPI* MyNtQuerySystemInformation)(
UINT, PVOID, DWORD, PDWORD);
PVOID fpNtQuerySystemInformation = NULL;
int wmain(int argc, wchar_t* argv[])
{
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
if (argc != 2)
{
return MessageBoxW(NULL,
L"参数错误",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
}
WCHAR* wsTargetProcessName = argv[1];
PWSTR hServer = new WCHAR[45];
HMODULE hWinsta = LoadLibraryW(L"winsta.dll");
if (!hWinsta)
return MessageBoxW(NULL,
L"加载 winsta.dll 动态链接库失败。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
if (!hNtdll)
return MessageBoxW(NULL,
L"加载 ntdll.dll 动态链接库失败。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
SetLastError(0);
fpNtQuerySystemInformation = (MyNtQuerySystemInformation)GetProcAddress(
hNtdll,"NtQuerySystemInformation");
if (fpNtQuerySystemInformation == NULL || GetLastError() != 0)
return MessageBoxW(NULL,
L"无法定位函数 NtQuerySystemInformation 入口点位于动态链接库 ntdll.dll 上。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
SetLastError(0);
fpWinStationOpenServerW =
(MyWinStationOpenServerW)
GetProcAddress(hWinsta, "WinStationOpenServerW");
if (fpWinStationOpenServerW == NULL || GetLastError() != 0)
return MessageBoxW(NULL,
L"无法定位函数 WinStationOpenServerW 入口点位于动态链接库 winsta.dll 上。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
SetLastError(0);
fpWinStationTerminateProcess =
(MyWinStationTerminateProcess)
GetProcAddress(hWinsta, "WinStationTerminateProcess");
if (fpWinStationTerminateProcess == NULL || GetLastError() != 0)
return MessageBoxW(NULL,
L"无法定位函数 WinStationTerminateProcess 入口点位于动态链接库 winsta.dll 上。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
wcscpy_s(hServer, 45 * sizeof(WCHAR), L"(null)");
if (!WinstaEnumerateSutdownProcess(hServer, wsTargetProcessName, 0))
{
return MessageBoxW(NULL,
L"调用 WinstaEnumerateSutdownProcess 时失败。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
}
else {
printf("操作成功完成!\n");
}
//system("pause");
return 0;
}
BOOL WinstaEnumerateSutdownProcess(
PWSTR hServer,
PWSTR wsTargetProcess,
ULONG ExitCode)
{
BOOLEAN Ret = (unsigned)0;
DWORD dwProcessId = 0;
BOOLEAN ret = (unsigned)0;
if(!wcscmp(hServer, L"(null)"))
hServerSign = NULL;
else
hServerSign =
((MyWinStationOpenServerW)fpWinStationOpenServerW)
(hServer);
if (wsTargetProcess == NULL)
{
printf("错误:内存不足。\n");
return 0;
}
dwProcessId = WinstaEnumerateProcess(wsTargetProcess);
if (dwProcessId == 0)
{
printf("错误:找不到进程名\"%ws\"。\n", wsTargetProcess);
return FALSE;
}
SetLastError(0);
ret = ((MyWinStationTerminateProcess)fpWinStationTerminateProcess)
(hServerSign, dwProcessId, ExitCode);
if (ret == (unsigned)0)
{
DWORD Lasterror = GetLastError();
if (Lasterror == 5)
{
printf("错误:结束进程 \"%ws\" 时失败。\n原因:拒绝访问[%d]。\n",
wsTargetProcess, Lasterror);
MessageBoxW(NULL,
L"调用 WinStationTerminateProcess 时失败。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
}
else {
printf("错误:结束进程 \"%ws\" 时失败。\n原因:[%d]。\n",
wsTargetProcess, Lasterror);
MessageBoxW(NULL,
L"调用 WinStationTerminateProcess 时失败。",
L"应用程序错误",
MB_OK | MB_APPLMODAL | MB_ICONERROR);
}
return FALSE;// TODO: GetLastError();FormatMessage() Ntdll;MessageBox()
}
else return TRUE;
}
DWORD WinstaEnumerateProcess(PWSTR wsTargetProcess)
{
NTSTATUS status = STATUS_SUCCESS;
DWORD dwSize = 0xFFFFF;
DWORD dwNameSize = (DWORD)wcslen(wsTargetProcess) + 1;
if (dwNameSize <= 0) return FALSE;
WCHAR* wsTargetName = new WCHAR[dwNameSize];
WCHAR* wsCurrentName = new WCHAR[MAX_PATH];
PSYSTEM_PROCESS_INFORMATION pInfo = { 0 };
LPTSTR pBuf = NULL;
pBuf = new TCHAR[dwSize];
if (NULL == pBuf)
{
return FALSE;
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuf;
status = ((MyNtQuerySystemInformation)fpNtQuerySystemInformation)
(
SystemProcessInformation,
pInfo,
dwSize,
&dwSize
);
if (!NT_SUCCESS(status)) {/*如果函数执行失败*/
return FALSE;
}
// 遍历结构体,找到对应的进程
while (pInfo->NextEntryDelta)
{
if (0 != pInfo->InheritedFromProcessId)
{
//printf("进程名: %wZ, PID: %d\n", &(pInfo->ProcessName), pInfo->ProcessId);
PWSTR curpoint = pInfo->ProcessName.Buffer;
wcscpy_s(wsCurrentName, MAX_PATH * sizeof(WCHAR), curpoint);
wcscpy_s(wsTargetName, MAX_PATH * sizeof(WCHAR), wsTargetProcess);
if (!_wcsicmp(curpoint, wsTargetProcess))
{
return pInfo->InheritedFromProcessId;
}
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);
}
if (NULL != pBuf)
{
delete[] pBuf;
pBuf = NULL;
}
return FALSE;
}
效果如图:
可以在不提权的情况下结束管理员权限的进程。
后记
Winsta 有好几个有意思的函数,比如可以用其中的函数结束工作站强制注销或关机等等。