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

随着SandboxiePlus版本的迭代,从1.15.10版本开始,原有无限试用其高级功能的方法已经不再适用。

上图中,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这个全局变量中。每次使用带有高级功能的沙盒时,会获取该变量的信息,来判断是否可以使用高级功能,不能使用高级功能则出现如下提示:

如果能够直接修改驱动侧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 < moduleInfo->NumberOfModules; i++)
    {
            if (!_stricmp((const char*)moduleInfo->Modules[i].FullPathName +
                    moduleInfo->Modules[i].OffsetToFileName, "sbiedrv.sys"))
            {
                    sbiedrvBaseAddress = (uint64_t)moduleInfo->Modules[i].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 << "Invalid handle on CreateFileA!" << std::endl;
//Get the last error from windows for CreateFile
std::cout << "Error code: " << GetLastError() << std::endl;
}

// Yes, this buffer seems useless - but without it the driver BSOD's the PC.
//Create a buffer to have data returned to.
void* buf = (void*)malloc(4096);

//Call IOCTL that sets the PID variable and gets past the DWORD check
//0x9e6a0594 - IOCTL Code,设置当前的进程。如果不设置当前进程,无法调用驱动对应的功能,具体原因请逆向分析echo_driver.sys文件
BOOL success = DeviceIoControl(hDevice, 0x9e6a0594, NULL, NULL, buf, 4096, NULL, NULL);
if (!success) {
std::cout << "DeviceIOControl 0x9e6a0594 failed!" << std::endl;
std::cout << "Error code: " << GetLastError() << std::endl;

CloseHandle(hDevice);
return;

}

//We don't need that buffer anymore
free(buf);
}
调用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为一个有效的值,实现了无证书的情况下使用其高级功能的效果

posted @ 2025-06-12 15:35  爷很困扰  阅读(782)  评论(0)    收藏  举报