基于BYOVD方法无限使用SandboxiePlus的高级功能

查看 30|回复 4
作者:暴龙兽   
前言
随着SandboxiePlus版本的迭代,从1.15.10版本开始,原有无限试用其高级功能的方法已经不再适用。


verify_certinfo_active.png (178.76 KB, 下载次数: 0)
下载附件
verify_certinfo_active
2025-6-10 16:06 上传

上图中,proces.c中的Process_Create函数在目标进程启动的时候校验了证书的active字段.如果active是0,则在目标进程启动五分钟后,kill掉目标进程。否则,进程正常执行,不进行kill。
原来的破解方法是基于证书有效期的字段全在用户态进行校验,修改用户态校验的结果,进而达到无限试用高级功能的目的。但从1.15.10版本开始出现了驱动侧(r0)验证证书的有效性,自然而然原来的破解方法就失效了。本文使用BYOVD的方法修改驱动侧的全局变量Verify_CertInfo,实现在没有证书的情况下也可以免费试用其高级功能。
原理介绍
BYOVD(Bring Your Own Vulnerable Driver),即攻击者向目标环境植入一个带有漏洞的合法驱动程序,再通过漏洞利用获得内核权限以杀死/致盲终端安全软件等。这项技术最初主要被如Turla和方程式这样的顶级APT组织所使用,而随着攻击成本的降低,其它攻击组织也逐渐开始使用这项技术。
在SandboxiePlus 高级功能无限试用文章中,我介绍了SandboxiePlus的验证过程。它通过验证证书的内容和签名,来判断当前证书的类型和有效期,最后将这些信息写入到Verify_CertInfo这个全局变量中。每次使用带有高级功能的沙盒时,会获取该变量的信息,来判断是否可以使用高级功能,不能使用高级功能则出现如下提示:


redbox_5_minutes.png (117.88 KB, 下载次数: 0)
下载附件
redbox_5_minutes
2025-6-10 16:06 上传

如果能够直接修改驱动侧Verify_CertInfo变量的值为一个有效期证书验证之后的结果,那么即便没有证书,我们也可以实现无限使用高级功能的效果。而是实现驱动侧修改Verify_CertInfo变量的方法就是使用一个带有写任意地址漏洞的签名驱动。
过程分析
为了保证使用者系统的安全性,在需要破解的时候创建漏洞驱动服务,马上启动该服务。在修改变量结束之后,马上停止该漏洞服务,并删除。这里使用的漏洞驱动是echo_driver,漏洞利用的Github地址是https://github.com/kite03/echoac-poc/tree/main/PoC。
获取Verify_CertInfo变量地址
SandboxiePlus的安装目录中存在SbieDrv.sys和SbieDrv.pdb文件,可以解析这个PDB文件获取Verify_CertInfo变量的偏移offset。然后获取SbieDrv驱动的基地址,然后两者之和就是Verify_CertInfo地址。在Windows系统中,所有进程共享内核地址空间,因此当前进程修改Verify_CertInfo地址就可以实现预期的效果。

  • 借助DbgHelp库中的函数获取Verify_CertInfo变量的偏移。
    // 参数是sbiedrv驱动路径和sbiedrv pdb路径
    uint64_t getVerifyCertInfoOffset(const wchar_t* sysPath, const wchar_t* pdbPath)
    {
        uint64_t res = 0;
        SymInitialize(GetCurrentProcess(), NULL, FALSE);
        // load sys file
        uint64_t baseAddress = SymLoadModuleExW(GetCurrentProcess(), NULL, sysPath, NULL, 0x10000000, 0, NULL, 0);
        if (baseAddress == 0)
        {
                printf("[!] Error load sys file\n");
                return 0;
        }
        // load pdb file
        if (!SymSetSearchPathW(GetCurrentProcess(), pdbPath))
        {
                printf("[!] Error load pdb file\n");
                SymUnloadModule(GetCurrentProcess(), baseAddress);
                SymCleanup(GetCurrentProcess());
                return 0;
        }
        char symbolBuffer[sizeof(SYMBOL_INFOW) + 0x100 * sizeof(wchar_t)] = { 0 };
        PSYMBOL_INFOW pSymbol = (PSYMBOL_INFOW)symbolBuffer;
        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFOW);
        pSymbol->MaxNameLen = 0xff;
        if (SymFromNameW(GetCurrentProcess(), L"Verify_CertInfo", pSymbol))
        {
                res = pSymbol->Address - baseAddress;
        }
        SymUnloadModule(GetCurrentProcess(), baseAddress);
        SymCleanup(GetCurrentProcess());
        return res;
    }
  • 遍历当前系统中的驱动模块,获取SbieDrv模块的基地址。

    // 这里需要使用未公开的系统调用参数SystemModuleInformation,对应的系统调用是NtQuerySystemInformation
    uint64_t getSbieDrvBaseAddress()
    {
            NTSTATUS status = STATUS_SUCCESS;
            uint64_t sbiedrvBaseAddress = 0;
            PRTL_PROCESS_MODULES moduleInfo = (PRTL_PROCESS_MODULES)VirtualAlloc(NULL, 0x400 * 0x400, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
            if (moduleInfo == NULL)
            {
                    printf("[!] Error allocating module memory! Error Code: %lu\n", GetLastError());
                    return 0;
            }
            if(!NT_SUCCESS(status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)11,
                    moduleInfo, 0x400 * 0x400, NULL)))
            {
                    printf("[!] Error: Unable to query module list (%#x)\n", status);
                    VirtualFree(moduleInfo, 0, MEM_RELEASE);
                    return 0;
            }
            for (ULONG i = 0; i NumberOfModules; i++)
            {
                    if (!_stricmp((const char*)moduleInfo->Modules.FullPathName +
                            moduleInfo->Modules.OffsetToFileName, "sbiedrv.sys"))
                    {
                            sbiedrvBaseAddress = (uint64_t)moduleInfo->Modules.ImageBase;
                            break;
                    }
            }
            return sbiedrvBaseAddress;
    }
    修改Verify_CertInfo变量的值

  • 前文echoac-poc项目首先需要将代签名的漏洞驱动echo_driver.sys运行起来,然后获取驱动创建的设备的句柄,并设置当前进程。
    DriverInterface::DriverInterface()
    {
    // 获取对应的设备句柄
    hDevice = CreateFileA(
        "\\\\.\\EchoDrv",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        NULL,
        NULL
    );
    //If driver handle failed to open print message and return
    if (hDevice == INVALID_HANDLE_VALUE) {
        std::cout
  • 调用read_memory_raw函数将合适的值写入到Verify_CertInfo变量中

    HANDLE processHandle = Driver.get_handle_for_pid(GetCurrentProcessId());
    uint64_t newValue = 0xfffffffff000fcc1;
    // 0x000c3550 f000fcc1 -> 0xffffffff f000fcc1,其中0x000c3550表示证书的有效期,修改为0xffffffff表示可以无限长时间使用
    Driver.read_memory_raw((void *)&newValue, (void *)(SbieDrvBaseAddress + verifyCertInfoOffset), 8, processHandle);
    // read_memory_raw 函数如下
    BOOL read_memory_raw(void* address, void* buf, size_t len, HANDLE targetProcess)
    {
        k_param_readmem req{};
        req.fromAddress = (void*)address;
        req.length = len;
        req.targetProcess = targetProcess;
        req.toAddress = (void*)buf;
        BOOL success = DeviceIoControl(hDevice, 0x60a26124, &req, sizeof(k_param_readmem), &req, sizeof(k_param_readmem), NULL, NULL);
        return success;
    }
    read_memory_raw函数参数的意思是将targetProcess进程的address地址的len字节内容写入到当前进程buf地址中。这里processHandle我们设置成当前进程,表示从当前进程中读取newValue对应的值写入到Verify_CertInfo变量中,而newValue变量的值是一个合法试用期证书的值,但将时间调至了无限。
    什么时候修改
    前面两个小结介绍了修改变量Verify_CertInfo的方法,即怎么修改。还有一个关键问题:什么时候修改?通过阅读SandboxiePlus的代码,发现了什么时候会触发Verify_CertInfo变量的修改:
  • SbieDrv.sys驱动加载时,会校验证书的有效性。

    _FX NTSTATUS DriverEntry(
        IN  DRIVER_OBJECT  *DriverObject,
        IN  UNICODE_STRING *RegistryPath)
    {
        ......
        if (ok)
            MyValidateCertificate();
        ......
    }
    _FX NTSTATUS MyValidateCertificate(void)
    {
        if(!*g_uuid_str)
            InitFwUuid();
        NTSTATUS status = KphValidateCertificate();
        if (status == STATUS_ACCOUNT_EXPIRED)
            status = STATUS_SUCCESS;
        return status;
    }
  • 用户态的校验。SbieDrv模块的源码中有一个Conf_Api_Reload,当用户态调用该函数且带有SBIE_CONF_FLAG_RELOAD_CERT参数时,会重置Verify_CertInfo变量的值。而且调用这个参数的地方有多出。

    _FX NTSTATUS Conf_Api_Reload(PROCESS *proc, ULONG64 *parms)
    {
        NTSTATUS status;
        ULONG flags;
        if (proc)
            return STATUS_NOT_IMPLEMENTED;
        flags = (ULONG)parms[2];
        if (flags & SBIE_CONF_FLAG_RELOAD_CERT) {
            status = MyValidateCertificate();
            goto finish;
        }
        ......
    }
    解决方法:
  • 第一种触发情况,其实不必处理,因为SandboxiePlus必须依赖SbieDrv驱动。可以将第一种情况归类于第二种情况。
  • 第二种触发情况,可以选择在用户态调用Conf_Api_Reload之后,执行一下我们的修改程序。幸运的是,调用Conf_Api_Reload的地方都在dll中,可以修改dll中的代码,然后编译替换对应的模块即可。这里修改的代码为SbieAPI.cpp文件中的ReloadConf函数,修改的函数如下:

    SB_STATUS CSbieAPI::ReloadConf(quint32 flags, quint32 SessionId)
    {
            __declspec(align(8)) ULONG64 parms[API_NUM_ARGS];
            memset(parms, 0, sizeof(parms));
            parms[0] = API_RELOAD_CONF;
            parms[1] = SessionId;
            parms[2] = flags;
            NTSTATUS status = m->IoControl(parms);
        // 新增的代码
            /**********************开始*************************/
        if(flags & SBIE_CONF_FLAG_RELOAD_CERT)
            {
                    WCHAR exePatchPath[MAX_PATH] = { L'\x00' };
                    if (GetModuleFileNameW(NULL, exePatchPath, MAX_PATH))
                    {
                            PathRemoveFileSpecW(exePatchPath);
                // SandBoxiePlusPatch是我们编译出的补丁文件,用于加载驱动和修改Verify_CertInfo变量
                            wcscat(exePatchPath, L"\\SandBoxiePlusPatch.exe");
                            ShellExecuteW(NULL, L"open", exePatchPath, NULL, NULL, SW_SHOW);
                            OutputDebugStringW(exePatchPath);
                    }
            }
        /**********************结尾*************************/
            if (!NT_SUCCESS(status))
                    return SB_ERR(status);
            emit ConfigReloaded();
            m_bBoxesDirty = true;
            return SB_OK;
    }
    总结
    本文利用BYOVD方法直接修改驱动SbieDrv的变量Verify_CertInfo为一个有效的值,实现了无证书的情况下使用其高级功能的效果。

    变量, 进程

  • qqycra   

    感谢分享,支持下,看看你的文章
    zhouxinyi   

    已经准备购买授权了,不过还是感谢技术分析
    暴龙兽
    OP
      


    zhouxinyi 发表于 2025-6-10 17:13
    已经准备购买授权了,不过还是感谢技术分析

    买订阅制的?
    m_h   

    看上去好复杂 没学会。
    您需要登录后才可以回帖 登录 | 立即注册