暴龙兽 发表于 2025-9-22 10:00

Win11环境下使用BYOVD方法破解SandboxiePlus验证

前几天有评论说win11环境下之前的方法不行,试了一下确实不行。主要原因还是echo_driver.sys驱动加载失败了,返回的错误码是0x800B010C,对应的含义如下:

需要重新寻找新的具有任意地址读写漏洞的具有合法签名驱动文件。
经过一段时间的寻找,功夫不负有心人,终于找到了一个符合需求的项目BYOVD_read_write_primitive,相关项目地址如下:https://github.com/0xJs/BYOVD_read_write_primitive 。重新编写程序的过程中,发现一个有意思的问题:win11环境下,管理员执行NtQuerySystemInformation获取SbieDrv驱动模块基地址的竟然是0,而在win10环境下符合预期。
本篇文章的第一部分主要探究这个问题,第二部分给出基于BYOVD_read_write_primitive的破解思路。
# NtQuerySystemInformation系统调用问题
破解程序使用该系统调用获取SbieDrv驱动模块的基地址,它的函数原型如下:
```c++
NTSTATUS NtQuerySystemInformation(
            SYSTEM_INFORMATION_CLASS SystemInformationClass,
       PVOID                  SystemInformation,
            ULONG                  SystemInformationLength,
PULONG                   ReturnLength
);
```
第一个参数为SystemModuleInformation(11)时,获取当前所有驱动模块的信息,比较文件名即可获取SbieDrv驱动模块的基地址。

## Win10环境
当NtQuerySystemInformation系统调用的第一个参数为SystemModuleInformation时,NtQuerySystemInformation会调用ExpQuerySystemInformation函数,该函数相关的代码如下:

- ExIsRestrictedCaller的IDA F5反汇编代码如下:

```c++

_BOOL8 __fastcall ExIsRestrictedCaller(char a1)
{
BOOLEAN v1; // bl
_BOOL8 result; // rax
struct _SECURITY_SUBJECT_CONTEXT SubjectSecurityContext; // BYREF
NTSTATUS AccessStatus; // BYREF
ACCESS_MASK GrantedAccess; // BYREF
AccessStatus = 0;
GrantedAccess = 0;
memset(&SubjectSecurityContext, 0, sizeof(SubjectSecurityContext));
result = 0;
if ( a1 )
{
    SeCaptureSubjectContext(&SubjectSecurityContext);
    v1 = SeAccessCheck(
         SeMediumDaclSd,// SecurityDescriptor
         &SubjectSecurityContext, // SubjectSecurityContext
         0,   // SubjectContextLocked
         0x20000u,    // DesiredAccess, READ_CONTROL ->
         0,         // PreviouslyGrantedAccess
         0i64,      // Privileges
         (PGENERIC_MAPPING)&ExpRestrictedGenericMapping,// GenericMapping
         1,   // AccessMode,KernelMode -> 0, UserMode -> 1
         &GrantedAccess,// GrantedAccess
         &AccessStatus);// AccessStatus
    SeReleaseSubjectContext(&SubjectSecurityContext);
    if ( !v1 || AccessStatus < 0 )
      return 1;
}
return result;
}
```
如果ExIsRestrictedCaller返回一个true,则NtQuerySystemInformatio n系统调用返回一个0xC0000022,表示STATUS_ACCESS_DENIED。管理员启动一个进程的话,ExIsRestrictedCaller返回一个False,非管理员启动则返回一个True。

- 接下来执行ExpQueryModuleInformation函数,


从PsLoadModuleList链表中遍历已经加载的内核模块,提取信息赋值给NtQuerySystemInformation的第二个参数SystemInformation,它是一个PRTL_PROCESS_MODULES结构体。该结构体的内容如下:

```c++
typedef struct _RTL_PROCESS_MODULE_INFORMATION
{
    HANDLE Section;
    PVOID MappedBase;
    PVOID ImageBase;
    ULONG ImageSize;
    ULONG Flags;
    USHORT LoadOrderIndex;
    USHORT InitOrderIndex;
    USHORT LoadCount;
    USHORT OffsetToFileName;
    UCHAR FullPathName;
} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES
{
    ULONG NumberOfModules;
    RTL_PROCESS_MODULE_INFORMATION Modules;
}
//
```
**总结**:在win10环境下,只要使用管理员启动进程来调用NtQuerySyst emInformation系统调用即可获取所有内核模块的的信息,包括基地址。
## Win11环境
Win11下NtQuerySystemInformation系统调用的执行和win10相似,主要在ExIsRestrictedCaller和ExpQueryModuleInformation函数中有些不同。

- ExIsRestrictedCaller函数的不同,增加了是否拥有SeDebugPrivilege权限的验证。

```c++

__int64 __fastcall ExIsRestrictedCaller(KPROCESSOR_MODE a1, _DWORD *a2)
{
unsigned int v2; // edi
BOOLEAN v5; // bl
struct _SECURITY_SUBJECT_CONTEXT SubjectContext; // BYREF
NTSTATUS AccessStatus; // BYREF
ACCESS_MASK GrantedAccess; // BYREF
v2 = 0;
AccessStatus = 0;
GrantedAccess = 0;
memset(&SubjectContext, 0, sizeof(SubjectContext));
if ( a2 )
    *a2 = 0;
if ( !a1 )
    return 0i64;
// 不同之处,新增
if ( a2 && (unsigned int)Feature_RestrictKernelAddressLeaks__private_IsEnabledDeviceUsageNoInline() )
    *a2 = SeSinglePrivilegeCheck(SeDebugPrivilege, a1) == 0;   
SeCaptureSubjectContext(&SubjectContext);
v5 = SeAccessCheck(
         SeMediumDaclSd,
         &SubjectContext,
         0,
         0x20000u,
         0,
         0i64,
         (PGENERIC_MAPPING)&ExpRestrictedGenericMapping,
         1,
         &GrantedAccess,
         &AccessStatus);
SeReleaseSubjectContext(&SubjectContext);
if ( !v5 )
    return 1i64;
LOBYTE(v2) = AccessStatus < 0;
return v2;
}
```

如果拥有SeDebugPrivilege权限,将a2指针处设置为False,否则为True。从Feature_RestrictKernelAddressLeaks__private_IsEnabled DeviceUsageNoInline这个符号名也可以看出,此处是为了限制非SeDebugPrivilege权限的进程获取内核模块的基地址的。

- ExpQueryModuleInformation函数的不同,ExIsRestrictedCaller函数验证通过之后,会将a2指针处的值传递给ExpQueryModuleInforma tion函数的第一个参数。

```c++

__int64 __fastcall ExpQueryModuleInformation(int a1, _DWORD *a2, unsigned int a3, _DWORD *a4)
{
__int64 result; // rax
unsigned int v8; // ecx
_QWORD v9; // BYREF
unsigned int v10; //
int v11; //
_DWORD *v12; //
int v13; //
int v14; //
v9 = 0i64;
v13 = a1;
v14 = 0;
v12 = a4;
v11 = 8;
v9 = a2 + 2;
v10 = a3;
// v9是一个结构体,
result = MmEnumerateSystemImagesShared(ExpQueryModuleInformationImage, v9);   
v8 = 0xC0000004;
if ( (int)(result + 0x80000000) < 0 || (_DWORD)result == 0xC0000004 )
{
    *a4 = v11;
    if ( a3 >= 8 )
    {
      *a2 = v14;
      return LODWORD(v9);
    }
    return v8;
}
return result;
}
__int64 __fastcall MmEnumerateSystemImagesShared(__int64 a1, __int64 a2)
{
return MiEnumerateSystemImages(a1, a2, 2i64);
}
```

- MiEnumerateSystemImages函数的反汇编代码如下:

```c++

// a1 -> ExpQueryModuleInformationImage函数
// a2 -> ExpQueryModuleInformation的v9
__int64 __fastcall MiEnumerateSystemImages(__int64 a1, __int64 a2, __int64 a3)
{
struct _KTHREAD *CurrentThread; // r9
int v4; // edi
unsigned int v5; // r12d
__int64 Lock; // rax
PVOID *v8; // rbx
__int64 v9; // rbp
__int64 v10; // r8
__int64 v11; // r9
PVOID *i; // rbx
CurrentThread = KeGetCurrentThread();
v4 = 0;
v5 = a3;
if ( (struct _KTHREAD *)qword_140E2D610 == CurrentThread )
{
    for ( i = (PVOID *)PsLoadedModuleList; i != &PsLoadedModuleList; i = (PVOID *)*i )
    {
      v4 = guard_dispatch_icall_no_overrides(i, a2, a3, CurrentThread); //
      if ( v4 < 0 )
      break;
    }
}
else
{
    Lock = MiAcquireLoadLock(0i64);
    v8 = (PVOID *)PsLoadedModuleList;
    v9 = Lock;
    while ( v8 != &PsLoadedModuleList )
    {
      MiLockLoaderEntry(v8, v5);
      v4 = guard_dispatch_icall_no_overrides(v8, a2, v10, v11);
      MiUnlockLoaderEntry(v8, v5);
      if ( v4 < 0 )
      break;
      v8 = (PVOID *)*v8;
    }
    MmReleaseLoadLockShared(v9);
}
return (unsigned int)v4;
}
```

这个函数就是遍历PsLoadedModuleList链表中的内核模块,每一个模块都调用一次ExpQueryModuleInformationImage函数。ExpQueryModuleInformationImage函数伪代码如下:


其实a2就是v9,那a2+0x20其实就是ExpQueryModuleInformation函数的 _QWORD v9; // BYREF加0x20,就是 int v13; // 。这就是说,如果拥有SeDebugPrivilege就将内核模块的地址透出,否则置为空。
**总结:**win11环境下除了需要管理员权限启动进程之外,还需要额外获取SeDebugPrivilege权限才能破解成功。设想一下,补丁程序在win11上面运行,管理员权限启动,发现破解失败;这时你需要使用调试器去定位问题,发现又可以破解成功,这就很让人迷糊。当你使用管理员权限启动一个调试器创建管理员进程,进行分析,其实被调试的进程已经具备了SeDebugPrivilege权限,但如果你附加调试,目标进程的权限位并未发生变化。

而问题的真正原因呢,还是在内核,如果不逆向分析内核,无法得出定位出。
# 破解程序代码
- 第一步:获取Debug权限

```c++
bool GetDebugPrivilege()
{
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tokenPrivileges;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
      std::cerr << "Error : get current process token, error code : " << GetLastError() << std::endl;
      return false;
    }
    if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &luid))
    {
      std::cerr << "Error : get debug privilege luid, error code : " << GetLastError() << std::endl;
      return false;
    }
    tokenPrivileges.PrivilegeCount = 1;
    tokenPrivileges.Privileges.Luid = luid;
    tokenPrivileges.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
    if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, NULL))
    {
      std::cerr << "Error : adjust debug privilege, error code : " << GetLastError() << std::endl;
      CloseHandle(hToken);
      return false;
    }
    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
      std::cerr << "Error : no enough privilege to adjust debug privilege" << std::endl;
      CloseHandle(hToken);
      return false;
    }
    CloseHandle(hToken);
    return true;
}
```
- 第二步:获取Verify_CertInfo的地址。getVerifyCertInfoOffset获取Verify_CertInfo关键验证变量的偏移
```c++
// sysPath -> 驱动文件的位置
// pdbPath -> 驱动文件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 = { 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;
}
```

getSbieDrvBaseAddress获取SbieDrv内核模块的基地址

```c++
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.FullPathName +
            moduleInfo->Modules.OffsetToFileName, "sbiedrv.sys"))
      {
            sbiedrvBaseAddress = (uint64_t)moduleInfo->Modules.ImageBase;
            break;
      }
    }
    return sbiedrvBaseAddress;
}
```
最后将两个函数的结果相加即可得到Verify_CertInfo变量的内核地址


第三步:修改变量的内容
```c++
DriverInterface Driver(rotcoreSysPath);
uint64_t newValue = 0xfffffffff000fcc1;
res =Driver.write_arbitary_address_memory((SbieDrvBaseAddress + verifyCertInfoOffset), (newValue & 0xffffffff));      // // 0x000c3550 f000fcc1 -> 0xffffffff f000fcc1
if (res == false)
{
      printf("Failure\n");
      return 0;
}
res = Driver.write_arbitary_address_memory((SbieDrvBaseAddress + verifyCertInfoOffset + 4), (newValue & 0xffffffff00000000) >> 32);      // // 0x000c3550 f000fcc1 -> 0xffffffff f000fcc1
if (res == false)
{
      printf("Failure\n");
      return 0;
}
```

# 总结
Win11系统中采用更加严格的驱动策略导致驱动加载失败,无法破解SandboxiePlus。使用新版漏洞驱动之后,内核的一些策略导致了获取驱动地址失败。以后,为了兼容各种windows系统版本,可以check一遍操作所需的权限,不管有没有已经获得。

VMxxxz 发表于 2025-9-22 10:44

厉害了,我滴神。学习学习

w1066602520 发表于 2025-9-22 10:45

好复杂 先试试

HuaGdao1 发表于 2025-9-22 10:52

技术贴,学习了

SN1t2lO 发表于 2025-9-22 11:16

只要不升级,第一代方案好用

暴龙兽 发表于 2025-9-22 11:57

SN1t2lO 发表于 2025-9-22 11:16
只要不升级,第一代方案好用

新版本会修复一些bug和cve漏洞

seanwang 发表于 2025-9-22 20:52

新版本修复了cve漏洞,这个挺好的。

yuhu1123 发表于 2025-9-23 20:35

好东西啊   不过我用的还是WIN10

jlt 发表于 2025-9-23 21:13

厉害了,我滴神

weiguopei 发表于 2025-9-23 22:51

虽然看不懂,但还是觉得很厉害!
页: [1] 2 3 4 5
查看完整版本: Win11环境下使用BYOVD方法破解SandboxiePlus验证