最近更新于 2023-02-05 00:55

环境

开发

Windows SDK 10.0(Windows 11)
平台工具集:Visual Studio 2022 v143
C 语言标准:C17
字符集:Unicode
运行库:多线程 /MT (链接静态库,运行程序的系统不需要安装运行库)

测试

Windows 7 旗舰版(虚拟机)

注:涉及注册表操作,不建议在主机测试,一不小心可能会把系统搞崩。

程序逻辑

主要就两步,一个是实现重启,另外一个是编辑注册表让程序开机自启动,在 “计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run” 下添加一个字符串值,名字自己取,键值写入可执行文件的绝对路径。
在测试中发现,没有管理员权限写注册表会失败。要想做到无限重启就必须要写注册表,也就必须至少以管理员权限运行一次。
项目属性可以设置程序运行就请求管理员权限(下图1),但是这样每次运行都会弹出 UAC(下图2),只要中途点了否,那么就不会继续重启了。当然如果把 UAC 调到“从不通知”的话(下图3),使用管理员权限都不会弹 UAC 问你了。
file
file
file

因此不能设置默认请求管理员权限,那么就需要首次以管理员权限运行做到将自启动写到注册表。此时考虑的就是重启和添加注册表各自的功能实现函数哪个先哪个后执行,如果不判断返回值的话,先后就无所谓了。我这里代码判断了返回值(方便调试),如果添加注册的模块先执行,第一次运行有管理员权限,添加注册表会成功,然后执行重启。但是开机后自动运行第二轮的时候,添加注册表没有管理员权限失败就直接 return 了,就不会执行后面那个重启。而重启放前面,在第一次管理员权限执行的时候,虽然发出了重启指令,但是重启执行前有个间隙足够在注册表写入自启动了。第二轮的时候,先执行重启,再执行添加注册表,这时候没有管理员权限必然失败,但是自启动只要第一次写成功了,以后都无所谓了,重启执行下来就又开始循环了。
如果不使用管理员权限执行,写自启动不会成功,这个程序就单纯成为了一个“重启图标”,一运行就重启。

在测试中,多次重启以后,最终出现了下面的情况
file
点还原可以修复好
file

手动停止方法:
1.开机一直按 F8 进安全模式,安全模式下只加载系统必要的组件,可以删除添加的注册表值或者删除程序文件(有可能优化开机速度的时候禁止了 F8 进安全模式)
file
file

2.拼手速,开机后在重启执行前把程序文件删掉,再次开机后就不会重启了
3.如果是有开机密码的,只要不输入密码登录,重启就不会执行。然后按住 Shift 点重启进 Recovery 模式,里面也有途径进安全模式。(Windows 7 不行,10 和 11 可以,8 和 8.1 不清楚)
file

代码

/**
* @file automatic restart.c
* @author IYATT-yx
* @brief 无限重启
*/
#include <Windows.h>

/**
 * @brief 重启系统
 * @return TRUE 成功;FALSE 失败
*/
BOOL reboot()
{
    HANDLE token = NULL; // 用于存放当前进程的令牌
    TOKEN_PRIVILEGES tkp = {0};

     // 获取当前进程的令牌
    if (!OpenProcessToken(GetCurrentProcess(), // 当前进程句柄
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, // 在令牌中启用或禁用特权所必需的; 查询令牌所必需的
                          &token)) // 新打开的令牌的句柄
    {
        return (FALSE);
    }

    // 获取关机权限的 LUID
    LookupPrivilegeValue(NULL, // 在本地系统上查找特权名称
                         SE_SHUTDOWN_NAME, // 特权名称
                         &tkp.Privileges[0].Luid); // 接收 LUID

    tkp.PrivilegeCount = 1; // 设置一个权限
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 启用特权

    // 获取此进程的关闭权限
    AdjustTokenPrivileges(token, // 指定令牌
                          FALSE, // 设置 TRUE 禁用所有令牌权限,FALSE 根据下一个参数修改特权
                          &tkp, // 指定特权
                          0, // 指定下一个参数指向的缓冲区的大小
                          (PTOKEN_PRIVILEGES)NULL, // 接收已调整的权限
                          0); // 接收前一个参数指向的缓冲区所需的大小

    if (GetLastError() != ERROR_SUCCESS)
    {
        return FALSE;
    }

    // 关闭系统且强制所有应用关闭,并重启
    if (!ExitWindowsEx(EWX_REBOOT | EWX_FORCE, // 强制重启
                       SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED)) // 关闭的原因:主要 其它;次要 其它;计划内
    {
        return FALSE;
    }

    return TRUE;
}

/**
 * @brief 向注册表写入自启动
 * @return TRUE 成功;FALSE 失败
*/
BOOL setAutoStart()
{
    HKEY regKey = {0};
    // 打开注册表 - 可能需要管理员权限执行
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, // 注册表项
                     L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", // 要打开的注册表子项的名称
                     0, // 指定打开键时要应用的选项
                     KEY_WRITE, // 一个掩码,指定要打开的密钥的所需访问权限
                     &regKey) // 一个变量的指针,此变量指向已打开键的句柄
        != ERROR_SUCCESS)
    {
        return FALSE;
    }

    WCHAR exePath[MAX_PATH] = {0};
    if (GetModuleFileName(NULL, // 已加载模块的句柄,NULL 检索当前可执行文件路径
                          exePath,
                          MAX_PATH)
        == 0)
    {
        return FALSE;
    }

    if (RegSetValueEx(regKey,
                      L"automatic restart", // 注册表值名称
                      0, // 保留参数必须为 0
                      REG_SZ, // 注册表值类型
                      (PBYTE)exePath, // 注册表值
                      sizeof(exePath)) // 前一个参数指向信息的大小
        != ERROR_SUCCESS)
    {
        return FALSE;
    }

    RegCloseKey(regKey);
    return TRUE;
}

int main()
{
    if (!reboot())
    {
        return 1;
    }
    if (!setAutoStart())
    {
        return 2;
    }
    return 0;
}