Windowsをリモートから強制的に再起動する方法(カーネルモード)

前回の日記で、『再起動に失敗した Windows をリモートから強制的に再起動する方法』を書きましたが、カーネルモードドライバを用いて、やはり Windows を強制的に再起動する方法を紹介します。

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
	KeBugCheckEx(0x000000E5L, 0, 0, 0, 0);
	return STATUS_SUCCESS;
}


上記のドライバを WindowsDDK で適当にビルドし、再起動したいマシンの SCM (サービスコトロールマネージャ) 経由でシステムサービスとして Windows レジストリに登録し、そのドライバを開始するだけで、コンピュータをブルースクリーンにして再起動することができます。


これで、再起動処理の直前に停止し、telnetssh で接続したり新しいプロセスを開始したりすることができないリモートマシンを強制的に再起動することができます。


どのようにリモートマシン上にドライバを登録するか、ということですが、

  1. sc コマンドを用いる
  2. CreateService API 等を用いる


といった方法があると思われます。
CreateService API 等を用いる方法を以下に書きます。

// 超いいかげんに書いたプログラム  by dnobori

#define _CRT_SECURE_NO_WARNINGS
#define SECURITY_WIN32
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char exename[MAX_PATH];
char exedir[MAX_PATH];

// いいかげんなプログラム
void GetDirNameFromFilePath(char *dst, UINT size, char *filepath)
{
	char tmp[520];
	UINT wp;
	UINT i;
	UINT len;
	// 引数チェック
	if (dst == NULL || filepath == NULL)
	{
		return;
	}

	strcpy(tmp, filepath);

	len = strlen(tmp);

	strcpy(dst, "");

	wp = 0;

	for (i = 0;i < len;i++)
	{
		char c = tmp[i];
		if (c == '/' || c == '\\')
		{
			tmp[wp++] = 0;
			wp = 0;
			strcat(dst, tmp);
			tmp[wp++] = c;
		}
		else
		{
			tmp[wp++] = c;
		}
	}

	if (strlen(dst) == 0)
	{
		strcpy(dst, "/");
	}
}

int do_main(char *pcname)
{
	char tmp[MAX_PATH];
	char src[MAX_PATH];

	sprintf(src, "%s\\krdriver.sys", exedir);
	sprintf(tmp, "\\\\%s\\c$\\tmp", pcname);

	CreateDirectory(tmp, NULL);

	sprintf(tmp, "\\\\%s\\c$\\tmp\\krdriver.sys", pcname);

	if (CopyFile(src, tmp, FALSE) == FALSE)
	{
		printf("Copy file to %s Error.\n", tmp);
		return 0;
	}
	else
	{
		SC_HANDLE sc, service;

		sc = OpenSCManagerA(pcname, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
		if (sc == NULL)
		{
			printf("OpenSCManagerA Error.\n");
			return 0;
		}

		service = CreateServiceA(sc, "KernelReboot", "KernelReboot by daiyuu nobori", SERVICE_ALL_ACCESS,
			SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, "c:\\tmp\\krdriver.sys",
			NULL, NULL, NULL, NULL, NULL);

		if (service == NULL)
		{
			printf("CreateServiceA Error. %u\n", GetLastError());
		}
		else
		{
			printf("CreateServiceA Ok.\n");
			CloseHandle(service);
		}

		printf("exec OpenService...\n");
		service = OpenService(sc, "KernelReboot", SERVICE_START | SERVICE_QUERY_STATUS);
		if (service == NULL)
		{
			printf("OpenService Error. %u\n", GetLastError());
		}
		else
		{
			printf("OpenService ok.\n");
			printf("exec StartServiceA...\n");
			if (StartServiceA(service, 0, NULL) == FALSE)
			{
				printf("StartServiceA Error. %u\n", GetLastError());
				return 0;
			}
			else
			{
				printf("ok.\n");
			}
		}
	}

	return 1;
}

int main(int argc, char **argv)
{
	char *pcname = "dummy_str";

	if (argc <= 1)
	{
		printf("Usage: kreboot <PCNAME>\n");
		return 0;
	}

	pcname = argv[1];

	GetModuleFileNameA(GetModuleHandle(NULL), exename, sizeof(exename));
	GetDirNameFromFilePath(exedir, sizeof(exedir), exename);

	if (do_main(pcname) == 0)
	{
		printf("regist_driver() Failed.\n");
		return -1;
	}

	return 0;
}


汚いコードなので間違いがあったらすみません。


上記をコンパイルしたプログラムの引数としてターゲットの PC 名を指定すると、まずその PC の c$\tmp に krdriver.sys (1 個目のソースコードDDKコンパイルすると出てくる sys ファイル) をコピーし、次にそれをカーネルサービス (ドライバ) として登録し、最後にドライバを開始して Windowsブルースクリーンにしてくれます。